Merge branch 'master' into feature/autoscaling

# Conflicts:
#	solr/CHANGES.txt
This commit is contained in:
Shalin Shekhar Mangar 2017-09-18 09:23:41 -07:00
commit de3107c70f
149 changed files with 5575 additions and 807 deletions

View File

@ -374,7 +374,23 @@
</subant> </subant>
</target> </target>
<target name="validate-maven-dependencies" depends="generate-maven-artifacts" <target name="-install-maven-artifacts" depends="resolve,resolve-groovy,resolve-markdown,install-maven-tasks">
<ant dir="lucene" inheritall="false">
<target name="-unpack-lucene-tgz"/>
<target name="-filter-pom-templates"/>
<propertyset refid="uptodate.and.compiled.properties"/>
</ant>
<ant dir="solr" target="-unpack-solr-tgz" inheritall="false">
<propertyset refid="uptodate.and.compiled.properties"/>
</ant>
<subant target="-install-to-maven-local-repo" inheritall="false" failonerror="true">
<fileset dir="lucene" includes="build.xml" />
<fileset dir="solr" includes="build.xml" />
<propertyset refid="uptodate.and.compiled.properties"/>
</subant>
</target>
<target name="validate-maven-dependencies" depends="-install-maven-artifacts"
description="Validates maven dependencies, licenses, etc."> description="Validates maven dependencies, licenses, etc.">
<subant target="-validate-maven-dependencies" inheritall="false" failonerror="true"> <subant target="-validate-maven-dependencies" inheritall="false" failonerror="true">
<fileset dir="lucene" includes="build.xml"/> <fileset dir="lucene" includes="build.xml"/>

View File

@ -66,6 +66,13 @@
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
<release>
<Version>
<name>lucene-6.6.1</name>
<created>2017-09-07</created>
<revision>6.6.1</revision>
</Version>
</release>
<release> <release>
<Version> <Version>
<name>lucene-6.6.0</name> <name>lucene-6.6.0</name>

View File

@ -66,6 +66,13 @@
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
<release>
<Version>
<name>solr-6.6.1</name>
<created>2017-09-07</created>
<revision>6.6.1</revision>
</Version>
</release>
<release> <release>
<Version> <Version>
<name>solr-6.6.0</name> <name>solr-6.6.0</name>

View File

@ -23,6 +23,8 @@ New Features
* LUCENE-7927: Add LongValueFacetCounts, to compute facet counts for individual * LUCENE-7927: Add LongValueFacetCounts, to compute facet counts for individual
numeric values (Mike McCandless) numeric values (Mike McCandless)
* LUCENE-7940: Add BengaliAnalyzer. (Md. Abdulla-Al-Sun via Robert Muir)
Optimizations Optimizations
* LUCENE-7905: Optimize how OrdinalMap (used by * LUCENE-7905: Optimize how OrdinalMap (used by
@ -52,6 +54,17 @@ Bug Fixes
not recommended, lucene-analyzers-icu contains binary data structures not recommended, lucene-analyzers-icu contains binary data structures
specific to ICU/Unicode versions it is built against. (Chris Koenig, Robert Muir) specific to ICU/Unicode versions it is built against. (Chris Koenig, Robert Muir)
* LUCENE-7891: Lucene's taxonomy facets now uses a non-buggy LRU cache
by default. (Jan-Willem van den Broek via Mike McCandless)
* LUCENE-7959: Improve NativeFSLockFactory's exception message if it cannot create
write.lock for an empty index due to bad permissions/read-only filesystem/etc.
(Erick Erickson, Shawn Heisey, Robert Muir)
* LUCENE-7968: AnalyzingSuggester would sometimes order suggestions incorrectly,
it did not properly break ties on the surface forms when both the weights and
the analyzed forms were equal. (Robert Muir)
Build Build
* SOLR-11181: Switch order of maven artifact publishing procedure: deploy first * SOLR-11181: Switch order of maven artifact publishing procedure: deploy first
@ -192,6 +205,13 @@ Bug Fixes
* LUCENE-7864: IndexMergeTool is not using intermediate hard links (even * LUCENE-7864: IndexMergeTool is not using intermediate hard links (even
if possible). (Dawid Weiss) if possible). (Dawid Weiss)
* LUCENE-7956: Fixed potential stack overflow error in ICUNormalizer2CharFilter.
(Adrien Grand)
* LUCENE-7963: Remove useless getAttribute() in DefaultIndexingChain that
causes performance drop, introduced by LUCENE-7626. (Daniel Mitterdorfer
via Uwe Schindler)
Improvements Improvements
* LUCENE-7489: Better storage of sparse doc-values fields with the default * LUCENE-7489: Better storage of sparse doc-values fields with the default

View File

@ -54,13 +54,14 @@ The KStem stemmer in
was developed by Bob Krovetz and Sergio Guzman-Lara (CIIR-UMass Amherst) was developed by Bob Krovetz and Sergio Guzman-Lara (CIIR-UMass Amherst)
under the BSD-license. under the BSD-license.
The Arabic,Persian,Romanian,Bulgarian, and Hindi analyzers (common) come with a default The Arabic,Persian,Romanian,Bulgarian, Hindi and Bengali analyzers (common) come with a default
stopword list that is BSD-licensed created by Jacques Savoy. These files reside in: stopword list that is BSD-licensed created by Jacques Savoy. These files reside in:
analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt, analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt,
analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt, analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt,
analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt, analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt,
analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt, analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt,
analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt,
analysis/common/src/resources/org/apache/lucene/analysis/bn/stopwords.txt
See http://members.unine.ch/jacques.savoy/clef/index.html. See http://members.unine.ch/jacques.savoy/clef/index.html.
The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers

View File

@ -125,6 +125,10 @@
<forall-analyzers target="-dist-maven"/> <forall-analyzers target="-dist-maven"/>
</target> </target>
<target name="-install-to-maven-local-repo">
<forall-analyzers target="-install-to-maven-local-repo"/>
</target>
<target name="-validate-maven-dependencies"> <target name="-validate-maven-dependencies">
<forall-analyzers target="-validate-maven-dependencies"/> <forall-analyzers target="-validate-maven-dependencies"/>
</target> </target>

View File

@ -0,0 +1,132 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.core.DecimalDigitFilter;
import org.apache.lucene.analysis.in.IndicNormalizationFilter;
import org.apache.lucene.analysis.miscellaneous.SetKeywordMarkerFilter;
import org.apache.lucene.analysis.standard.StandardFilter;
import org.apache.lucene.analysis.standard.StandardTokenizer;
import java.io.IOException;
import java.io.Reader;
/**
* Analyzer for Bengali.
*/
public final class BengaliAnalyzer extends StopwordAnalyzerBase {
private final CharArraySet stemExclusionSet;
/**
* File containing default Bengali stopwords.
*
* Default stopword list is from http://members.unine.ch/jacques.savoy/clef/bengaliST.txt
* The stopword list is BSD-Licensed.
*/
public final static String DEFAULT_STOPWORD_FILE = "stopwords.txt";
private static final String STOPWORDS_COMMENT = "#";
/**
* Returns an unmodifiable instance of the default stop-words set.
* @return an unmodifiable instance of the default stop-words set.
*/
public static CharArraySet getDefaultStopSet(){
return DefaultSetHolder.DEFAULT_STOP_SET;
}
/**
* Atomically loads the DEFAULT_STOP_SET in a lazy fashion once the outer class
* accesses the static final set the first time.;
*/
private static class DefaultSetHolder {
static final CharArraySet DEFAULT_STOP_SET;
static {
try {
DEFAULT_STOP_SET = loadStopwordSet(false, BengaliAnalyzer.class, DEFAULT_STOPWORD_FILE, STOPWORDS_COMMENT);
} catch (IOException ex) {
throw new RuntimeException("Unable to load default stopword set");
}
}
}
/**
* Builds an analyzer with the given stop words
*
* @param stopwords a stopword set
* @param stemExclusionSet a stemming exclusion set
*/
public BengaliAnalyzer(CharArraySet stopwords, CharArraySet stemExclusionSet) {
super(stopwords);
this.stemExclusionSet = CharArraySet.unmodifiableSet(CharArraySet.copy(stemExclusionSet));
}
/**
* Builds an analyzer with the given stop words
*
* @param stopwords a stopword set
*/
public BengaliAnalyzer(CharArraySet stopwords) {
this(stopwords, CharArraySet.EMPTY_SET);
}
/**
* Builds an analyzer with the default stop words:
* {@link #DEFAULT_STOPWORD_FILE}.
*/
public BengaliAnalyzer() {
this(DefaultSetHolder.DEFAULT_STOP_SET);
}
/**
* Creates
* {@link org.apache.lucene.analysis.Analyzer.TokenStreamComponents}
* used to tokenize all the text in the provided {@link Reader}.
*
* @return {@link org.apache.lucene.analysis.Analyzer.TokenStreamComponents}
* built from a {@link StandardTokenizer} filtered with
* {@link LowerCaseFilter}, {@link DecimalDigitFilter}, {@link IndicNormalizationFilter},
* {@link BengaliNormalizationFilter}, {@link SetKeywordMarkerFilter}
* if a stem exclusion set is provided, {@link BengaliStemFilter}, and
* Bengali Stop words
*/
@Override
protected TokenStreamComponents createComponents(String fieldName) {
final Tokenizer source = new StandardTokenizer();
TokenStream result = new LowerCaseFilter(source);
result = new DecimalDigitFilter(result);
if (!stemExclusionSet.isEmpty())
result = new SetKeywordMarkerFilter(result, stemExclusionSet);
result = new IndicNormalizationFilter(result);
result = new BengaliNormalizationFilter(result);
result = new StopFilter(result, stopwords);
result = new BengaliStemFilter(result);
return new TokenStreamComponents(source, result);
}
@Override
protected TokenStream normalize(String fieldName, TokenStream in) {
TokenStream result = new StandardFilter(in);
result = new LowerCaseFilter(result);
result = new DecimalDigitFilter(result);
result = new IndicNormalizationFilter(result);
result = new BengaliNormalizationFilter(result);
return result;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.miscellaneous.SetKeywordMarkerFilter;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.KeywordAttribute;
import java.io.IOException;
/**
* A {@link TokenFilter} that applies {@link BengaliNormalizer} to normalize the
* orthography.
* <p>
* In some cases the normalization may cause unrelated terms to conflate, so
* to prevent terms from being normalized use an instance of
* {@link SetKeywordMarkerFilter} or a custom {@link TokenFilter} that sets
* the {@link KeywordAttribute} before this {@link TokenStream}.
* </p>
* @see BengaliNormalizer
*/
public final class BengaliNormalizationFilter extends TokenFilter {
private final BengaliNormalizer normalizer = new BengaliNormalizer();
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final KeywordAttribute keywordAtt = addAttribute(KeywordAttribute.class);
public BengaliNormalizationFilter(TokenStream input) {
super(input);
}
@Override
public boolean incrementToken() throws IOException {
if (input.incrementToken()) {
if (!keywordAtt.isKeyword())
termAtt.setLength(normalizer.normalize(termAtt.buffer(),
termAtt.length()));
return true;
}
return false;
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.util.AbstractAnalysisFactory;
import org.apache.lucene.analysis.util.MultiTermAwareComponent;
import org.apache.lucene.analysis.util.TokenFilterFactory;
import java.util.Map;
/**
* Factory for {@link BengaliNormalizationFilter}.
* <pre class="prettyprint">
* &lt;fieldType name="text_bnnormal" class="solr.TextField" positionIncrementGap="100"&gt;
* &lt;analyzer&gt;
* &lt;tokenizer class="solr.StandardTokenizerFactory"/&gt;
* &lt;filter class="solr.BengaliNormalizationFilterFactory"/&gt;
* &lt;/analyzer&gt;
* &lt;/fieldType&gt;</pre>
*/
public class BengaliNormalizationFilterFactory extends TokenFilterFactory implements MultiTermAwareComponent {
public BengaliNormalizationFilterFactory(Map<String,String> args) {
super(args);
if (!args.isEmpty()) {
throw new IllegalArgumentException("Unknown parameters: " + args);
}
}
@Override
public TokenStream create(TokenStream input) {
return new BengaliNormalizationFilter(input);
}
@Override
public AbstractAnalysisFactory getMultiTermComponent() {
return this;
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.lucene.analysis.bn;
import static org.apache.lucene.analysis.util.StemmerUtil.delete;
/**
* Normalizer for Bengali.
* <p>
* Implements the Bengali-language specific algorithm specified in:
* <i>A Double Metaphone encoding for Bangla and its application in spelling checker</i>
* Naushad UzZaman and Mumit Khan.
* http://www.panl10n.net/english/final%20reports/pdf%20files/Bangladesh/BAN16.pdf
* </p>
*/
public class BengaliNormalizer {
/**
* Normalize an input buffer of Bengali text
*
* @param s input buffer
* @param len length of input buffer
* @return length of input buffer after normalization
*/
public int normalize(char s[], int len) {
for (int i = 0; i < len; i++) {
switch (s[i]) {
// delete Chandrabindu
case '\u0981':
len = delete(s, i, len);
i--;
break;
// DirghoI kar -> RosshoI kar
case '\u09C0':
s[i] = '\u09BF';
break;
// DirghoU kar -> RosshoU kar
case '\u09C2':
s[i] = '\u09C1';
break;
// Khio (Ka + Hoshonto + Murdorno Sh)
case '\u0995':
if(i + 2 < len && s[i+1] == '\u09CD' && s[i+2] == '\u09BF') {
if (i == 0) {
s[i] = '\u0996';
len = delete(s, i + 2, len);
len = delete(s, i + 1, len);
} else {
s[i+1] = '\u0996';
len = delete(s, i + 2, len);
}
}
break;
// Nga to Anusvara
case '\u0999':
s[i] = '\u0982';
break;
// Ja Phala
case '\u09AF':
if(i - 2 == 0 && s[i-1] == '\u09CD') {
s[i - 1] = '\u09C7';
if(i + 1 < len && s[i+1] == '\u09BE') {
len = delete(s, i+1, len);
}
len = delete(s, i, len);
i --;
} else if(i - 1 >= 0 && s[i-1] == '\u09CD' ){
len = delete(s, i, len);
len = delete(s, i-1, len);
i -=2;
}
break;
// Ba Phalaa
case '\u09AC':
if((i >= 1 && s[i-1] != '\u09CD') || i == 0)
break;
if(i - 2 == 0) {
len = delete(s, i, len);
len = delete(s, i - 1, len);
i -= 2;
} else if(i - 5 >= 0 && s[i - 3] == '\u09CD') {
len = delete(s, i, len);
len = delete(s, i-1, len);
i -=2;
} else if(i - 2 >= 0){
s[i - 1] = s[i - 2];
len = delete(s, i, len);
i --;
}
break;
// Visarga
case '\u0983':
if(i == len -1) {
if(len <= 3) {
s[i] = '\u09B9';
} else {
len = delete(s, i, len);
}
} else {
s[i] = s[i+1];
}
break;
//All sh
case '\u09B6':
case '\u09B7':
s[i] = '\u09B8';
break;
//check na
case '\u09A3':
s[i] = '\u09A8';
break;
//check ra
case '\u09DC':
case '\u09DD':
s[i] = '\u09B0';
break;
case '\u09CE':
s[i] = '\u09A4';
break;
default:
break;
}
}
return len;
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.KeywordAttribute;
import java.io.IOException;
/**
* A {@link TokenFilter} that applies {@link BengaliStemmer} to stem Bengali words.
*/
public final class BengaliStemFilter extends TokenFilter {
private final CharTermAttribute termAttribute = addAttribute(CharTermAttribute.class);
private final KeywordAttribute keywordAttribute = addAttribute(KeywordAttribute.class);
private final BengaliStemmer bengaliStemmer = new BengaliStemmer();
public BengaliStemFilter(TokenStream input) {
super(input);
}
@Override
public boolean incrementToken() throws IOException {
if (input.incrementToken()) {
if (!keywordAttribute.isKeyword())
termAttribute.setLength(bengaliStemmer.stem(termAttribute.buffer(), termAttribute.length()));
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.util.TokenFilterFactory;
import java.util.Map;
/**
* Factory for {@link BengaliStemFilter}.
* <pre class="prettyprint">
* &lt;fieldType name="text_histem" class="solr.TextField" positionIncrementGap="100"&gt;
* &lt;analyzer&gt;
* &lt;tokenizer class="solr.StandardTokenizerFactory"/&gt;
* &lt;filter class="solr.BengaliStemFilterFactory"/&gt;
* &lt;/analyzer&gt;
* &lt;/fieldType&gt;</pre>
*/
public class BengaliStemFilterFactory extends TokenFilterFactory {
public BengaliStemFilterFactory(Map<String,String> args) {
super(args);
if (!args.isEmpty()) {
throw new IllegalArgumentException("Unknown parameters: " + args);
}
}
@Override
public TokenStream create(TokenStream input) {
return new BengaliStemFilter(input);
}
}

View File

@ -0,0 +1,183 @@
/*
* 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.lucene.analysis.bn;
import static org.apache.lucene.analysis.util.StemmerUtil.endsWith;
/**
* Stemmer for Bengali.
* <p>
* The algorithm is based on the report in:
* <i>Natural Language Processing in an Indian Language (Bengali)-I: Verb Phrase Analysis</i>
* P Sengupta and B B Chaudhuri
* </p>
*
* <p>
* Few Stemmer criteria are taken from:
* <i>http://members.unine.ch/jacques.savoy/clef/BengaliStemmerLight.java.txt</i>
* </p>
*/
public class BengaliStemmer {
public int stem(char buffer[], int len) {
// 8
if (len > 9 && (endsWith(buffer, len, "িয়াছিলাম")
|| endsWith(buffer, len, "িতেছিলাম")
|| endsWith(buffer, len, "িতেছিলেন")
|| endsWith(buffer, len, "ইতেছিলেন")
|| endsWith(buffer, len, "িয়াছিলেন")
|| endsWith(buffer, len, "ইয়াছিলেন")
))
return len - 8;
// 7
if ((len > 8) && (endsWith(buffer, len, "িতেছিলি")
|| endsWith(buffer, len, "িতেছিলে")
|| endsWith(buffer, len, "িয়াছিলা")
|| endsWith(buffer, len, "িয়াছিলে")
|| endsWith(buffer, len, "িতেছিলা")
|| endsWith(buffer, len, "িয়াছিলি")
|| endsWith(buffer, len, "য়েদেরকে")
))
return len - 7;
// 6
if ((len > 7) && (endsWith(buffer, len, "িতেছিস")
|| endsWith(buffer, len, "িতেছেন")
|| endsWith(buffer, len, "িয়াছিস")
|| endsWith(buffer, len, "িয়াছেন")
|| endsWith(buffer, len, "েছিলাম")
|| endsWith(buffer, len, "েছিলেন")
|| endsWith(buffer, len, "েদেরকে")
))
return len - 6;
// 5
if ((len > 6) && (endsWith(buffer, len, "িতেছি")
|| endsWith(buffer, len, "িতেছা")
|| endsWith(buffer, len, "িতেছে")
|| endsWith(buffer, len, "ছিলাম")
|| endsWith(buffer, len, "ছিলেন")
|| endsWith(buffer, len, "িয়াছি")
|| endsWith(buffer, len, "িয়াছা")
|| endsWith(buffer, len, "িয়াছে")
|| endsWith(buffer, len, "েছিলে")
|| endsWith(buffer, len, "েছিলা")
|| endsWith(buffer, len, "য়েদের")
|| endsWith(buffer, len, "দেরকে")
))
return len - 5;
// 4
if ((len > 5) && (endsWith(buffer, len, "িলাম")
|| endsWith(buffer, len, "িলেন")
|| endsWith(buffer, len, "িতাম")
|| endsWith(buffer, len, "িতেন")
|| endsWith(buffer, len, "িবেন")
|| endsWith(buffer, len, "ছিলি")
|| endsWith(buffer, len, "ছিলে")
|| endsWith(buffer, len, "ছিলা")
|| endsWith(buffer, len, "তেছে")
|| endsWith(buffer, len, "িতেছ")
|| endsWith(buffer, len, "খানা")
|| endsWith(buffer, len, "খানি")
|| endsWith(buffer, len, "গুলো")
|| endsWith(buffer, len, "গুলি")
|| endsWith(buffer, len, "য়েরা")
|| endsWith(buffer, len, "েদের")
))
return len - 4;
// 3
if ((len > 4) && (endsWith(buffer, len, "লাম")
|| endsWith(buffer, len, "িলি")
|| endsWith(buffer, len, "ইলি")
|| endsWith(buffer, len, "িলে")
|| endsWith(buffer, len, "ইলে")
|| endsWith(buffer, len, "লেন")
|| endsWith(buffer, len, "িলা")
|| endsWith(buffer, len, "ইলা")
|| endsWith(buffer, len, "তাম")
|| endsWith(buffer, len, "িতি")
|| endsWith(buffer, len, "ইতি")
|| endsWith(buffer, len, "িতে")
|| endsWith(buffer, len, "ইতে")
|| endsWith(buffer, len, "তেন")
|| endsWith(buffer, len, "িতা")
|| endsWith(buffer, len, "িবা")
|| endsWith(buffer, len, "ইবা")
|| endsWith(buffer, len, "িবি")
|| endsWith(buffer, len, "ইবি")
|| endsWith(buffer, len, "বেন")
|| endsWith(buffer, len, "িবে")
|| endsWith(buffer, len, "ইবে")
|| endsWith(buffer, len, "ছেন")
|| endsWith(buffer, len, "য়োন")
|| endsWith(buffer, len, "য়ের")
|| endsWith(buffer, len, "েরা")
|| endsWith(buffer, len, "দের")
))
return len - 3;
// 2
if ((len > 3) && (endsWith(buffer, len, "িস")
|| endsWith(buffer, len, "েন")
|| endsWith(buffer, len, "লি")
|| endsWith(buffer, len, "লে")
|| endsWith(buffer, len, "লা")
|| endsWith(buffer, len, "তি")
|| endsWith(buffer, len, "তে")
|| endsWith(buffer, len, "তা")
|| endsWith(buffer, len, "বি")
|| endsWith(buffer, len, "বে")
|| endsWith(buffer, len, "বা")
|| endsWith(buffer, len, "ছি")
|| endsWith(buffer, len, "ছা")
|| endsWith(buffer, len, "ছে")
|| endsWith(buffer, len, "ুন")
|| endsWith(buffer, len, "ুক")
|| endsWith(buffer, len, "টা")
|| endsWith(buffer, len, "টি")
|| endsWith(buffer, len, "নি")
|| endsWith(buffer, len, "ের")
|| endsWith(buffer, len, "তে")
|| endsWith(buffer, len, "রা")
|| endsWith(buffer, len, "কে")
))
return len - 2;
// 1
if ((len > 2) && (endsWith(buffer, len, "ি")
|| endsWith(buffer, len, "")
|| endsWith(buffer, len, "")
|| endsWith(buffer, len, "")
|| endsWith(buffer, len, "")
|| endsWith(buffer, len, "")
|| endsWith(buffer, len, "")
))
return len - 1;
return len;
}
}

View File

@ -14,24 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.solr.search;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
/** /**
* Parse Solr's variant of Lucene QueryParser syntax, including the * Analyzer for Bengali Language.
* deprecated sort specification after the query.
* <br>Example: <code>{!lucenePlusSort}myfield:foo +bar -baz;price asc</code>
*
* @deprecated This class should have been removed a long time ago, it will be removed in Solr 8.0
*/ */
@Deprecated package org.apache.lucene.analysis.bn;
public class OldLuceneQParserPlugin extends QParserPlugin {
public static final String NAME = "lucenePlusSort";
@Override
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
return new OldLuceneQParser(qstr, localParams, params, req);
}
}

View File

@ -17,6 +17,8 @@ org.apache.lucene.analysis.tr.ApostropheFilterFactory
org.apache.lucene.analysis.ar.ArabicNormalizationFilterFactory org.apache.lucene.analysis.ar.ArabicNormalizationFilterFactory
org.apache.lucene.analysis.ar.ArabicStemFilterFactory org.apache.lucene.analysis.ar.ArabicStemFilterFactory
org.apache.lucene.analysis.bg.BulgarianStemFilterFactory org.apache.lucene.analysis.bg.BulgarianStemFilterFactory
org.apache.lucene.analysis.bn.BengaliNormalizationFilterFactory
org.apache.lucene.analysis.bn.BengaliStemFilterFactory
org.apache.lucene.analysis.br.BrazilianStemFilterFactory org.apache.lucene.analysis.br.BrazilianStemFilterFactory
org.apache.lucene.analysis.cjk.CJKBigramFilterFactory org.apache.lucene.analysis.cjk.CJKBigramFilterFactory
org.apache.lucene.analysis.cjk.CJKWidthFilterFactory org.apache.lucene.analysis.cjk.CJKWidthFilterFactory

View File

@ -0,0 +1,121 @@
# See http://members.unine.ch/jacques.savoy/clef/index.html.
# This file was created by Jacques Savoy and is distributed under the BSD license
এই
থেকে
করে
না
ওই
এক্
নিয়ে
করা
বলেন
সঙ্গে
যে
এব
তা
আর
কোনো
বলে
সেই
দিন
হয়
কি
দু
পরে
সব
দেওয়া
মধ্যে
এর
সি
শুরু
কাজ
কিছু
কাছে
সে
তবে
বা
বন
আগে
জ্নজন
পি
পর
তো
ছিল
এখন
আমরা
প্রায়
দুই
আমাদের
তাই
অন্য
গিয়ে
প্রযন্ত
মনে
নতুন
মতো
কেখা
প্রথম
আজ
টি
ধামার
অনেক
বিভিন্ন
হাজার
জানা
নয়
অবশ্য
বেশি
এস
করে
কে
হতে
বি
কয়েক
সহ
বেশ
এমন
এমনি
কেন
কেউ
নেওয়া
চেষ্টা
লক্ষ
বলা
কারণ
আছে
শুধু
তখন
যা
এসে
চার
ছিল
যদি
আবার
কোটি
উত্তর
সামনে
উপর
বক্তব্য
এত
প্রাথমিক
উপরে
আছে
প্রতি
কাজে
যখন
খুব
বহু
গেল
পেয়্র্
চালু
নাগাদ
থাকা
পাচ
যাওয়া
রকম
সাধারণ
কমনে

View File

@ -0,0 +1,53 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
/**
* Tests the BengaliAnalyzer
*/
public class TestBengaliAnalyzer extends BaseTokenStreamTestCase {
public void testResourcesAvailable() {
new BengaliAnalyzer().close();
}
public void testBasics() throws Exception {
Analyzer a = new BengaliAnalyzer();
checkOneTerm(a, "বাড়ী", "বার");
checkOneTerm(a, "বারী", "বার");
a.close();
}
/**
* test Digits
*/
public void testDigits() throws Exception {
BengaliAnalyzer a = new BengaliAnalyzer();
checkOneTerm(a, "১২৩৪৫৬৭৮৯০", "1234567890");
a.close();
}
/** blast some random strings through the analyzer */
public void testRandomStrings() throws Exception {
Analyzer analyzer = new BengaliAnalyzer();
checkRandomData(random(), analyzer, 1000*RANDOM_MULTIPLIER);
analyzer.close();
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.lucene.analysis.bn;
import java.io.Reader;
import java.io.StringReader;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.util.BaseTokenStreamFactoryTestCase;
/**
* Test Bengali Filter Factory
*/
public class TestBengaliFilters extends BaseTokenStreamFactoryTestCase {
/**
* Test IndicNormalizationFilterFactory
*/
public void testIndicNormalizer() throws Exception {
Reader reader = new StringReader("ত্‍ আমি");
TokenStream stream = whitespaceMockTokenizer(reader);
stream = tokenFilterFactory("IndicNormalization").create(stream);
assertTokenStreamContents(stream, new String[] { "", "আমি" });
}
/**
* Test BengaliNormalizationFilterFactory
*/
public void testBengaliNormalizer() throws Exception {
Reader reader = new StringReader("বাড়ী");
TokenStream stream = whitespaceMockTokenizer(reader);
stream = tokenFilterFactory("IndicNormalization").create(stream);
stream = tokenFilterFactory("BengaliNormalization").create(stream);
assertTokenStreamContents(stream, new String[] {"বারি"});
}
/**
* Test BengaliStemFilterFactory
*/
public void testStemmer() throws Exception {
Reader reader = new StringReader("বাড়ী");
TokenStream stream = whitespaceMockTokenizer(reader);
stream = tokenFilterFactory("IndicNormalization").create(stream);
stream = tokenFilterFactory("BengaliNormalization").create(stream);
stream = tokenFilterFactory("BengaliStem").create(stream);
assertTokenStreamContents(stream, new String[] {"বার"});
}
/** Test that bogus arguments result in exception */
public void testBogusArguments() throws Exception {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
tokenFilterFactory("IndicNormalization", "bogusArg", "bogusValue");
});
assertTrue(expected.getMessage().contains("Unknown parameters"));
expected = expectThrows(IllegalArgumentException.class, () -> {
tokenFilterFactory("BengaliNormalization", "bogusArg", "bogusValue");
});
assertTrue(expected.getMessage().contains("Unknown parameters"));
expected = expectThrows(IllegalArgumentException.class, () -> {
tokenFilterFactory("BengaliStem", "bogusArg", "bogusValue");
});
assertTrue(expected.getMessage().contains("Unknown parameters"));
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import org.apache.lucene.util.TestUtil;
import java.io.IOException;
/**
* Test BengaliNormalizer
*/
public class TestBengaliNormalizer extends BaseTokenStreamTestCase {
/**
* Test some basic normalization, with an example from the paper.
*/
public void testChndrobindu() throws IOException {
check("চাঁদ", "চাদ");
}
public void testRosshoIKar() throws IOException {
check("বাড়ী", "বারি");
check("তীর", "তির");
}
public void testRosshoUKar() throws IOException {
check("ভূল", "ভুল");
check("অনূপ", "অনুপ");
}
public void testNga() throws IOException {
check("বাঙলা", "বাংলা");
}
public void testJaPhaala() throws IOException {
check("ব্যাক্তি", "বেক্তি");
check( "সন্ধ্যা", "সন্ধা");
}
public void testBaPhalaa() throws IOException {
check("স্বদেশ", "সদেস");
check("তত্ত্ব", "তত্ত");
check("বিশ্ব", "বিসস");
}
public void testVisarga() throws IOException {
check("দুঃখ", "দুখখ");
check("উঃ", "উহ");
check("পুনঃ", "পুন");
}
public void testBasics() throws IOException {
check("কণা", "কনা");
check("শরীর", "সরির");
check("বাড়ি", "বারি");
}
/** creates random strings in the bengali block and ensures the normalizer doesn't trip up on them */
public void testRandom() throws IOException {
BengaliNormalizer normalizer = new BengaliNormalizer();
for (int i = 0; i < 100000; i++) {
String randomBengali = TestUtil.randomSimpleStringRange(random(), '\u0980', '\u09FF', 7);
try {
int newLen = normalizer.normalize(randomBengali.toCharArray(), randomBengali.length());
assertTrue(newLen >= 0); // should not return negative length
assertTrue(newLen <= randomBengali.length()); // should not increase length of string
} catch (Exception e) {
System.err.println("normalizer failed on input: '" + randomBengali + "' (" + escape(randomBengali) + ")");
throw e;
}
}
}
private void check(String input, String output) throws IOException {
Tokenizer tokenizer = whitespaceMockTokenizer(input);
TokenFilter tf = new BengaliNormalizationFilter(tokenizer);
assertTokenStreamContents(tf, new String[] { output });
}
public void testEmptyTerm() throws IOException {
Analyzer a = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer tokenizer = new KeywordTokenizer();
return new TokenStreamComponents(tokenizer, new BengaliNormalizationFilter(tokenizer));
}
};
checkOneTerm(a, "", "");
a.close();
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.lucene.analysis.bn;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import java.io.IOException;
/**
* Test Codes for BengaliStemmer
*/
public class TestBengaliStemmer extends BaseTokenStreamTestCase {
/**
* Testing few verbal words
*/
public void testVerbsInShadhuForm() throws IOException {
check("করেছিলাম", "কর");
check("করিতেছিলে", "কর");
check("খাইতাম", "খাই");
check("যাইবে", "যা");
}
public void testVerbsInCholitoForm() throws IOException {
check("করছিলাম", "কর");
check("করছিলে", "কর");
check("করতাম", "কর");
check("যাব", "যা");
check("যাবে", "যা");
check("করি", "কর");
check("করো", "কর");
}
public void testNouns() throws IOException {
check("মেয়েরা", "মে");
check("মেয়েদেরকে", "মে");
check("মেয়েদের", "মে");
check("একটি", "এক");
check("মানুষগুলি", "মানুষ");
}
private void check(String input, String output) throws IOException {
Tokenizer tokenizer = whitespaceMockTokenizer(input);
TokenFilter tf = new BengaliStemFilter(tokenizer);
assertTokenStreamContents(tf, new String[] { output });
}
public void testEmptyTerm() throws IOException {
Analyzer a = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer tokenizer = new KeywordTokenizer();
return new TokenStreamComponents(tokenizer, new BengaliStemFilter(tokenizer));
}
};
checkOneTerm(a, "", "");
a.close();
}
}

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.util.Objects; import java.util.Objects;
import org.apache.lucene.analysis.CharacterUtils;
import org.apache.lucene.analysis.charfilter.BaseCharFilter; import org.apache.lucene.analysis.charfilter.BaseCharFilter;
import com.ibm.icu.text.Normalizer2; import com.ibm.icu.text.Normalizer2;
@ -61,7 +62,7 @@ public final class ICUNormalizer2CharFilter extends BaseCharFilter {
ICUNormalizer2CharFilter(Reader in, Normalizer2 normalizer, int bufferSize) { ICUNormalizer2CharFilter(Reader in, Normalizer2 normalizer, int bufferSize) {
super(in); super(in);
this.normalizer = Objects.requireNonNull(normalizer); this.normalizer = Objects.requireNonNull(normalizer);
this.tmpBuffer = new char[bufferSize]; this.tmpBuffer = CharacterUtils.newCharacterBuffer(bufferSize);
} }
@Override @Override
@ -94,23 +95,31 @@ public final class ICUNormalizer2CharFilter extends BaseCharFilter {
return -1; return -1;
} }
private final char[] tmpBuffer; private final CharacterUtils.CharacterBuffer tmpBuffer;
private int readInputToBuffer() throws IOException { private void readInputToBuffer() throws IOException {
final int len = input.read(tmpBuffer); while (true) {
if (len == -1) { // CharacterUtils.fill is supplementary char aware
final boolean hasRemainingChars = CharacterUtils.fill(tmpBuffer, input);
assert tmpBuffer.getOffset() == 0;
inputBuffer.append(tmpBuffer.getBuffer(), 0, tmpBuffer.getLength());
if (hasRemainingChars == false) {
inputFinished = true; inputFinished = true;
return 0; break;
}
final int lastCodePoint = Character.codePointBefore(tmpBuffer.getBuffer(), tmpBuffer.getLength(), 0);
if (normalizer.isInert(lastCodePoint)) {
// we require an inert char so that we can normalize content before and
// after this character independently
break;
}
} }
inputBuffer.append(tmpBuffer, 0, len);
// if checkedInputBoundary was at the end of a buffer, we need to check that char again // if checkedInputBoundary was at the end of a buffer, we need to check that char again
checkedInputBoundary = Math.max(checkedInputBoundary - 1, 0); checkedInputBoundary = Math.max(checkedInputBoundary - 1, 0);
// this loop depends on 'isInert' (changes under normalization) but looks only at characters.
// so we treat all surrogates as non-inert for simplicity
if (normalizer.isInert(tmpBuffer[len - 1]) && !Character.isSurrogate(tmpBuffer[len-1])) {
return len;
} else return len + readInputToBuffer();
} }
private int readAndNormalizeFromInput() { private int readAndNormalizeFromInput() {

View File

@ -20,12 +20,14 @@ package org.apache.lucene.analysis.icu;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.util.Arrays;
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.BaseTokenStreamTestCase; import org.apache.lucene.analysis.BaseTokenStreamTestCase;
import org.apache.lucene.analysis.CharFilter; import org.apache.lucene.analysis.CharFilter;
import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.KeywordTokenizer;
import org.apache.lucene.analysis.ngram.NGramTokenizer; import org.apache.lucene.analysis.ngram.NGramTokenizer;
import org.apache.lucene.util.TestUtil; import org.apache.lucene.util.TestUtil;
@ -418,4 +420,23 @@ public class TestICUNormalizer2CharFilter extends BaseTokenStreamTestCase {
} }
a.close(); a.close();
} }
// https://issues.apache.org/jira/browse/LUCENE-7956
public void testVeryLargeInputOfNonInertChars() throws Exception {
char[] text = new char[1000000];
Arrays.fill(text, 'a');
try (Analyzer a = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
return new TokenStreamComponents(new KeywordTokenizer());
}
@Override
protected Reader initReader(String fieldName, Reader reader) {
return new ICUNormalizer2CharFilter(reader, Normalizer2.getInstance(null, "nfkc_cf", Normalizer2.Mode.COMPOSE));
}
}) {
checkAnalysisConsistency(random(), a, false, new String(text));
}
}
} }

View File

@ -432,7 +432,9 @@ public class TestBackwardsCompatibility extends LuceneTestCase {
"6.5.1-cfs", "6.5.1-cfs",
"6.5.1-nocfs", "6.5.1-nocfs",
"6.6.0-cfs", "6.6.0-cfs",
"6.6.0-nocfs" "6.6.0-nocfs",
"6.6.1-cfs",
"6.6.1-nocfs"
}; };
// TODO: on 6.0.0 release, gen the single segment indices and add here: // TODO: on 6.0.0 release, gen the single segment indices and add here:

View File

@ -428,6 +428,19 @@
</sequential> </sequential>
</target> </target>
<target name="-install-to-maven-local-repo" depends="install-maven-tasks">
<sequential>
<m2-install pom.xml="${filtered.pom.templates.dir}/pom.xml"/> <!-- Lucene/Solr grandparent POM -->
<m2-install pom.xml="${filtered.pom.templates.dir}/lucene/pom.xml"/> <!-- Lucene parent POM -->
<subant target="-install-to-maven-local-repo" failonerror="true" inheritall="false">
<propertyset refid="uptodate.and.compiled.properties"/>
<fileset dir="${common.dir}/core" includes="build.xml"/>
<fileset dir="${common.dir}/test-framework" includes="build.xml"/>
</subant>
<modules-crawl target="-install-to-maven-local-repo"/>
</sequential>
</target>
<target name="generate-maven-artifacts" depends="-unpack-lucene-tgz"> <target name="generate-maven-artifacts" depends="-unpack-lucene-tgz">
<ant dir=".." target="resolve" inheritall="false"/> <ant dir=".." target="resolve" inheritall="false"/>
<antcall target="-filter-pom-templates" inheritall="false"/> <antcall target="-filter-pom-templates" inheritall="false"/>

View File

@ -23,4 +23,6 @@
<import file="../module-build.xml"/> <import file="../module-build.xml"/>
<target name="-dist-maven" depends="-dist-maven-src-java"/> <target name="-dist-maven" depends="-dist-maven-src-java"/>
<target name="-install-to-maven-local-repo" depends="-install-src-java-to-maven-local-repo"/>
</project> </project>

View File

@ -596,6 +596,19 @@
</sequential> </sequential>
</macrodef> </macrodef>
<macrodef name="m2-install" description="Installs a Maven artifact into the local repository">
<element name="parent-poms" optional="yes"/>
<attribute name="pom.xml"/>
<attribute name="jar.file" default="${dist.jar.dir.prefix}-${version}/${dist.jar.dir.suffix}/${final.name}.jar"/>
<sequential>
<parent-poms/>
<artifact:pom id="maven.project" file="@{pom.xml}"/>
<artifact:install file="@{jar.file}">
<pom refid="maven.project"/>
</artifact:install>
</sequential>
</macrodef>
<!-- validate maven dependencies --> <!-- validate maven dependencies -->
<macrodef name="m2-validate-dependencies"> <macrodef name="m2-validate-dependencies">
<attribute name="pom.xml"/> <attribute name="pom.xml"/>
@ -1713,6 +1726,44 @@ ${tests-output}/junit4-*.suites - per-JVM executed suites
</sequential> </sequential>
</target> </target>
<target name="-install-to-maven-local-repo" depends="install-maven-tasks">
<sequential>
<property name="top.level.dir" location="${common.dir}/.."/>
<pathconvert property="pom.xml">
<mapper>
<chainedmapper>
<globmapper from="${top.level.dir}*" to="${filtered.pom.templates.dir}*"/>
<globmapper from="*build.xml" to="*pom.xml"/>
</chainedmapper>
</mapper>
<path location="${ant.file}"/>
</pathconvert>
<artifact:pom id="maven.project" file="${pom.xml}"/>
<artifact:install file="${dist.jar.dir.prefix}-${version}/${dist.jar.dir.suffix}/${final.name}.jar">
<pom refid="maven.project"/>
</artifact:install>
</sequential>
</target>
<target name="-install-src-java-to-maven-local-repo" depends="install-maven-tasks">
<sequential>
<property name="top.level.dir" location="${common.dir}/.."/>
<pathconvert property="pom.xml">
<mapper>
<chainedmapper>
<globmapper from="${top.level.dir}*" to="${filtered.pom.templates.dir}*"/>
<globmapper from="*build.xml" to="*/src/java/pom.xml"/>
</chainedmapper>
</mapper>
<path location="${ant.file}"/>
</pathconvert>
<artifact:pom id="maven.project" file="${pom.xml}"/>
<artifact:install file="${dist.jar.dir.prefix}-${version}/${dist.jar.dir.suffix}/${final.name}.jar">
<pom refid="maven.project"/>
</artifact:install>
</sequential>
</target>
<target name="-dist-maven-src-java" depends="install-maven-tasks, jar-src, javadocs"> <target name="-dist-maven-src-java" depends="install-maven-tasks, jar-src, javadocs">
<sequential> <sequential>
<property name="top.level.dir" location="${common.dir}/.."/> <property name="top.level.dir" location="${common.dir}/.."/>

View File

@ -64,6 +64,8 @@
<target name="-dist-maven" depends="-dist-maven-src-java"/> <target name="-dist-maven" depends="-dist-maven-src-java"/>
<target name="-install-to-maven-local-repo" depends="-install-src-java-to-maven-local-repo"/>
<macrodef name="createLevAutomaton"> <macrodef name="createLevAutomaton">
<attribute name="n"/> <attribute name="n"/>
<sequential> <sequential>

View File

@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.codecs.DocValuesConsumer; import org.apache.lucene.codecs.DocValuesConsumer;
import org.apache.lucene.codecs.DocValuesFormat; import org.apache.lucene.codecs.DocValuesFormat;
import org.apache.lucene.codecs.NormsConsumer; import org.apache.lucene.codecs.NormsConsumer;
@ -733,7 +732,6 @@ final class DefaultIndexingChain extends DocConsumer {
stream.reset(); stream.reset();
invertState.setAttributeSource(stream); invertState.setAttributeSource(stream);
termsHashPerField.start(field, first); termsHashPerField.start(field, first);
CharTermAttribute termAtt = tokenStream.getAttribute(CharTermAttribute.class);
while (stream.incrementToken()) { while (stream.incrementToken()) {

View File

@ -93,15 +93,27 @@ public final class NativeFSLockFactory extends FSLockFactory {
Path lockFile = lockDir.resolve(lockName); Path lockFile = lockDir.resolve(lockName);
IOException creationException = null;
try { try {
Files.createFile(lockFile); Files.createFile(lockFile);
} catch (IOException ignore) { } catch (IOException ignore) {
// we must create the file to have a truly canonical path. // we must create the file to have a truly canonical path.
// if it's already created, we don't care. if it cant be created, it will fail below. // if it's already created, we don't care. if it cant be created, it will fail below.
creationException = ignore;
} }
// fails if the lock file does not exist // fails if the lock file does not exist
final Path realPath = lockFile.toRealPath(); final Path realPath;
try {
realPath = lockFile.toRealPath();
} catch (IOException e) {
// if we couldn't resolve the lock file, it might be because we couldn't create it.
// so append any exception from createFile as a suppressed exception, in case its useful
if (creationException != null) {
e.addSuppressed(creationException);
}
throw e;
}
// used as a best-effort check, to see if the underlying file has changed // used as a best-effort check, to see if the underlying file has changed
final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime(); final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();

View File

@ -18,9 +18,17 @@ package org.apache.lucene.store;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.util.Set;
import org.apache.lucene.mockfile.FilterFileSystemProvider;
import org.apache.lucene.mockfile.FilterPath;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.TestUtil; import org.apache.lucene.util.TestUtil;
@ -89,4 +97,38 @@ public class TestNativeFSLockFactory extends BaseLockFactoryTestCase {
IOUtils.closeWhileHandlingException(lock); IOUtils.closeWhileHandlingException(lock);
} }
} }
/** MockFileSystem that throws AccessDeniedException on creating test.lock */
static class MockBadPermissionsFileSystem extends FilterFileSystemProvider {
public MockBadPermissionsFileSystem(FileSystem delegateInstance) {
super("mockbadpermissions://", delegateInstance);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (path.getFileName().toString().equals("test.lock")) {
throw new AccessDeniedException(path.toString(), null, "fake access denied");
}
return super.newByteChannel(path, options, attrs);
}
}
public void testBadPermissions() throws IOException {
// create a mock filesystem that will throw exc on creating test.lock
Path tmpDir = createTempDir();
tmpDir = FilterPath.unwrap(tmpDir).toRealPath();
FileSystem mock = new MockBadPermissionsFileSystem(tmpDir.getFileSystem()).getFileSystem(null);
Path mockPath = mock.getPath(tmpDir.toString());
// we should get an IOException (typically NoSuchFileException but no guarantee) with
// our fake AccessDenied added as suppressed.
Directory dir = getDirectory(mockPath.resolve("indexDir"));
IOException expected = expectThrows(IOException.class, () -> {
dir.obtainLock("test.lock");
});
AccessDeniedException suppressed = (AccessDeniedException) expected.getSuppressed()[0];
assertTrue(suppressed.getMessage().contains("fake access denied"));
dir.close();
}
} }

View File

@ -32,8 +32,12 @@ public class LruTaxonomyWriterCache implements TaxonomyWriterCache {
* function, LRU_STRING should be used. * function, LRU_STRING should be used.
*/ */
public enum LRUType { public enum LRUType {
/** Use the label's hash as the key; this can lead to /** Use only the label's 64 bit longHashCode as the hash key. Do not
* silent conflicts! */ * check equals, unlike most hash maps.
* Note that while these hashes are very likely to be unique, the chance
* of a collision is still greater than zero. If such an unlikely event
* occurs, your document will get an incorrect facet.
*/
LRU_HASHED, LRU_HASHED,
/** Use the label as the hash key; this is always /** Use the label as the hash key; this is always
@ -43,15 +47,15 @@ public class LruTaxonomyWriterCache implements TaxonomyWriterCache {
private NameIntCacheLRU cache; private NameIntCacheLRU cache;
/** Creates this with {@link LRUType#LRU_HASHED} method. */ /** Creates this with {@link LRUType#LRU_STRING} method. */
public LruTaxonomyWriterCache(int cacheSize) { public LruTaxonomyWriterCache(int cacheSize) {
// TODO (Facet): choose between NameHashIntCacheLRU and NameIntCacheLRU. // TODO (Facet): choose between NameHashIntCacheLRU and NameIntCacheLRU.
// For guaranteed correctness - not relying on no-collisions in the hash // For guaranteed correctness - not relying on no-collisions in the hash
// function, NameIntCacheLRU should be used: // function, NameIntCacheLRU should be used:
// On the other hand, NameHashIntCacheLRU takes less RAM but if there // On the other hand, NameHashIntCacheLRU takes less RAM but if there
// are collisions (which we never found) two different paths would be // are collisions two different paths would be mapped to the same
// mapped to the same ordinal... // ordinal...
this(cacheSize, LRUType.LRU_HASHED); this(cacheSize, LRUType.LRU_STRING);
} }
/** Creates this with the specified method. */ /** Creates this with the specified method. */
@ -60,8 +64,8 @@ public class LruTaxonomyWriterCache implements TaxonomyWriterCache {
// For guaranteed correctness - not relying on no-collisions in the hash // For guaranteed correctness - not relying on no-collisions in the hash
// function, NameIntCacheLRU should be used: // function, NameIntCacheLRU should be used:
// On the other hand, NameHashIntCacheLRU takes less RAM but if there // On the other hand, NameHashIntCacheLRU takes less RAM but if there
// are collisions (which we never found) two different paths would be // are collisions two different paths would be mapped to the same
// mapped to the same ordinal... // ordinal...
if (lruType == LRUType.LRU_HASHED) { if (lruType == LRUType.LRU_HASHED) {
this.cache = new NameHashIntCacheLRU(cacheSize); this.cache = new NameHashIntCacheLRU(cacheSize);
} else { } else {

View File

@ -0,0 +1,50 @@
/*
* 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.lucene.facet.taxonomy.writercache;
import org.apache.lucene.facet.FacetTestCase;
import org.apache.lucene.facet.taxonomy.FacetLabel;
import org.junit.Test;
public class TestLruTaxonomyWriterCache extends FacetTestCase {
@Test
public void testDefaultLRUTypeIsCollisionSafe() {
// These labels are clearly different, but have identical longHashCodes.
// Note that these labels are clearly contrived. We did encounter
// collisions in actual production data, but we aren't allowed to publish
// those.
final FacetLabel a = new FacetLabel("\0", "\u0003\uFFE2");
final FacetLabel b = new FacetLabel("\1", "\0");
// If this fails, then the longHashCode implementation has changed. This
// cannot prevent collisions. (All hashes must allow for collisions.) It
// will however stop the rest of this test from making sense. To fix, find
// new colliding labels, or make a subclass of FacetLabel that produces
// collisions.
assertEquals(a.longHashCode(), b.longHashCode());
// Make a cache with capacity > 2 so both our labels will fit. Don't
// specify an LRUType, since we want to check if the default is
// collision-safe.
final LruTaxonomyWriterCache cache = new LruTaxonomyWriterCache(10);
cache.put(a, 0);
cache.put(b, 1);
assertEquals(cache.get(a), 0);
assertEquals(cache.get(b), 1);
}
}

View File

@ -94,7 +94,7 @@ org.apache.calcite.version = 1.13.0
/org.apache.commons/commons-compress = 1.11 /org.apache.commons/commons-compress = 1.11
/org.apache.commons/commons-exec = 1.3 /org.apache.commons/commons-exec = 1.3
/org.apache.commons/commons-lang3 = 3.6 /org.apache.commons/commons-lang3 = 3.6
/org.apache.commons/commons-math3 = 3.4.1 /org.apache.commons/commons-math3 = 3.6.1
org.apache.curator.version = 2.8.0 org.apache.curator.version = 2.8.0
/org.apache.curator/curator-client = ${org.apache.curator.version} /org.apache.curator/curator-client = ${org.apache.curator.version}

View File

@ -82,6 +82,11 @@ public class GeoBBoxFactory {
//System.err.println(" not vertical line"); //System.err.println(" not vertical line");
if (extent >= Math.PI) { if (extent >= Math.PI) {
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) { if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
return new GeoDegeneratePoint(planetModel, topLat, 0.0);
} else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
return new GeoDegeneratePoint(planetModel, bottomLat, 0.0);
}
//System.err.println(" wide degenerate line"); //System.err.println(" wide degenerate line");
return new GeoWideDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon); return new GeoWideDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);
} }
@ -94,8 +99,10 @@ public class GeoBBoxFactory {
return new GeoWideRectangle(planetModel, topLat, bottomLat, leftLon, rightLon); return new GeoWideRectangle(planetModel, topLat, bottomLat, leftLon, rightLon);
} }
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) { if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) { if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
return new GeoDegeneratePoint(planetModel, topLat, 0.0); return new GeoDegeneratePoint(planetModel, topLat, 0.0);
} else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
return new GeoDegeneratePoint(planetModel, bottomLat, 0.0);
} }
//System.err.println(" horizontal line"); //System.err.println(" horizontal line");
return new GeoDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon); return new GeoDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);

View File

@ -0,0 +1,788 @@
/*
* 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.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* GeoShape representing a path across the surface of the globe,
* with a specified half-width. Path is described by a series of points.
* Distances are measured from the starting point along the path, and then at right
* angles to the path.
*
* @lucene.internal
*/
class GeoDegeneratePath extends GeoBasePath {
/** The original list of path points */
protected final List<GeoPoint> points = new ArrayList<GeoPoint>();
/** A list of SegmentEndpoints */
protected List<SegmentEndpoint> endPoints;
/** A list of PathSegments */
protected List<PathSegment> segments;
/** A point on the edge */
protected GeoPoint[] edgePoints;
/** Set to true if path has been completely constructed */
protected boolean isDone = false;
/** Constructor.
*@param planetModel is the planet model.
*@param pathPoints are the points in the path.
*/
public GeoDegeneratePath(final PlanetModel planetModel, final GeoPoint[] pathPoints) {
this(planetModel);
Collections.addAll(points, pathPoints);
done();
}
/** Piece-wise constructor. Use in conjunction with addPoint() and done().
*@param planetModel is the planet model.
*/
public GeoDegeneratePath(final PlanetModel planetModel) {
super(planetModel);
}
/** Add a point to the path.
*@param lat is the latitude of the point.
*@param lon is the longitude of the point.
*/
public void addPoint(final double lat, final double lon) {
if (isDone)
throw new IllegalStateException("Can't call addPoint() if done() already called");
points.add(new GeoPoint(planetModel, lat, lon));
}
/** Complete the path.
*/
public void done() {
if (isDone)
throw new IllegalStateException("Can't call done() twice");
if (points.size() == 0)
throw new IllegalArgumentException("Path must have at least one point");
isDone = true;
endPoints = new ArrayList<>(points.size());
segments = new ArrayList<>(points.size());
// First, build all segments. We'll then go back and build corresponding segment endpoints.
GeoPoint lastPoint = null;
for (final GeoPoint end : points) {
if (lastPoint != null) {
final Plane normalizedConnectingPlane = new Plane(lastPoint, end);
if (normalizedConnectingPlane == null) {
continue;
}
segments.add(new PathSegment(planetModel, lastPoint, end, normalizedConnectingPlane));
}
lastPoint = end;
}
if (segments.size() == 0) {
// Simple circle
final GeoPoint point = points.get(0);
final SegmentEndpoint onlyEndpoint = new SegmentEndpoint(point);
endPoints.add(onlyEndpoint);
this.edgePoints = new GeoPoint[]{point};
return;
}
// Create segment endpoints. Use an appropriate constructor for the start and end of the path.
for (int i = 0; i < segments.size(); i++) {
final PathSegment currentSegment = segments.get(i);
if (i == 0) {
// Starting endpoint
final SegmentEndpoint startEndpoint = new SegmentEndpoint(currentSegment.start,
currentSegment.startCutoffPlane);
endPoints.add(startEndpoint);
this.edgePoints = new GeoPoint[]{currentSegment.start};
continue;
}
endPoints.add(new SegmentEndpoint(currentSegment.start,
segments.get(i-1).endCutoffPlane,
currentSegment.startCutoffPlane));
}
// Do final endpoint
final PathSegment lastSegment = segments.get(segments.size()-1);
endPoints.add(new SegmentEndpoint(lastSegment.end,
lastSegment.endCutoffPlane));
}
/**
* Constructor for deserialization.
* @param planetModel is the planet model.
* @param inputStream is the input stream.
*/
public GeoDegeneratePath(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
this(planetModel,
SerializableObject.readPointArray(planetModel, inputStream));
}
@Override
public void write(final OutputStream outputStream) throws IOException {
SerializableObject.writePointArray(outputStream, points);
}
@Override
public double computePathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// Walk along path and keep track of the closest distance we find
double closestDistance = Double.POSITIVE_INFINITY;
// Segments first
for (PathSegment segment : segments) {
final double segmentDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentDistance < closestDistance) {
closestDistance = segmentDistance;
}
}
// Now, endpoints
for (SegmentEndpoint endpoint : endPoints) {
final double endpointDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointDistance < closestDistance) {
closestDistance = endpointDistance;
}
}
return closestDistance;
}
@Override
public double computeNearestDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double currentDistance = 0.0;
double minPathCenterDistance = Double.POSITIVE_INFINITY;
double bestDistance = Double.POSITIVE_INFINITY;
int segmentIndex = 0;
for (SegmentEndpoint endpoint : endPoints) {
final double endpointPathCenterDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointPathCenterDistance < minPathCenterDistance) {
// Use this endpoint
minPathCenterDistance = endpointPathCenterDistance;
bestDistance = currentDistance;
}
// Look at the following segment, if any
if (segmentIndex < segments.size()) {
final PathSegment segment = segments.get(segmentIndex++);
final double segmentPathCenterDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentPathCenterDistance < minPathCenterDistance) {
minPathCenterDistance = segmentPathCenterDistance;
bestDistance = distanceStyle.aggregateDistances(currentDistance, segment.nearestPathDistance(planetModel, distanceStyle, x, y, z));
}
currentDistance = distanceStyle.aggregateDistances(currentDistance, segment.fullPathDistance(distanceStyle));
}
}
return bestDistance;
}
@Override
protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// Algorithm:
// (1) If the point is within any of the segments along the path, return that value.
// (2) If the point is within any of the segment end circles along the path, return that value.
double currentDistance = 0.0;
for (PathSegment segment : segments) {
double distance = segment.pathDistance(planetModel, distanceStyle, x,y,z);
if (distance != Double.POSITIVE_INFINITY)
return distanceStyle.fromAggregationForm(distanceStyle.aggregateDistances(currentDistance, distance));
currentDistance = distanceStyle.aggregateDistances(currentDistance, segment.fullPathDistance(distanceStyle));
}
int segmentIndex = 0;
currentDistance = 0.0;
for (SegmentEndpoint endpoint : endPoints) {
double distance = endpoint.pathDistance(distanceStyle, x, y, z);
if (distance != Double.POSITIVE_INFINITY)
return distanceStyle.fromAggregationForm(distanceStyle.aggregateDistances(currentDistance, distance));
if (segmentIndex < segments.size())
currentDistance = distanceStyle.aggregateDistances(currentDistance, segments.get(segmentIndex++).fullPathDistance(distanceStyle));
}
return Double.POSITIVE_INFINITY;
}
@Override
protected void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
// TBD: Compute actual bounds based on distance
getBounds(bounds);
}
@Override
protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double minDistance = Double.POSITIVE_INFINITY;
for (final SegmentEndpoint endpoint : endPoints) {
final double newDistance = endpoint.outsideDistance(distanceStyle, x,y,z);
if (newDistance < minDistance)
minDistance = newDistance;
}
for (final PathSegment segment : segments) {
final double newDistance = segment.outsideDistance(planetModel, distanceStyle, x, y, z);
if (newDistance < minDistance)
minDistance = newDistance;
}
return minDistance;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (SegmentEndpoint pathPoint : endPoints) {
if (pathPoint.isWithin(x, y, z)) {
return true;
}
}
for (PathSegment pathSegment : segments) {
if (pathSegment.isWithin(x, y, z)) {
return true;
}
}
return false;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
// We look for an intersection with any of the exterior edges of the path.
// We also have to look for intersections with the cones described by the endpoints.
// Return "true" if any such intersections are found.
// For plane intersections, the basic idea is to come up with an equation of the line that is
// the intersection (if any). Then, find the intersections with the unit sphere (if any). If
// any of the intersection points are within the bounds, then we've detected an intersection.
// Well, sort of. We can detect intersections also due to overlap of segments with each other.
// But that's an edge case and we won't be optimizing for it.
//System.err.println(" Looking for intersection of plane "+plane+" with path "+this);
// Since the endpoints are included in the path segments, we only need to do this if there are
// no path segments
if (endPoints.size() == 1) {
return endPoints.get(0).intersects(planetModel, plane, notablePoints, bounds);
}
for (final PathSegment pathSegment : segments) {
if (pathSegment.intersects(planetModel, plane, notablePoints, bounds)) {
return true;
}
}
return false;
}
@Override
public boolean intersects(GeoShape geoShape) {
// Since the endpoints are included in the path segments, we only need to do this if there are
// no path segments
if (endPoints.size() == 1) {
return endPoints.get(0).intersects(geoShape);
}
for (final PathSegment pathSegment : segments) {
if (pathSegment.intersects(geoShape)) {
return true;
}
}
return false;
}
@Override
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
// For building bounds, order matters. We want to traverse
// never more than 180 degrees longitude at a pop or we risk having the
// bounds object get itself inverted. So do the edges first.
for (PathSegment pathSegment : segments) {
pathSegment.getBounds(planetModel, bounds);
}
if (endPoints.size() == 1) {
endPoints.get(0).getBounds(planetModel, bounds);
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegeneratePath))
return false;
GeoDegeneratePath p = (GeoDegeneratePath) o;
if (!super.equals(p))
return false;
return points.equals(p.points);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + points.hashCode();
return result;
}
@Override
public String toString() {
return "GeoDegeneratePath: {planetmodel=" + planetModel+", points={" + points + "}}";
}
/**
* This is precalculated data for segment endpoint. Since the path is degenerate, there are several different cases:
* (1) The path consists of a single endpoint. In this case, the degenerate path consists of this one point.
* (2) This is the end of a path. There is a bounding plane passed in which describes the part of the world that is considered
* to belong to this endpoint.
* (3) Intersection. There are two cutoff planes, one for each end of the intersection.
*/
private static class SegmentEndpoint {
/** The center point of the endpoint */
public final GeoPoint point;
/** Pertinent cutoff planes from adjoining segments */
public final Membership[] cutoffPlanes;
/** Notable points for this segment endpoint */
public final GeoPoint[] notablePoints;
/** No notable points from the circle itself */
public final static GeoPoint[] circlePoints = new GeoPoint[0];
/** Null membership */
public final static Membership[] NO_MEMBERSHIP = new Membership[0];
/** Constructor for case (1).
*@param point is the center point.
*/
public SegmentEndpoint(final GeoPoint point) {
this.point = point;
this.cutoffPlanes = NO_MEMBERSHIP;
this.notablePoints = circlePoints;
}
/** Constructor for case (2).
* Generate an endpoint, given a single cutoff plane plus upper and lower edge points.
*@param point is the center point.
*@param cutoffPlane is the plane from the adjoining path segment marking the boundary between this endpoint and that segment.
*/
public SegmentEndpoint(final GeoPoint point, final SidedPlane cutoffPlane) {
this.point = point;
this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane)};
this.notablePoints = new GeoPoint[]{point};
}
/** Constructor for case (3).
* Generate an endpoint, given two cutoff planes.
*@param point is the center.
*@param cutoffPlane1 is one adjoining path segment cutoff plane.
*@param cutoffPlane2 is another adjoining path segment cutoff plane.
*/
public SegmentEndpoint(final GeoPoint point,
final SidedPlane cutoffPlane1, final SidedPlane cutoffPlane2) {
this.point = point;
this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane1), new SidedPlane(cutoffPlane2)};
this.notablePoints = new GeoPoint[]{point};
}
/** Check if point is within this endpoint.
*@param point is the point.
*@return true of within.
*/
public boolean isWithin(final Vector point) {
return this.point.isIdentical(point.x, point.y, point.z);
}
/** Check if point is within this endpoint.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return true of within.
*/
public boolean isWithin(final double x, final double y, final double z) {
return this.point.isIdentical(x, y, z);
}
/** Compute interior path distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, in aggregation form.
*/
public double pathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!isWithin(x,y,z))
return Double.POSITIVE_INFINITY;
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(this.point, x, y, z));
}
/** Compute nearest path distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric (always value zero), in aggregation form, or POSITIVE_INFINITY
* if the point is not within the bounds of the endpoint.
*/
public double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return distanceStyle.toAggregationForm(0.0);
}
/** Compute path center distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, or POSITIVE_INFINITY
* if the point is not within the bounds of the endpoint.
*/
public double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return distanceStyle.computeDistance(this.point, x, y, z);
}
/** Compute external distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric.
*/
public double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
return distanceStyle.computeDistance(this.point, x, y, z);
}
/** Determine if this endpoint intersects a specified plane.
*@param planetModel is the planet model.
*@param p is the plane.
*@param notablePoints are the points associated with the plane.
*@param bounds are any bounds which the intersection must lie within.
*@return true if there is a matching intersection.
*/
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
// If not on the plane, no intersection
if (!p.evaluateIsZero(point))
return false;
for (Membership m : bounds) {
if (!m.isWithin(point))
return false;
}
return true;
}
/** Determine if this endpoint intersects a GeoShape.
*@param geoShape is the GeoShape.
*@return true if there is shape intersect this endpoint.
*/
public boolean intersects(final GeoShape geoShape) {
return geoShape.isWithin(point);
}
/** Get the bounds for a segment endpoint.
*@param planetModel is the planet model.
*@param bounds are the bounds to be modified.
*/
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
bounds.addPoint(point);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SegmentEndpoint))
return false;
SegmentEndpoint other = (SegmentEndpoint) o;
return point.equals(other.point);
}
@Override
public int hashCode() {
return point.hashCode();
}
@Override
public String toString() {
return point.toString();
}
}
/**
* This is the pre-calculated data for a path segment.
*/
private static class PathSegment {
/** Starting point of the segment */
public final GeoPoint start;
/** End point of the segment */
public final GeoPoint end;
/** Place to keep any complete segment distances we've calculated so far */
public final Map<DistanceStyle,Double> fullDistanceCache = new HashMap<DistanceStyle,Double>();
/** Normalized plane connecting the two points and going through world center */
public final Plane normalizedConnectingPlane;
/** Plane going through the center and start point, marking the start edge of the segment */
public final SidedPlane startCutoffPlane;
/** Plane going through the center and end point, marking the end edge of the segment */
public final SidedPlane endCutoffPlane;
/** Notable points for the connecting plane */
public final GeoPoint[] connectingPlanePoints;
/** Construct a path segment.
*@param planetModel is the planet model.
*@param start is the starting point.
*@param end is the ending point.
*@param normalizedConnectingPlane is the connecting plane.
*/
public PathSegment(final PlanetModel planetModel, final GeoPoint start, final GeoPoint end,
final Plane normalizedConnectingPlane) {
this.start = start;
this.end = end;
this.normalizedConnectingPlane = normalizedConnectingPlane;
// Cutoff planes use opposite endpoints as correct side examples
startCutoffPlane = new SidedPlane(end, normalizedConnectingPlane, start);
endCutoffPlane = new SidedPlane(start, normalizedConnectingPlane, end);
connectingPlanePoints = new GeoPoint[]{start, end};
}
/** Compute the full distance along this path segment.
*@param distanceStyle is the distance style.
*@return the distance metric, in aggregation form.
*/
public double fullPathDistance(final DistanceStyle distanceStyle) {
synchronized (fullDistanceCache) {
Double dist = fullDistanceCache.get(distanceStyle);
if (dist == null) {
dist = new Double(distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, end.x, end.y, end.z)));
fullDistanceCache.put(distanceStyle, dist);
}
return dist.doubleValue();
}
}
/** Check if point is within this segment.
*@param point is the point.
*@return true of within.
*/
public boolean isWithin(final Vector point) {
return startCutoffPlane.isWithin(point) &&
endCutoffPlane.isWithin(point) &&
normalizedConnectingPlane.evaluateIsZero(point);
}
/** Check if point is within this segment.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return true of within.
*/
public boolean isWithin(final double x, final double y, final double z) {
return startCutoffPlane.isWithin(x, y, z) &&
endCutoffPlane.isWithin(x, y, z) &&
normalizedConnectingPlane.evaluateIsZero(x, y, z);
}
/** Compute path center distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, or Double.POSITIVE_INFINITY if outside this segment
*/
public double pathCenterDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// First, if this point is outside the endplanes of the segment, return POSITIVE_INFINITY.
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
// (1) Compute normalizedPerpPlane. If degenerate, then there is no such plane, which means that the point given
// is insufficient to distinguish between a family of such planes. This can happen only if the point is one of the
// "poles", imagining the normalized plane to be the "equator". In that case, the distance returned should be zero.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
return distanceStyle.computeDistance(start, x, y, z);
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
GeoPoint thePoint;
if (intersectionPoints.length == 0)
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
else if (intersectionPoints.length == 1)
thePoint = intersectionPoints[0];
else {
if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
thePoint = intersectionPoints[0];
else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
thePoint = intersectionPoints[1];
else
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
}
return distanceStyle.computeDistance(thePoint, x, y, z);
}
/** Compute nearest path distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, in aggregation form, or Double.POSITIVE_INFINITY if outside this segment
*/
public double nearestPathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// First, if this point is outside the endplanes of the segment, return POSITIVE_INFINITY.
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
// (1) Compute normalizedPerpPlane. If degenerate, then there is no such plane, which means that the point given
// is insufficient to distinguish between a family of such planes. This can happen only if the point is one of the
// "poles", imagining the normalized plane to be the "equator". In that case, the distance returned should be zero.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
return distanceStyle.toAggregationForm(0.0);
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
GeoPoint thePoint;
if (intersectionPoints.length == 0)
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
else if (intersectionPoints.length == 1)
thePoint = intersectionPoints[0];
else {
if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
thePoint = intersectionPoints[0];
else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
thePoint = intersectionPoints[1];
else
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
}
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z));
}
/** Compute interior path distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, in aggregation form.
*/
public double pathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!isWithin(x,y,z))
return Double.POSITIVE_INFINITY;
// (1) Compute normalizedPerpPlane. If degenerate, then return point distance from start to point.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, x,y,z));
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
// Old computation: too expensive, because it calculates the intersection point twice.
//return distanceStyle.computeDistance(planetModel, normalizedConnectingPlane, x, y, z, startCutoffPlane, endCutoffPlane) +
// distanceStyle.computeDistance(planetModel, normalizedPerpPlane, start.x, start.y, start.z, upperConnectingPlane, lowerConnectingPlane);
final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
GeoPoint thePoint;
if (intersectionPoints.length == 0)
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
else if (intersectionPoints.length == 1)
thePoint = intersectionPoints[0];
else {
if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
thePoint = intersectionPoints[0];
else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
thePoint = intersectionPoints[1];
else
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
}
return distanceStyle.aggregateDistances(distanceStyle.toAggregationForm(distanceStyle.computeDistance(thePoint, x, y, z)),
distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z)));
}
/** Compute external distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric.
*/
public double outsideDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
final double distance = distanceStyle.computeDistance(planetModel, normalizedConnectingPlane, x,y,z, startCutoffPlane, endCutoffPlane);
final double startDistance = distanceStyle.computeDistance(start, x,y,z);
final double endDistance = distanceStyle.computeDistance(end, x,y,z);
return Math.min(
Math.min(startDistance, endDistance),
distance);
}
/** Determine if this endpoint intersects a specified plane.
*@param planetModel is the planet model.
*@param p is the plane.
*@param notablePoints are the points associated with the plane.
*@param bounds are any bounds which the intersection must lie within.
*@return true if there is a matching intersection.
*/
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return normalizedConnectingPlane.intersects(planetModel, p, connectingPlanePoints, notablePoints, bounds, startCutoffPlane, endCutoffPlane);
}
/** Determine if this endpoint intersects a specified GeoShape.
*@param geoShape is the GeoShape.
*@return true if there GeoShape intersects this endpoint.
*/
public boolean intersects(final GeoShape geoShape) {
return geoShape.intersects(normalizedConnectingPlane, connectingPlanePoints, startCutoffPlane, endCutoffPlane);
}
/** Get the bounds for a segment endpoint.
*@param planetModel is the planet model.
*@param bounds are the bounds to be modified.
*/
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
// We need to do all bounding planes as well as corner points
bounds.addPoint(start).addPoint(end)
.addPlane(planetModel, normalizedConnectingPlane, startCutoffPlane, endCutoffPlane);
}
}
}

View File

@ -125,7 +125,7 @@ class GeoDegeneratePoint extends GeoPoint implements GeoPointShape {
@Override @Override
public boolean isWithin(final double x, final double y, final double z) { public boolean isWithin(final double x, final double y, final double z) {
return x == this.x && y == this.y && z == this.z; return this.isIdentical(x, y, z);
} }
@Override @Override

View File

@ -22,4 +22,62 @@ package org.apache.lucene.spatial3d.geom;
* @lucene.experimental * @lucene.experimental
*/ */
public interface GeoPath extends GeoDistanceShape { public interface GeoPath extends GeoDistanceShape {
// The following methods compute distances along the path from the shape to a point
// that doesn't need to be inside the shape. The perpendicular distance from the path
// itself to the point is not included in the calculation.
/**
* Compute the nearest path distance to the GeoPoint.
* The path distance will not include the distance from the path itself to the
* point, but just the distance along the path to the nearest point on the path.
*
* @param distanceStyle is the distance style.
* @param point is the point to compute the distance to.
* @return the distance to the nearest path point.
*/
public default double computeNearestDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
return computeNearestDistance(distanceStyle, point.x, point.y, point.z);
}
/**
* Compute the nearest path distance to the GeoPoint.
* The path distance will not include the distance from the path itself to the
* point, but just the distance along the path to the nearest point on the path.
*
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the distance to the nearest path point.
*/
public double computeNearestDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
// The following methods compute the best distance from the path center to the point.
/**
* Compute the shortest distance from the path center to the GeoPoint.
* The distance is meant to allow comparisons between different
* paths to find the one that goes closest to a point.
*
* @param distanceStyle is the distance style.
* @param point is the point to compute the distance to.
* @return the shortest distance from the path center to the point.
*/
public default double computePathCenterDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
return computePathCenterDistance(distanceStyle, point.x, point.y, point.z);
}
/**
* Compute the shortest distance from the path center to the GeoPoint.
* The distance is meant to allow comparisons between different
* paths to find the one that goes closest to a point.
*
* @param distanceStyle is the distance style.
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the shortest distance from the path center to the point.
*/
public double computePathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
} }

View File

@ -33,6 +33,9 @@ public class GeoPathFactory {
* @return a GeoPath corresponding to what was specified. * @return a GeoPath corresponding to what was specified.
*/ */
public static GeoPath makeGeoPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) { public static GeoPath makeGeoPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) {
if (maxCutoffAngle < Vector.MINIMUM_ANGULAR_RESOLUTION) {
return new GeoDegeneratePath(planetModel, pathPoints);
}
return new GeoStandardPath(planetModel, maxCutoffAngle, pathPoints); return new GeoStandardPath(planetModel, maxCutoffAngle, pathPoints);
} }

View File

@ -214,6 +214,55 @@ class GeoStandardPath extends GeoBasePath {
SerializableObject.writePointArray(outputStream, points); SerializableObject.writePointArray(outputStream, points);
} }
@Override
public double computePathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// Walk along path and keep track of the closest distance we find
double closestDistance = Double.POSITIVE_INFINITY;
// Segments first
for (PathSegment segment : segments) {
final double segmentDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentDistance < closestDistance) {
closestDistance = segmentDistance;
}
}
// Now, endpoints
for (SegmentEndpoint endpoint : endPoints) {
final double endpointDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointDistance < closestDistance) {
closestDistance = endpointDistance;
}
}
return closestDistance;
}
@Override
public double computeNearestDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double currentDistance = 0.0;
double minPathCenterDistance = Double.POSITIVE_INFINITY;
double bestDistance = Double.POSITIVE_INFINITY;
int segmentIndex = 0;
for (SegmentEndpoint endpoint : endPoints) {
final double endpointPathCenterDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointPathCenterDistance < minPathCenterDistance) {
// Use this endpoint
minPathCenterDistance = endpointPathCenterDistance;
bestDistance = currentDistance;
}
// Look at the following segment, if any
if (segmentIndex < segments.size()) {
final PathSegment segment = segments.get(segmentIndex++);
final double segmentPathCenterDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentPathCenterDistance < minPathCenterDistance) {
minPathCenterDistance = segmentPathCenterDistance;
bestDistance = distanceStyle.aggregateDistances(currentDistance, segment.nearestPathDistance(planetModel, distanceStyle, x, y, z));
}
currentDistance = distanceStyle.aggregateDistances(currentDistance, segment.fullPathDistance(distanceStyle));
}
}
return bestDistance;
}
@Override @Override
protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) { protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// Algorithm: // Algorithm:
@ -562,6 +611,40 @@ class GeoStandardPath extends GeoBasePath {
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(this.point, x, y, z)); return distanceStyle.toAggregationForm(distanceStyle.computeDistance(this.point, x, y, z));
} }
/** Compute nearest path distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric (always value zero), in aggregation form, or POSITIVE_INFINITY
* if the point is not within the bounds of the endpoint.
*/
public double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return distanceStyle.toAggregationForm(0.0);
}
/** Compute path center distance.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, or POSITIVE_INFINITY
* if the point is not within the bounds of the endpoint.
*/
public double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return distanceStyle.computeDistance(this.point, x, y, z);
}
/** Compute external distance. /** Compute external distance.
*@param distanceStyle is the distance style. *@param distanceStyle is the distance style.
*@param x is the point x. *@param x is the point x.
@ -766,6 +849,93 @@ class GeoStandardPath extends GeoBasePath {
lowerConnectingPlane.isWithin(x, y, z); lowerConnectingPlane.isWithin(x, y, z);
} }
/** Compute path center distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, or Double.POSITIVE_INFINITY if outside this segment
*/
public double pathCenterDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// First, if this point is outside the endplanes of the segment, return POSITIVE_INFINITY.
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
// (1) Compute normalizedPerpPlane. If degenerate, then there is no such plane, which means that the point given
// is insufficient to distinguish between a family of such planes. This can happen only if the point is one of the
// "poles", imagining the normalized plane to be the "equator". In that case, the distance returned should be zero.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
return distanceStyle.computeDistance(start, x, y, z);
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
GeoPoint thePoint;
if (intersectionPoints.length == 0)
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
else if (intersectionPoints.length == 1)
thePoint = intersectionPoints[0];
else {
if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
thePoint = intersectionPoints[0];
else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
thePoint = intersectionPoints[1];
else
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
}
return distanceStyle.computeDistance(thePoint, x, y, z);
}
/** Compute nearest path distance.
*@param planetModel is the planet model.
*@param distanceStyle is the distance style.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
*@return the distance metric, in aggregation form, or Double.POSITIVE_INFINITY if outside this segment
*/
public double nearestPathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
// First, if this point is outside the endplanes of the segment, return POSITIVE_INFINITY.
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
// (1) Compute normalizedPerpPlane. If degenerate, then there is no such plane, which means that the point given
// is insufficient to distinguish between a family of such planes. This can happen only if the point is one of the
// "poles", imagining the normalized plane to be the "equator". In that case, the distance returned should be zero.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
return distanceStyle.toAggregationForm(0.0);
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
GeoPoint thePoint;
if (intersectionPoints.length == 0)
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
else if (intersectionPoints.length == 1)
thePoint = intersectionPoints[0];
else {
if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
thePoint = intersectionPoints[0];
else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
thePoint = intersectionPoints[1];
else
throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
}
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z));
}
/** Compute interior path distance. /** Compute interior path distance.
*@param planetModel is the planet model. *@param planetModel is the planet model.
*@param distanceStyle is the distance style. *@param distanceStyle is the distance style.

View File

@ -73,6 +73,7 @@ class StandardObjects {
classRegsitry.put(XYdZSolid.class, 33); classRegsitry.put(XYdZSolid.class, 33);
classRegsitry.put(StandardXYZSolid.class, 34); classRegsitry.put(StandardXYZSolid.class, 34);
classRegsitry.put(PlanetModel.class, 35); classRegsitry.put(PlanetModel.class, 35);
classRegsitry.put(GeoDegeneratePath.class, 36);
for (Class<?> clazz : classRegsitry.keySet()){ for (Class<?> clazz : classRegsitry.keySet()){
codeRegsitry.put(classRegsitry.get(clazz), clazz); codeRegsitry.put(classRegsitry.get(clazz), clazz);

View File

@ -56,8 +56,10 @@ public class GeoPathTest {
p.done(); p.done();
gp = new GeoPoint(PlanetModel.SPHERE, 0.05, 0.15); gp = new GeoPoint(PlanetModel.SPHERE, 0.05, 0.15);
assertEquals(0.15 + 0.05, p.computeDistance(DistanceStyle.ARC,gp), 0.000001); assertEquals(0.15 + 0.05, p.computeDistance(DistanceStyle.ARC,gp), 0.000001);
assertEquals(0.15, p.computeNearestDistance(DistanceStyle.ARC,gp), 0.000001);
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, 0.12); gp = new GeoPoint(PlanetModel.SPHERE, 0.0, 0.12);
assertEquals(0.12, p.computeDistance(DistanceStyle.ARC,gp), 0.000001); assertEquals(0.12, p.computeDistance(DistanceStyle.ARC,gp), 0.000001);
assertEquals(0.12, p.computeNearestDistance(DistanceStyle.ARC,gp), 0.000001);
// Now try a vertical path, and make sure distances are as expected // Now try a vertical path, and make sure distances are as expected
p = new GeoStandardPath(PlanetModel.SPHERE, 0.1); p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
@ -302,4 +304,77 @@ public class GeoPathTest {
assertTrue(solid.isWithin(point)); assertTrue(solid.isWithin(point));
} }
@Test
public void testInterpolation() {
final double lat = 52.51607;
final double lon = 13.37698;
final double[] pathLats = new double[] {52.5355,52.54,52.5626,52.5665,52.6007,52.6135,52.6303,52.6651,52.7074};
final double[] pathLons = new double[] {13.3634,13.3704,13.3307,13.3076,13.2806,13.2484,13.2406,13.241,13.1926};
// Set up a point in the right way
final GeoPoint carPoint = new GeoPoint(PlanetModel.SPHERE, toRadians(lat), toRadians(lon));
// Create the path, but use a tiny width (e.g. zero)
final GeoPoint[] pathPoints = new GeoPoint[pathLats.length];
for (int i = 0; i < pathPoints.length; i++) {
pathPoints[i] = new GeoPoint(PlanetModel.SPHERE, toRadians(pathLats[i]), toRadians(pathLons[i]));
}
// Construct a path with no width
final GeoPath thisPath = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 0.0, pathPoints);
// Construct a path with a width
final GeoPath legacyPath = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 1e-6, pathPoints);
// Compute the inside distance to the atPoint using zero-width path
final double distance = thisPath.computeNearestDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using legacy path
final double legacyDistance = legacyPath.computeNearestDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using the legacy formula
final double oldFormulaDistance = thisPath.computeDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using the legacy formula with the legacy shape
final double oldFormulaLegacyDistance = legacyPath.computeDistance(DistanceStyle.ARC, carPoint);
// These should be about the same
assertEquals(legacyDistance, distance, 1e-12);
assertEquals(oldFormulaLegacyDistance, oldFormulaDistance, 1e-12);
// This isn't true because example search center is off of the path.
//assertEquals(oldFormulaDistance, distance, 1e-12);
}
@Test
public void testInterpolation2() {
final double lat = 52.5665;
final double lon = 13.3076;
final double[] pathLats = new double[] {52.5355,52.54,52.5626,52.5665,52.6007,52.6135,52.6303,52.6651,52.7074};
final double[] pathLons = new double[] {13.3634,13.3704,13.3307,13.3076,13.2806,13.2484,13.2406,13.241,13.1926};
final GeoPoint carPoint = new GeoPoint(PlanetModel.SPHERE, toRadians(lat), toRadians(lon));
final GeoPoint[] pathPoints = new GeoPoint[pathLats.length];
for (int i = 0; i < pathPoints.length; i++) {
pathPoints[i] = new GeoPoint(PlanetModel.SPHERE, toRadians(pathLats[i]), toRadians(pathLons[i]));
}
// Construct a path with no width
final GeoPath thisPath = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 0.0, pathPoints);
// Construct a path with a width
final GeoPath legacyPath = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 1e-6, pathPoints);
// Compute the inside distance to the atPoint using zero-width path
final double distance = thisPath.computeNearestDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using legacy path
final double legacyDistance = legacyPath.computeNearestDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using the legacy formula
final double oldFormulaDistance = thisPath.computeDistance(DistanceStyle.ARC, carPoint);
// Compute the inside distance using the legacy formula with the legacy shape
final double oldFormulaLegacyDistance = legacyPath.computeDistance(DistanceStyle.ARC, carPoint);
// These should be about the same
assertEquals(legacyDistance, distance, 1e-12);
assertEquals(oldFormulaLegacyDistance, oldFormulaDistance, 1e-12);
// Since the point we picked is actually on the path, this should also be true
assertEquals(oldFormulaDistance, distance, 1e-12);
}
} }

View File

@ -27,18 +27,18 @@ import org.junit.Test;
/** /**
* Test to check Serialization * Test to check Serialization
*/ */
public class RandomBinaryCodecTest extends RandomGeoShapeGenerator{ public class RandomBinaryCodecTest extends RandomGeo3dShapeGenerator {
@Test @Test
@Repeat(iterations = 10) @Repeat(iterations = 10)
public void testRandomPointCodec() throws IOException{ public void testRandomPointCodec() throws IOException{
PlanetModel planetModel = randomPlanetModel(); PlanetModel planetModel = randomPlanetModel();
GeoPoint shape = randomGeoPoint(planetModel, getEmptyConstraint()); GeoPoint shape = randomGeoPoint(planetModel);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
SerializableObject.writeObject(outputStream, shape); SerializableObject.writeObject(outputStream, shape);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
SerializableObject shapeCopy = SerializableObject.readObject(planetModel, inputStream); SerializableObject shapeCopy = SerializableObject.readObject(planetModel, inputStream);
assertEquals(shape, shapeCopy); assertEquals(shape.toString(), shape, shapeCopy);
} }
@Test @Test
@ -51,7 +51,7 @@ public class RandomBinaryCodecTest extends RandomGeoShapeGenerator{
SerializableObject.writePlanetObject(outputStream, shape); SerializableObject.writePlanetObject(outputStream, shape);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
SerializableObject shapeCopy = SerializableObject.readPlanetObject(inputStream); SerializableObject shapeCopy = SerializableObject.readPlanetObject(inputStream);
assertEquals(shape, shapeCopy); assertEquals(shape.toString(), shape, shapeCopy);
} }
@Test @Test
@ -64,6 +64,6 @@ public class RandomBinaryCodecTest extends RandomGeoShapeGenerator{
SerializableObject.writeObject(outputStream, shape); SerializableObject.writeObject(outputStream, shape);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
SerializableObject shapeCopy = SerializableObject.readObject(planetModel, inputStream); SerializableObject shapeCopy = SerializableObject.readObject(planetModel, inputStream);
assertEquals(shape, shapeCopy); assertEquals(shape.toString(), shape, shapeCopy);
} }
} }

View File

@ -36,7 +36,7 @@ import static com.carrotsearch.randomizedtesting.RandomizedTest.randomDouble;
* created using GeoConvexPolygons and GeoConcavePolygons. * created using GeoConvexPolygons and GeoConcavePolygons.
* *
*/ */
public class RandomGeoShapeGenerator extends LuceneTestCase { public class RandomGeo3dShapeGenerator extends LuceneTestCase {
/* Max num of iterations to find right shape under given constrains */ /* Max num of iterations to find right shape under given constrains */
final private static int MAX_SHAPE_ITERATIONS = 50; final private static int MAX_SHAPE_ITERATIONS = 50;
@ -53,19 +53,31 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
final protected static int RECTANGLE = 6; final protected static int RECTANGLE = 6;
final protected static int PATH = 7; final protected static int PATH = 7;
final protected static int COLLECTION = 8; final protected static int COLLECTION = 8;
final protected static int POINT = 9;
final protected static int LINE = 10;
/* Helper shapes for generating constraints whch are just three sided polygons */ /* Helper shapes for generating constraints whch are just three sided polygons */
final protected static int CONVEX_SIMPLE_POLYGON = 500; final protected static int CONVEX_SIMPLE_POLYGON = 500;
final protected static int CONCAVE_SIMPLE_POLYGON = 501; final protected static int CONCAVE_SIMPLE_POLYGON = 501;
/** /**
* Method that returns empty Constraints object.. * Method that returns a random generated Planet model from the supported
* Planet models. currently SPHERE and WGS84
* *
* @return an empty Constraints object * @return a random generated Planet model
*/ */
public Constraints getEmptyConstraint(){ public PlanetModel randomPlanetModel() {
return new Constraints(); final int shapeType = random().nextInt(2);
switch (shapeType) {
case 0: {
return PlanetModel.SPHERE;
}
case 1: {
return PlanetModel.WGS84;
}
default:
throw new IllegalStateException("Unexpected planet model");
}
} }
/** /**
@ -75,7 +87,20 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
* @return a random generated shape code * @return a random generated shape code
*/ */
public int randomShapeType(){ public int randomShapeType(){
return random().nextInt(9); return random().nextInt(11);
}
/**
* Method that returns a random generated GeoAreaShape code from all
* supported GeoAreaShapes.
*
* We are removing Collections because it is difficult to create shapes
* with properties in some cases.
*
* @return a random generated polygon code
*/
public int randomGeoAreaShapeType(){
return random().nextInt(11);
} }
/** /**
@ -106,19 +131,6 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
return shapeType; return shapeType;
} }
/**
* Method that returns a random generated GeoAreaShape code from all
* supported GeoAreaShapes.
*
* We are removing Collections because it is difficult to create shapes
* with properties in some cases.
*
* @return a random generated polygon code
*/
public int randomGeoAreaShapeType(){
return random().nextInt(8);
}
/** /**
* Check if a shape code represents a concave shape * Check if a shape code represents a concave shape
* *
@ -129,23 +141,26 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
} }
/** /**
* Method that returns a random generated Planet model from the supported * Method that returns empty Constraints object..
* Planet models. currently SPHERE and WGS84
* *
* @return a random generated Planet model * @return an empty Constraints object
*/ */
public PlanetModel randomPlanetModel() { public Constraints getEmptyConstraint(){
final int shapeType = random().nextInt(2); return new Constraints();
switch (shapeType) {
case 0: {
return PlanetModel.SPHERE;
} }
case 1: {
return PlanetModel.WGS84; /**
} * Method that returns a random generated GeoPoint.
default: *
throw new IllegalStateException("Unexpected planet model"); * @param planetModel The planet model.
* @return The random generated GeoPoint.
*/
public GeoPoint randomGeoPoint(PlanetModel planetModel) {
GeoPoint point = null;
while (point == null) {
point = randomGeoPoint(planetModel, getEmptyConstraint());
} }
return point;
} }
/** /**
@ -159,13 +174,13 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
public GeoPoint randomGeoPoint(PlanetModel planetModel, Constraints constraints) { public GeoPoint randomGeoPoint(PlanetModel planetModel, Constraints constraints) {
int iterations = 0; int iterations = 0;
while (iterations < MAX_POINT_ITERATIONS) { while (iterations < MAX_POINT_ITERATIONS) {
double lat = randomDouble(); double lat = randomDouble() * Math.PI/2;
if (Math.PI/2 - Math.abs(lat) <0){ if (random().nextBoolean()) {
continue; lat = (-1)*lat;
} }
double lon = randomDouble(); double lon = randomDouble() * Math.PI;
if (Math.PI - Math.abs(lat) <0){ if (random().nextBoolean()) {
continue; lon = (-1)*lon;
} }
iterations++; iterations++;
GeoPoint point = new GeoPoint(planetModel, lat, lon); GeoPoint point = new GeoPoint(planetModel, lat, lon);
@ -257,6 +272,12 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
case COLLECTION: { case COLLECTION: {
return collection(planetModel, constraints); return collection(planetModel, constraints);
} }
case POINT: {
return point(planetModel, constraints);
}
case LINE: {
return line(planetModel, constraints);
}
case CONVEX_SIMPLE_POLYGON: { case CONVEX_SIMPLE_POLYGON: {
return simpleConvexPolygon(planetModel, constraints); return simpleConvexPolygon(planetModel, constraints);
} }
@ -268,6 +289,36 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
} }
} }
/**
* Method that returns a random generated a GeoPointShape under given constraints. Returns
* NULL if it cannot build the GeoCircle under the given constraints.
*
* @param planetModel The planet model.
* @param constraints The given constraints.
* @return The random generated GeoPointShape.
*/
private GeoPointShape point(PlanetModel planetModel , Constraints constraints) {
int iterations=0;
while (iterations < MAX_SHAPE_ITERATIONS) {
iterations++;
final GeoPoint point = randomGeoPoint(planetModel, constraints);
if (point == null){
continue;
}
try {
GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, point.getLatitude(), point.getLongitude());
if (!constraints.valid(pointShape)) {
continue;
}
return pointShape;
} catch (IllegalArgumentException e) {
continue;
}
}
return null;
}
/** /**
* Method that returns a random generated a GeoCircle under given constraints. Returns * Method that returns a random generated a GeoCircle under given constraints. Returns
* NULL if it cannot build the GeoCircle under the given constraints. * NULL if it cannot build the GeoCircle under the given constraints.
@ -339,6 +390,33 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
return null; return null;
} }
/**
* Method that returns a random generated degenerate GeoPath under given constraints. Returns
* NULL if it cannot build the degenerate GeoPath under the given constraints.
*
* @param planetModel The planet model.
* @param constraints The given constraints.
* @return The random generated degenerated GeoPath.
*/
private GeoPath line(PlanetModel planetModel, Constraints constraints) {
int iterations = 0;
while (iterations < MAX_SHAPE_ITERATIONS) {
iterations++;
int vertexCount = random().nextInt(2) + 2;
List<GeoPoint> geoPoints = points(vertexCount, planetModel, constraints);
try {
GeoPath path = GeoPathFactory.makeGeoPath(planetModel, 0, geoPoints.toArray(new GeoPoint[geoPoints.size()]));
if (!constraints.valid(path)) {
continue;
}
return path;
} catch (IllegalArgumentException e) {
continue;
}
}
return null;
}
/** /**
* Method that returns a random generated a GeoPath under given constraints. Returns * Method that returns a random generated a GeoPath under given constraints. Returns
* NULL if it cannot build the GeoPath under the given constraints. * NULL if it cannot build the GeoPath under the given constraints.
@ -732,13 +810,7 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
* @return the cutoff angle. * @return the cutoff angle.
*/ */
private double randomCutoffAngle() { private double randomCutoffAngle() {
while(true) { return randomDouble() * Math.PI;
double radius = randomDouble();
if (radius <0 || radius > Math.PI){
continue;
}
return radius;
}
} }
/** /**
@ -939,6 +1011,3 @@ public class RandomGeoShapeGenerator extends LuceneTestCase {
} }
} }
} }

View File

@ -23,8 +23,7 @@ import org.junit.Test;
/** /**
* Random test to check relationship between GeoAreaShapes and GeoShapes. * Random test to check relationship between GeoAreaShapes and GeoShapes.
*/ */
public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator { public class RandomGeoShapeRelationshipTest extends RandomGeo3dShapeGenerator {
/** /**
* Test for WITHIN points. We build a WITHIN shape with respect the geoAreaShape * Test for WITHIN points. We build a WITHIN shape with respect the geoAreaShape
@ -38,6 +37,9 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
int referenceShapeType = CONVEX_POLYGON; int referenceShapeType = CONVEX_POLYGON;
PlanetModel planetModel = randomPlanetModel(); PlanetModel planetModel = randomPlanetModel();
int shapeType = randomShapeType(); int shapeType = randomShapeType();
while (shapeType == POINT || shapeType == LINE) {
shapeType = randomShapeType();
}
GeoAreaShape shape = null; GeoAreaShape shape = null;
GeoPoint point = null; GeoPoint point = null;
while (point == null) { while (point == null) {
@ -51,7 +53,10 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
point = randomGeoPoint(planetModel, constraints); point = randomGeoPoint(planetModel, constraints);
} }
} }
assertTrue(shape.isWithin(point)); StringBuilder b = new StringBuilder();
b.append("shape: " + shape + "\n");
b.append("point: " + point);
assertTrue(b.toString(), shape.isWithin(point));
} }
/** /**
@ -78,7 +83,10 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
point = randomGeoPoint(planetModel, constraints); point = randomGeoPoint(planetModel, constraints);
} }
} }
assertFalse(shape.isWithin(point)); StringBuilder b = new StringBuilder();
b.append("shape: " + shape + "\n");
b.append("point: " + point);
assertFalse(b.toString(), shape.isWithin(point));
} }
/** /**
@ -109,11 +117,14 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
shape = randomGeoShape(shapeType, planetModel, constraints); shape = randomGeoShape(shapeType, planetModel, constraints);
} }
} }
StringBuilder b = new StringBuilder();
b.append("geoAreaShape: " + geoAreaShape + "\n");
b.append("shape: " + shape);
int rel = geoAreaShape.getRelationship(shape); int rel = geoAreaShape.getRelationship(shape);
assertEquals(GeoArea.DISJOINT, rel); assertEquals(b.toString(), GeoArea.DISJOINT, rel);
if (shape instanceof GeoArea) { if (shape instanceof GeoArea) {
rel = ((GeoArea)shape).getRelationship(geoAreaShape); rel = ((GeoArea)shape).getRelationship(geoAreaShape);
assertEquals(GeoArea.DISJOINT, rel); assertEquals(b.toString(), GeoArea.DISJOINT, rel);
} }
} }
@ -129,7 +140,11 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
public void testRandomWithIn() { public void testRandomWithIn() {
PlanetModel planetModel = randomPlanetModel(); PlanetModel planetModel = randomPlanetModel();
int geoAreaShapeType = randomGeoAreaShapeType(); int geoAreaShapeType = randomGeoAreaShapeType();
int shapeType =randomShapeType(); //shapes cannot be point or line -- no area!
while(geoAreaShapeType == POINT || geoAreaShapeType == LINE) {
geoAreaShapeType = randomGeoAreaShapeType();
}
int shapeType = LINE;//randomShapeType();
int referenceShapeType = CONVEX_SIMPLE_POLYGON; int referenceShapeType = CONVEX_SIMPLE_POLYGON;
if (!isConcave(geoAreaShapeType)){ if (!isConcave(geoAreaShapeType)){
shapeType =randomConvexShapeType(); shapeType =randomConvexShapeType();
@ -150,11 +165,14 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
shape = randomGeoShape(shapeType, planetModel, constraints); shape = randomGeoShape(shapeType, planetModel, constraints);
} }
} }
StringBuilder b = new StringBuilder();
b.append("geoAreaShape: " + geoAreaShape + "\n");
b.append("shape: " + shape);
int rel = geoAreaShape.getRelationship(shape); int rel = geoAreaShape.getRelationship(shape);
assertEquals(GeoArea.WITHIN, rel); assertEquals(b.toString(), GeoArea.WITHIN, rel);
if (shape instanceof GeoArea) { if (shape instanceof GeoArea) {
rel = ((GeoArea)shape).getRelationship(geoAreaShape); rel = ((GeoArea)shape).getRelationship(geoAreaShape);
assertEquals(GeoArea.CONTAINS, rel); assertEquals(b.toString(), GeoArea.CONTAINS, rel);
} }
} }
@ -178,6 +196,9 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
geoAreaShapeType = randomGeoAreaShapeType(); geoAreaShapeType = randomGeoAreaShapeType();
} }
int shapeType = randomShapeType(); int shapeType = randomShapeType();
while (shapeType == POINT || shapeType == LINE) {
shapeType = randomShapeType();
}
if (isConcave(geoAreaShapeType)){ if (isConcave(geoAreaShapeType)){
shapeType = randomConcaveShapeType(); shapeType = randomConcaveShapeType();
} }
@ -197,11 +218,14 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
shape = randomGeoShape(shapeType, planetModel, constraints); shape = randomGeoShape(shapeType, planetModel, constraints);
} }
} }
StringBuilder b = new StringBuilder();
b.append("geoAreaShape: " + geoAreaShape + "\n");
b.append("shape: " + shape);
int rel = geoAreaShape.getRelationship(shape); int rel = geoAreaShape.getRelationship(shape);
assertEquals(GeoArea.CONTAINS, rel); assertEquals(b.toString(), GeoArea.CONTAINS, rel);
if (shape instanceof GeoArea) { if (shape instanceof GeoArea) {
rel = ((GeoArea)shape).getRelationship(geoAreaShape); rel = ((GeoArea)shape).getRelationship(geoAreaShape);
assertEquals(GeoArea.WITHIN, rel); assertEquals(b.toString(), GeoArea.WITHIN, rel);
} }
} }
@ -216,8 +240,13 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
public void testRandomOverlaps() { public void testRandomOverlaps() {
PlanetModel planetModel = randomPlanetModel(); PlanetModel planetModel = randomPlanetModel();
int geoAreaShapeType = randomGeoAreaShapeType(); int geoAreaShapeType = randomGeoAreaShapeType();
while (geoAreaShapeType == POINT || geoAreaShapeType == LINE) {
geoAreaShapeType = randomGeoAreaShapeType();
}
int shapeType = randomShapeType(); int shapeType = randomShapeType();
while (shapeType == POINT || shapeType == LINE) {
shapeType = randomShapeType();
}
GeoShape shape = null; GeoShape shape = null;
GeoAreaShape geoAreaShape = null; GeoAreaShape geoAreaShape = null;
while (shape == null) { while (shape == null) {
@ -246,12 +275,14 @@ public class RandomGeoShapeRelationshipTest extends RandomGeoShapeGenerator {
shape = randomGeoShape(shapeType, planetModel, constraints); shape = randomGeoShape(shapeType, planetModel, constraints);
} }
} }
StringBuilder b = new StringBuilder();
b.append("geoAreaShape: " + geoAreaShape + "\n");
b.append("shape: " + shape);
int rel = geoAreaShape.getRelationship(shape); int rel = geoAreaShape.getRelationship(shape);
assertEquals(GeoArea.OVERLAPS, rel); assertEquals(b.toString(), GeoArea.OVERLAPS, rel);
if (shape instanceof GeoArea) { if (shape instanceof GeoArea) {
rel = ((GeoArea)shape).getRelationship(geoAreaShape); rel = ((GeoArea)shape).getRelationship(geoAreaShape);
assertEquals(GeoArea.OVERLAPS, rel); assertEquals(b.toString(), GeoArea.OVERLAPS, rel);
} }
} }
} }

View File

@ -24,6 +24,7 @@ import java.util.List;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/** /**
* Check relationship between polygon and GeoShapes of basic polygons. Normally we construct * Check relationship between polygon and GeoShapes of basic polygons. Normally we construct
@ -736,6 +737,29 @@ public class SimpleGeoPolygonRelationshipsTest {
assertEquals(GeoArea.CONTAINS, rel); assertEquals(GeoArea.CONTAINS, rel);
} }
@Test
public void testDegeneratedPointInPole(){
GeoBBox bBox1 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI*0.5, Math.PI*0.5, 0, 0);
GeoPoint point = new GeoPoint(PlanetModel.SPHERE, Math.PI*0.5, Math.PI);
System.out.println("bbox1 = "+bBox1+"; point = "+point);
assertTrue(bBox1.isWithin(point));
}
@Test
public void testDegeneratePathShape(){
GeoPoint point1 = new GeoPoint(PlanetModel.SPHERE, 0, 0);
GeoPoint point2 = new GeoPoint(PlanetModel.SPHERE, 0, 1);
GeoPoint[] pointPath1 = new GeoPoint[] {point1, point2};
GeoPath path1 = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 0, pointPath1);
GeoPath path2 = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 1, pointPath1);
int rel = path1.getRelationship(path2);
//if an end point is inside the shape it will always return intersects
assertEquals(GeoArea.CONTAINS, rel); //should be contains?
rel = path2.getRelationship(path1);
assertEquals(GeoArea.WITHIN, rel);
}
private GeoPolygon buildConvexGeoPolygon(double lon1, double lat1, private GeoPolygon buildConvexGeoPolygon(double lon1, double lat1,
double lon2, double lat2, double lon2, double lat2,
double lon3, double lat3, double lon3, double lat3,

View File

@ -390,9 +390,11 @@ public class AnalyzingSuggester extends Lookup implements Accountable {
} else { } else {
scratchA.offset = readerA.getPosition(); scratchA.offset = readerA.getPosition();
scratchB.offset = readerB.getPosition(); scratchB.offset = readerB.getPosition();
scratchA.length = a.length - scratchA.offset; scratchA.length = readerA.length() - readerA.getPosition();
scratchB.length = b.length - scratchB.offset; scratchB.length = readerB.length() - readerB.getPosition();
} }
assert scratchA.isValid();
assert scratchB.isValid();
return scratchA.compareTo(scratchB); return scratchA.compareTo(scratchB);
} }

View File

@ -1111,6 +1111,66 @@ public class AnalyzingSuggesterTest extends LuceneTestCase {
IOUtils.close(a, tempDir); IOUtils.close(a, tempDir);
} }
/**
* Adds 50 random keys, that all analyze to the same thing (dog), with the same cost,
* and checks that they come back in surface-form order.
*/
public void testTieBreakOnSurfaceForm() throws Exception {
Analyzer a = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer tokenizer = new MockTokenizer(MockTokenizer.SIMPLE, true);
return new TokenStreamComponents(tokenizer) {
@Override
public TokenStream getTokenStream() {
return new CannedTokenStream(new Token[] {
token("dog", 1, 1)
});
}
@Override
protected void setReader(final Reader reader) {
}
};
}
};
Directory tempDir = getDirectory();
AnalyzingSuggester suggester = new AnalyzingSuggester(tempDir, "suggest", a, a, 0, 256, -1, true);
// make 50 inputs all with the same cost of 1, random strings
Input[] inputs = new Input[100];
for (int i = 0; i < inputs.length; i++) {
inputs[i] = new Input(TestUtil.randomSimpleString(random()), 1);
}
suggester.build(new InputArrayIterator(inputs));
// Try to save/load:
Path tmpDir = createTempDir("AnalyzingSuggesterTest");
Path path = tmpDir.resolve("suggester");
OutputStream os = Files.newOutputStream(path);
suggester.store(os);
os.close();
InputStream is = Files.newInputStream(path);
suggester.load(is);
is.close();
// now suggest everything, and check that stuff comes back in order
List<LookupResult> results = suggester.lookup("", false, 50);
assertEquals(50, results.size());
for (int i = 1; i < 50; i++) {
String previous = results.get(i-1).toString();
String current = results.get(i).toString();
assertTrue("surface forms out of order: previous=" + previous + ",current=" + current,
current.compareTo(previous) >= 0);
}
IOUtils.close(a, tempDir);
}
public void test0ByteKeys() throws Exception { public void test0ByteKeys() throws Exception {
final Analyzer a = new Analyzer() { final Analyzer a = new Analyzer() {
@Override @Override

View File

@ -100,6 +100,13 @@ New Features
* SOLR-11072: Implement trigger for searchRate event type. (ab) * SOLR-11072: Implement trigger for searchRate event type. (ab)
* SOLR-11317: JSON Facet API: min/max aggregations on numeric fields are now typed better so int/long
fields return an appropriate integral type rather than a double. (yonik)
* SOLR-11316: JSON Facet API: min/max aggregations are now supported on single-valued date fields.
(yonik)
Bug Fixes Bug Fixes
---------------------- ----------------------
@ -142,6 +149,12 @@ Bug Fixes
* SOLR-11293: Potential data loss in TLOG replicas after replication failures (noble) * SOLR-11293: Potential data loss in TLOG replicas after replication failures (noble)
* SOLR-10101: TestLazyCores hangs (Erick Erickson)
* SOLR-11332: Fix sorting on 'enum' fieldTypes that use sortMissingFirst or sortMissingLast (hossman)
* SOLR-11348: Fix the DIH database example (James Dyer)
Optimizations Optimizations
---------------------- ----------------------
@ -154,6 +167,10 @@ Optimizations
* SOLR-10769: Allow nodeAdded / nodeLost events to report multiple nodes in one event. (ab) * SOLR-10769: Allow nodeAdded / nodeLost events to report multiple nodes in one event. (ab)
* SOLR-11242: QueryParser: re-use the LookaheadSuccess exception. (Michael Braun via David Smiley)
* SOLR-11314: FastCharStream: re-use the READ_PAST_EOF exception. (Michael Braun via David Smiley)
Other Changes Other Changes
---------------------- ----------------------
@ -216,6 +233,15 @@ Other Changes
* SOLR-11209: Upgrade HttpClient to 4.5.3. (Hrishikesh Gadre via Mark Miller) * SOLR-11209: Upgrade HttpClient to 4.5.3. (Hrishikesh Gadre via Mark Miller)
* SOLR-11322: JSON Facet API: instead of returning NaN, min & max aggregations omit
the value for any bucket with no values in the numeric field. (yonik)
* SOLR-10990: Breakup QueryComponent.process method for readability. (Christine Poerschke)
* SOLR-11132: Refactor common getSortField logic in various FieldTypes (Jason Gerlowski, hossman)
* SOLR-11351: Make LTRScoringModel model more extensible. (Christine Poerschke)
================== 7.0.0 ================== ================== 7.0.0 ==================
Versions of Major Components Versions of Major Components
@ -848,7 +874,7 @@ Other Changes
* SOLR-10494: Make default response format JSON (wt=json), and also indent text responses formats * SOLR-10494: Make default response format JSON (wt=json), and also indent text responses formats
(indent=on) by default (Trey Grainger & Cassandra Targett via hossman) (indent=on) by default (Trey Grainger & Cassandra Targett via hossman)
* SOLR-10760: Remove trie field types and fields from example schemas. (Steve Rowe) * SOLR-10760,SOLR-11315,SOLR-11313: Remove trie field types and fields from example schemas. (Steve Rowe)
* SOLR-11056: Add random range query test that compares results across Trie*, *Point and DocValue-only fields * SOLR-11056: Add random range query test that compares results across Trie*, *Point and DocValue-only fields
(Tomás Fernández Löbbe) (Tomás Fernández Löbbe)
@ -942,6 +968,8 @@ Other Changes
* SOLR-11261, SOLR-10966: Upgrade to Hadoop 2.7.4 to fix incompatibility with Java 9. * SOLR-11261, SOLR-10966: Upgrade to Hadoop 2.7.4 to fix incompatibility with Java 9.
(Uwe Schindler) (Uwe Schindler)
* SOLR-11324: Clean up mention of trie fields in documentation and source comments. (Steve Rowe)
================== 6.6.1 ================== ================== 6.6.1 ==================
Bug Fixes Bug Fixes

View File

@ -710,6 +710,20 @@
</sequential> </sequential>
</target> </target>
<target name="-install-to-maven-local-repo" depends="install-maven-tasks">
<sequential>
<m2-install pom.xml="${filtered.pom.templates.dir}/solr/pom.xml"/> <!-- Solr parent POM -->
<subant target="-install-to-maven-local-repo" inheritall="false">
<propertyset refid="uptodate.and.compiled.properties"/>
<fileset dir="core" includes="build.xml"/>
<fileset dir="solrj" includes="build.xml"/>
<fileset dir="test-framework" includes="build.xml"/>
<fileset dir="webapp" includes="build.xml"/>
</subant>
<contrib-crawl target="-install-to-maven-local-repo"/>
</sequential>
</target>
<target name="generate-maven-artifacts" depends="-unpack-solr-tgz"> <target name="generate-maven-artifacts" depends="-unpack-solr-tgz">
<ant dir=".." target="resolve" inheritall="false"/> <ant dir=".." target="resolve" inheritall="false"/>
<antcall target="-filter-pom-templates" inheritall="false"/> <antcall target="-filter-pom-templates" inheritall="false"/>

View File

@ -80,7 +80,7 @@ public abstract class LTRScoringModel {
protected final List<Feature> features; protected final List<Feature> features;
private final List<Feature> allFeatures; private final List<Feature> allFeatures;
private final Map<String,Object> params; private final Map<String,Object> params;
private final List<Normalizer> norms; protected final List<Normalizer> norms;
public static LTRScoringModel getInstance(SolrResourceLoader solrResourceLoader, public static LTRScoringModel getInstance(SolrResourceLoader solrResourceLoader,
String className, String name, List<Feature> features, String className, String name, List<Feature> features,
@ -123,6 +123,8 @@ public abstract class LTRScoringModel {
* {@link ModelException} if they do not make sense. * {@link ModelException} if they do not make sense.
*/ */
protected void validate() throws ModelException { protected void validate() throws ModelException {
final List<Feature> features = getFeatures();
final List<Normalizer> norms = getNorms();
if (features.isEmpty()) { if (features.isEmpty()) {
throw new ModelException("no features declared for model "+name); throw new ModelException("no features declared for model "+name);
} }

View File

@ -239,12 +239,14 @@ public class TestRerankBase extends RestTestBase {
.append(",\n"); .append(",\n");
sb.append("\"class\":").append('"').append(type).append('"').append(",\n"); sb.append("\"class\":").append('"').append(type).append('"').append(",\n");
sb.append("\"features\":").append('['); sb.append("\"features\":").append('[');
if (features.length > 0) {
for (final String feature : features) { for (final String feature : features) {
sb.append("\n\t{ "); sb.append("\n\t{ ");
sb.append("\"name\":").append('"').append(feature).append('"') sb.append("\"name\":").append('"').append(feature).append('"')
.append("},"); .append("},");
} }
sb.deleteCharAt(sb.length() - 1); sb.deleteCharAt(sb.length() - 1);
}
sb.append("\n]\n"); sb.append("\n]\n");
if (params != null) { if (params != null) {
sb.append(",\n"); sb.append(",\n");

View File

@ -60,6 +60,8 @@
<target name="-dist-maven" depends="-dist-maven-src-java"/> <target name="-dist-maven" depends="-dist-maven-src-java"/>
<target name="-install-to-maven-local-repo" depends="-install-src-java-to-maven-local-repo"/>
<target name="resolve" depends="ivy-availability-check,ivy-fail,ivy-configure"> <target name="resolve" depends="ivy-availability-check,ivy-fail,ivy-configure">
<sequential> <sequential>
<ivy:retrieve conf="compile,compile.hadoop" type="jar,bundle" sync="${ivy.sync}" log="download-only" symlink="${ivy.symlink}"/> <ivy:retrieve conf="compile,compile.hadoop" type="jar,bundle" sync="${ivy.sync}" log="download-only" symlink="${ivy.symlink}"/>
@ -83,6 +85,11 @@
byline="true" byline="true"
match="public QueryParser\(QueryParserTokenManager " match="public QueryParser\(QueryParserTokenManager "
replace="protected QueryParser(QueryParserTokenManager "/> replace="protected QueryParser(QueryParserTokenManager "/>
<!-- change an exception used for signaling to be static -->
<replaceregexp file="src/java/org/apache/solr/parser/QueryParser.java"
byline="true"
match="final private LookaheadSuccess jj_ls ="
replace="static final private LookaheadSuccess jj_ls =" />
<replace token="StringBuffer" value="StringBuilder" encoding="UTF-8"> <replace token="StringBuffer" value="StringBuilder" encoding="UTF-8">
<fileset dir="src/java/org/apache/solr/parser" includes="ParseException.java TokenMgrError.java"/> <fileset dir="src/java/org/apache/solr/parser" includes="ParseException.java TokenMgrError.java"/>
</replace> </replace>

View File

@ -33,84 +33,7 @@ import org.apache.solr.client.solrj.io.ModelCache;
import org.apache.solr.client.solrj.io.SolrClientCache; import org.apache.solr.client.solrj.io.SolrClientCache;
import org.apache.solr.client.solrj.io.Tuple; import org.apache.solr.client.solrj.io.Tuple;
import org.apache.solr.client.solrj.io.comp.StreamComparator; import org.apache.solr.client.solrj.io.comp.StreamComparator;
import org.apache.solr.client.solrj.io.eval.AbsoluteValueEvaluator; import org.apache.solr.client.solrj.io.eval.*;
import org.apache.solr.client.solrj.io.eval.AddEvaluator;
import org.apache.solr.client.solrj.io.eval.AndEvaluator;
import org.apache.solr.client.solrj.io.eval.AnovaEvaluator;
import org.apache.solr.client.solrj.io.eval.AppendEvaluator;
import org.apache.solr.client.solrj.io.eval.ArcCosineEvaluator;
import org.apache.solr.client.solrj.io.eval.ArcSineEvaluator;
import org.apache.solr.client.solrj.io.eval.ArcTangentEvaluator;
import org.apache.solr.client.solrj.io.eval.ArrayEvaluator;
import org.apache.solr.client.solrj.io.eval.AscEvaluator;
import org.apache.solr.client.solrj.io.eval.CeilingEvaluator;
import org.apache.solr.client.solrj.io.eval.CoalesceEvaluator;
import org.apache.solr.client.solrj.io.eval.ColumnEvaluator;
import org.apache.solr.client.solrj.io.eval.ConversionEvaluator;
import org.apache.solr.client.solrj.io.eval.ConvolutionEvaluator;
import org.apache.solr.client.solrj.io.eval.CopyOfEvaluator;
import org.apache.solr.client.solrj.io.eval.CopyOfRangeEvaluator;
import org.apache.solr.client.solrj.io.eval.CorrelationEvaluator;
import org.apache.solr.client.solrj.io.eval.CosineEvaluator;
import org.apache.solr.client.solrj.io.eval.CovarianceEvaluator;
import org.apache.solr.client.solrj.io.eval.CubedRootEvaluator;
import org.apache.solr.client.solrj.io.eval.CumulativeProbabilityEvaluator;
import org.apache.solr.client.solrj.io.eval.DescribeEvaluator;
import org.apache.solr.client.solrj.io.eval.DivideEvaluator;
import org.apache.solr.client.solrj.io.eval.EmpiricalDistributionEvaluator;
import org.apache.solr.client.solrj.io.eval.EqualToEvaluator;
import org.apache.solr.client.solrj.io.eval.EuclideanDistanceEvaluator;
import org.apache.solr.client.solrj.io.eval.ExclusiveOrEvaluator;
import org.apache.solr.client.solrj.io.eval.FindDelayEvaluator;
import org.apache.solr.client.solrj.io.eval.FloorEvaluator;
import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
import org.apache.solr.client.solrj.io.eval.HistogramEvaluator;
import org.apache.solr.client.solrj.io.eval.HyperbolicCosineEvaluator;
import org.apache.solr.client.solrj.io.eval.HyperbolicSineEvaluator;
import org.apache.solr.client.solrj.io.eval.HyperbolicTangentEvaluator;
import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
import org.apache.solr.client.solrj.io.eval.KolmogorovSmirnovEvaluator;
import org.apache.solr.client.solrj.io.eval.LengthEvaluator;
import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
import org.apache.solr.client.solrj.io.eval.ModuloEvaluator;
import org.apache.solr.client.solrj.io.eval.MovingAverageEvaluator;
import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
import org.apache.solr.client.solrj.io.eval.NaturalLogEvaluator;
import org.apache.solr.client.solrj.io.eval.NormalDistributionEvaluator;
import org.apache.solr.client.solrj.io.eval.NormalizeEvaluator;
import org.apache.solr.client.solrj.io.eval.NotEvaluator;
import org.apache.solr.client.solrj.io.eval.OrEvaluator;
import org.apache.solr.client.solrj.io.eval.PercentileEvaluator;
import org.apache.solr.client.solrj.io.eval.PowerEvaluator;
import org.apache.solr.client.solrj.io.eval.PredictEvaluator;
import org.apache.solr.client.solrj.io.eval.RankEvaluator;
import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
import org.apache.solr.client.solrj.io.eval.RegressionEvaluator;
import org.apache.solr.client.solrj.io.eval.ResidualsEvaluator;
import org.apache.solr.client.solrj.io.eval.ReverseEvaluator;
import org.apache.solr.client.solrj.io.eval.RoundEvaluator;
import org.apache.solr.client.solrj.io.eval.SampleEvaluator;
import org.apache.solr.client.solrj.io.eval.ScaleEvaluator;
import org.apache.solr.client.solrj.io.eval.SequenceEvaluator;
import org.apache.solr.client.solrj.io.eval.SineEvaluator;
import org.apache.solr.client.solrj.io.eval.SquareRootEvaluator;
import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
import org.apache.solr.client.solrj.io.eval.TangentEvaluator;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDay;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfQuarter;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfYear;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorEpoch;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorHour;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMinute;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMonth;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorQuarter;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorSecond;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorWeek;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorYear;
import org.apache.solr.client.solrj.io.eval.UniformDistributionEvaluator;
import org.apache.solr.client.solrj.io.eval.UuidEvaluator;
import org.apache.solr.client.solrj.io.graph.GatherNodesStream; import org.apache.solr.client.solrj.io.graph.GatherNodesStream;
import org.apache.solr.client.solrj.io.graph.ShortestPathStream; import org.apache.solr.client.solrj.io.graph.ShortestPathStream;
import org.apache.solr.client.solrj.io.ops.ConcatOperation; import org.apache.solr.client.solrj.io.ops.ConcatOperation;
@ -326,8 +249,15 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
.withFunctionName("copyOfRange", CopyOfRangeEvaluator.class) .withFunctionName("copyOfRange", CopyOfRangeEvaluator.class)
.withFunctionName("copyOf", CopyOfEvaluator.class) .withFunctionName("copyOf", CopyOfEvaluator.class)
.withFunctionName("cov", CovarianceEvaluator.class) .withFunctionName("cov", CovarianceEvaluator.class)
.withFunctionName("corr", CorrelationEvaluator.class)
.withFunctionName("kendallsCorr", KendallsCorrelationEvaluator.class)
.withFunctionName("spearmansCorr", SpearmansCorrelationEvaluator.class)
.withFunctionName("describe", DescribeEvaluator.class) .withFunctionName("describe", DescribeEvaluator.class)
.withFunctionName("distance", EuclideanDistanceEvaluator.class) .withFunctionName("distance", EuclideanDistanceEvaluator.class)
.withFunctionName("manhattanDistance", ManhattanDistanceEvaluator.class)
.withFunctionName("earthMoversDistance", EarthMoversDistanceEvaluator.class)
.withFunctionName("canberraDistance", CanberraDistanceEvaluator.class)
.withFunctionName("chebyshevDistance", ChebyshevDistanceEvaluator.class)
.withFunctionName("empiricalDistribution", EmpiricalDistributionEvaluator.class) .withFunctionName("empiricalDistribution", EmpiricalDistributionEvaluator.class)
.withFunctionName("finddelay", FindDelayEvaluator.class) .withFunctionName("finddelay", FindDelayEvaluator.class)
.withFunctionName("hist", HistogramEvaluator.class) .withFunctionName("hist", HistogramEvaluator.class)
@ -352,8 +282,26 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
.withFunctionName("ks", KolmogorovSmirnovEvaluator.class) .withFunctionName("ks", KolmogorovSmirnovEvaluator.class)
.withFunctionName("asc", AscEvaluator.class) .withFunctionName("asc", AscEvaluator.class)
.withFunctionName("cumulativeProbability", CumulativeProbabilityEvaluator.class) .withFunctionName("cumulativeProbability", CumulativeProbabilityEvaluator.class)
.withFunctionName("ebeAdd", EBEAddEvaluator.class)
.withFunctionName("ebeSubtract", EBESubtractEvaluator.class)
.withFunctionName("ebeMultiply", EBEMultiplyEvaluator.class)
.withFunctionName("ebeDivide", EBEDivideEvaluator.class)
.withFunctionName("dotProduct", DotProductEvaluator.class)
.withFunctionName("cosineSimilarity", CosineSimilarityEvaluator.class)
.withFunctionName("freqTable", FrequencyTableEvaluator.class)
.withFunctionName("uniformIntegerDistribution", UniformIntegerDistributionEvaluator.class)
.withFunctionName("binomialDistribution", BinomialDistributionEvaluator.class)
.withFunctionName("poissonDistribution", PoissonDistributionEvaluator.class)
.withFunctionName("enumeratedDistribution", EnumeratedDistributionEvaluator.class)
.withFunctionName("probability", ProbabilityEvaluator.class)
.withFunctionName("sumDifference", SumDifferenceEvaluator.class)
.withFunctionName("meanDifference", MeanDifferenceEvaluator.class)
.withFunctionName("primes", PrimesEvaluator.class)
// Boolean Stream Evaluators // Boolean Stream Evaluators
.withFunctionName("and", AndEvaluator.class) .withFunctionName("and", AndEvaluator.class)
.withFunctionName("eor", ExclusiveOrEvaluator.class) .withFunctionName("eor", ExclusiveOrEvaluator.class)
.withFunctionName("eq", EqualToEvaluator.class) .withFunctionName("eq", EqualToEvaluator.class)
@ -402,7 +350,6 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
.withFunctionName("cbrt", CubedRootEvaluator.class) .withFunctionName("cbrt", CubedRootEvaluator.class)
.withFunctionName("coalesce", CoalesceEvaluator.class) .withFunctionName("coalesce", CoalesceEvaluator.class)
.withFunctionName("uuid", UuidEvaluator.class) .withFunctionName("uuid", UuidEvaluator.class)
.withFunctionName("corr", CorrelationEvaluator.class)
// Conditional Stream Evaluators // Conditional Stream Evaluators
.withFunctionName("if", IfThenElseEvaluator.class) .withFunctionName("if", IfThenElseEvaluator.class)

View File

@ -306,12 +306,12 @@ public class QueryComponent extends SearchComponent
if (!params.getBool(COMPONENT_NAME, true)) { if (!params.getBool(COMPONENT_NAME, true)) {
return; return;
} }
SolrIndexSearcher searcher = req.getSearcher();
StatsCache statsCache = req.getCore().getStatsCache(); StatsCache statsCache = req.getCore().getStatsCache();
int purpose = params.getInt(ShardParams.SHARDS_PURPOSE, ShardRequest.PURPOSE_GET_TOP_IDS); int purpose = params.getInt(ShardParams.SHARDS_PURPOSE, ShardRequest.PURPOSE_GET_TOP_IDS);
if ((purpose & ShardRequest.PURPOSE_GET_TERM_STATS) != 0) { if ((purpose & ShardRequest.PURPOSE_GET_TERM_STATS) != 0) {
SolrIndexSearcher searcher = req.getSearcher();
statsCache.returnLocalStats(rb, searcher); statsCache.returnLocalStats(rb, searcher);
return; return;
} }
@ -321,50 +321,11 @@ public class QueryComponent extends SearchComponent
statsCache.receiveGlobalStats(req); statsCache.receiveGlobalStats(req);
} }
SolrQueryResponse rsp = rb.rsp;
IndexSchema schema = searcher.getSchema();
// Optional: This could also be implemented by the top-level searcher sending // Optional: This could also be implemented by the top-level searcher sending
// a filter that lists the ids... that would be transparent to // a filter that lists the ids... that would be transparent to
// the request handler, but would be more expensive (and would preserve score // the request handler, but would be more expensive (and would preserve score
// too if desired). // too if desired).
String ids = params.get(ShardParams.IDS); if (doProcessSearchByIds(rb)) {
if (ids != null) {
SchemaField idField = schema.getUniqueKeyField();
List<String> idArr = StrUtils.splitSmart(ids, ",", true);
int[] luceneIds = new int[idArr.size()];
int docs = 0;
if (idField.getType().isPointField()) {
for (int i=0; i<idArr.size(); i++) {
int id = searcher.search(
idField.getType().getFieldQuery(null, idField, idArr.get(i)), 1).scoreDocs[0].doc;
if (id >= 0) {
luceneIds[docs++] = id;
}
}
} else {
for (int i=0; i<idArr.size(); i++) {
int id = searcher.getFirstMatch(
new Term(idField.getName(), idField.getType().toInternal(idArr.get(i))));
if (id >= 0)
luceneIds[docs++] = id;
}
}
DocListAndSet res = new DocListAndSet();
res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0);
if (rb.isNeedDocSet()) {
// TODO: create a cache for this!
List<Query> queries = new ArrayList<>();
queries.add(rb.getQuery());
List<Query> filters = rb.getFilters();
if (filters != null) queries.addAll(filters);
res.docSet = searcher.getDocSet(queries);
}
rb.setResults(res);
ResultContext ctx = new BasicResultContext(rb);
rsp.addResponse(ctx);
return; return;
} }
@ -395,145 +356,15 @@ public class QueryComponent extends SearchComponent
if (groupingSpec != null) { if (groupingSpec != null) {
cmd.setSegmentTerminateEarly(false); // not supported, silently ignore any segmentTerminateEarly flag cmd.setSegmentTerminateEarly(false); // not supported, silently ignore any segmentTerminateEarly flag
try { try {
boolean needScores = (cmd.getFlags() & SolrIndexSearcher.GET_SCORES) != 0;
if (params.getBool(GroupParams.GROUP_DISTRIBUTED_FIRST, false)) { if (params.getBool(GroupParams.GROUP_DISTRIBUTED_FIRST, false)) {
CommandHandler.Builder topsGroupsActionBuilder = new CommandHandler.Builder() doProcessGroupedDistributedSearchFirstPhase(rb, cmd, result);
.setQueryCommand(cmd)
.setNeedDocSet(false) // Order matters here
.setIncludeHitCount(true)
.setSearcher(searcher);
for (String field : groupingSpec.getFields()) {
topsGroupsActionBuilder.addCommandField(new SearchGroupsFieldCommand.Builder()
.setField(schema.getField(field))
.setGroupSort(groupingSpec.getGroupSort())
.setTopNGroups(cmd.getOffset() + cmd.getLen())
.setIncludeGroupCount(groupingSpec.isIncludeGroupCount())
.build()
);
}
CommandHandler commandHandler = topsGroupsActionBuilder.build();
commandHandler.execute();
SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(searcher);
rsp.add("firstPhase", commandHandler.processResult(result, serializer));
rsp.add("totalHitCount", commandHandler.getTotalHitCount());
rb.setResult(result);
return; return;
} else if (params.getBool(GroupParams.GROUP_DISTRIBUTED_SECOND, false)) { } else if (params.getBool(GroupParams.GROUP_DISTRIBUTED_SECOND, false)) {
CommandHandler.Builder secondPhaseBuilder = new CommandHandler.Builder() doProcessGroupedDistributedSearchSecondPhase(rb, cmd, result);
.setQueryCommand(cmd)
.setTruncateGroups(groupingSpec.isTruncateGroups() && groupingSpec.getFields().length > 0)
.setSearcher(searcher);
int docsToCollect = Grouping.getMax(groupingSpec.getWithinGroupOffset(), groupingSpec.getWithinGroupLimit(), searcher.maxDoc());
docsToCollect = Math.max(docsToCollect, 1);
for (String field : groupingSpec.getFields()) {
SchemaField schemaField = schema.getField(field);
String[] topGroupsParam = params.getParams(GroupParams.GROUP_DISTRIBUTED_TOPGROUPS_PREFIX + field);
if (topGroupsParam == null) {
topGroupsParam = new String[0];
}
List<SearchGroup<BytesRef>> topGroups = new ArrayList<>(topGroupsParam.length);
for (String topGroup : topGroupsParam) {
SearchGroup<BytesRef> searchGroup = new SearchGroup<>();
if (!topGroup.equals(TopGroupsShardRequestFactory.GROUP_NULL_VALUE)) {
BytesRefBuilder builder = new BytesRefBuilder();
schemaField.getType().readableToIndexed(topGroup, builder);
searchGroup.groupValue = builder.get();
}
topGroups.add(searchGroup);
}
secondPhaseBuilder.addCommandField(
new TopGroupsFieldCommand.Builder()
.setField(schemaField)
.setGroupSort(groupingSpec.getGroupSort())
.setSortWithinGroup(groupingSpec.getSortWithinGroup())
.setFirstPhaseGroups(topGroups)
.setMaxDocPerGroup(docsToCollect)
.setNeedScores(needScores)
.setNeedMaxScore(needScores)
.build()
);
}
for (String query : groupingSpec.getQueries()) {
secondPhaseBuilder.addCommandField(new Builder()
.setDocsToCollect(docsToCollect)
.setSort(groupingSpec.getGroupSort())
.setQuery(query, rb.req)
.setDocSet(searcher)
.build()
);
}
CommandHandler commandHandler = secondPhaseBuilder.build();
commandHandler.execute();
TopGroupsResultTransformer serializer = new TopGroupsResultTransformer(rb);
rsp.add("secondPhase", commandHandler.processResult(result, serializer));
rb.setResult(result);
return; return;
} }
int maxDocsPercentageToCache = params.getInt(GroupParams.GROUP_CACHE_PERCENTAGE, 0); doProcessGroupedSearch(rb, cmd, result);
boolean cacheSecondPassSearch = maxDocsPercentageToCache >= 1 && maxDocsPercentageToCache <= 100;
Grouping.TotalCount defaultTotalCount = groupingSpec.isIncludeGroupCount() ?
Grouping.TotalCount.grouped : Grouping.TotalCount.ungrouped;
int limitDefault = cmd.getLen(); // this is normally from "rows"
Grouping grouping =
new Grouping(searcher, result, cmd, cacheSecondPassSearch, maxDocsPercentageToCache, groupingSpec.isMain());
grouping.setGroupSort(groupingSpec.getGroupSort())
.setWithinGroupSort(groupingSpec.getSortWithinGroup())
.setDefaultFormat(groupingSpec.getResponseFormat())
.setLimitDefault(limitDefault)
.setDefaultTotalCount(defaultTotalCount)
.setDocsPerGroupDefault(groupingSpec.getWithinGroupLimit())
.setGroupOffsetDefault(groupingSpec.getWithinGroupOffset())
.setGetGroupedDocSet(groupingSpec.isTruncateGroups());
if (groupingSpec.getFields() != null) {
for (String field : groupingSpec.getFields()) {
grouping.addFieldCommand(field, rb.req);
}
}
if (groupingSpec.getFunctions() != null) {
for (String groupByStr : groupingSpec.getFunctions()) {
grouping.addFunctionCommand(groupByStr, rb.req);
}
}
if (groupingSpec.getQueries() != null) {
for (String groupByStr : groupingSpec.getQueries()) {
grouping.addQueryCommand(groupByStr, rb.req);
}
}
if( rb.isNeedDocList() || rb.isDebug() ){
// we need a single list of the returned docs
cmd.setFlags(SolrIndexSearcher.GET_DOCLIST);
}
grouping.execute();
if (grouping.isSignalCacheWarning()) {
rsp.add(
"cacheWarning",
String.format(Locale.ROOT, "Cache limit of %d percent relative to maxdoc has exceeded. Please increase cache size or disable caching.", maxDocsPercentageToCache)
);
}
rb.setResult(result);
if (grouping.mainResult != null) {
ResultContext ctx = new BasicResultContext(rb, grouping.mainResult);
rsp.addResponse(ctx);
rsp.getToLog().add("hits", grouping.mainResult.matches());
} else if (!grouping.getCommands().isEmpty()) { // Can never be empty since grouping.execute() checks for this.
rsp.add("grouped", result.groupedResults);
rsp.getToLog().add("hits", grouping.getCommands().get(0).getMatches());
}
return; return;
} catch (SyntaxError e) { } catch (SyntaxError e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
@ -541,27 +372,7 @@ public class QueryComponent extends SearchComponent
} }
// normal search result // normal search result
searcher.search(result, cmd); doProcessUngroupedSearch(rb, cmd, result);
rb.setResult(result);
ResultContext ctx = new BasicResultContext(rb);
rsp.addResponse(ctx);
rsp.getToLog().add("hits", rb.getResults().docList.matches());
if ( ! rb.req.getParams().getBool(ShardParams.IS_SHARD,false) ) {
if (null != rb.getNextCursorMark()) {
rb.rsp.add(CursorMarkParams.CURSOR_MARK_NEXT,
rb.getNextCursorMark().getSerializedTotem());
}
}
if(rb.mergeFieldHandler != null) {
rb.mergeFieldHandler.handleMergeFields(rb, searcher);
} else {
doFieldSortValues(rb, searcher);
}
doPrefetch(rb);
} }
protected void doFieldSortValues(ResponseBuilder rb, SolrIndexSearcher searcher) throws IOException protected void doFieldSortValues(ResponseBuilder rb, SolrIndexSearcher searcher) throws IOException
@ -1385,6 +1196,265 @@ public class QueryComponent extends SearchComponent
return Category.QUERY; return Category.QUERY;
} }
private boolean doProcessSearchByIds(ResponseBuilder rb) throws IOException {
SolrQueryRequest req = rb.req;
SolrQueryResponse rsp = rb.rsp;
SolrParams params = req.getParams();
String ids = params.get(ShardParams.IDS);
if (ids == null) {
return false;
}
SolrIndexSearcher searcher = req.getSearcher();
IndexSchema schema = searcher.getSchema();
SchemaField idField = schema.getUniqueKeyField();
List<String> idArr = StrUtils.splitSmart(ids, ",", true);
int[] luceneIds = new int[idArr.size()];
int docs = 0;
if (idField.getType().isPointField()) {
for (int i=0; i<idArr.size(); i++) {
int id = searcher.search(
idField.getType().getFieldQuery(null, idField, idArr.get(i)), 1).scoreDocs[0].doc;
if (id >= 0) {
luceneIds[docs++] = id;
}
}
} else {
for (int i=0; i<idArr.size(); i++) {
int id = searcher.getFirstMatch(
new Term(idField.getName(), idField.getType().toInternal(idArr.get(i))));
if (id >= 0)
luceneIds[docs++] = id;
}
}
DocListAndSet res = new DocListAndSet();
res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0);
if (rb.isNeedDocSet()) {
// TODO: create a cache for this!
List<Query> queries = new ArrayList<>();
queries.add(rb.getQuery());
List<Query> filters = rb.getFilters();
if (filters != null) queries.addAll(filters);
res.docSet = searcher.getDocSet(queries);
}
rb.setResults(res);
ResultContext ctx = new BasicResultContext(rb);
rsp.addResponse(ctx);
return true;
}
private void doProcessGroupedDistributedSearchFirstPhase(ResponseBuilder rb, QueryCommand cmd, QueryResult result) throws IOException {
GroupingSpecification groupingSpec = rb.getGroupingSpec();
assert null != groupingSpec : "GroupingSpecification is null";
SolrQueryRequest req = rb.req;
SolrQueryResponse rsp = rb.rsp;
SolrIndexSearcher searcher = req.getSearcher();
IndexSchema schema = searcher.getSchema();
CommandHandler.Builder topsGroupsActionBuilder = new CommandHandler.Builder()
.setQueryCommand(cmd)
.setNeedDocSet(false) // Order matters here
.setIncludeHitCount(true)
.setSearcher(searcher);
for (String field : groupingSpec.getFields()) {
topsGroupsActionBuilder.addCommandField(new SearchGroupsFieldCommand.Builder()
.setField(schema.getField(field))
.setGroupSort(groupingSpec.getGroupSort())
.setTopNGroups(cmd.getOffset() + cmd.getLen())
.setIncludeGroupCount(groupingSpec.isIncludeGroupCount())
.build()
);
}
CommandHandler commandHandler = topsGroupsActionBuilder.build();
commandHandler.execute();
SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(searcher);
rsp.add("firstPhase", commandHandler.processResult(result, serializer));
rsp.add("totalHitCount", commandHandler.getTotalHitCount());
rb.setResult(result);
}
private void doProcessGroupedDistributedSearchSecondPhase(ResponseBuilder rb, QueryCommand cmd, QueryResult result) throws IOException, SyntaxError {
GroupingSpecification groupingSpec = rb.getGroupingSpec();
assert null != groupingSpec : "GroupingSpecification is null";
SolrQueryRequest req = rb.req;
SolrQueryResponse rsp = rb.rsp;
SolrParams params = req.getParams();
SolrIndexSearcher searcher = req.getSearcher();
IndexSchema schema = searcher.getSchema();
boolean needScores = (cmd.getFlags() & SolrIndexSearcher.GET_SCORES) != 0;
CommandHandler.Builder secondPhaseBuilder = new CommandHandler.Builder()
.setQueryCommand(cmd)
.setTruncateGroups(groupingSpec.isTruncateGroups() && groupingSpec.getFields().length > 0)
.setSearcher(searcher);
int docsToCollect = Grouping.getMax(groupingSpec.getWithinGroupOffset(), groupingSpec.getWithinGroupLimit(), searcher.maxDoc());
docsToCollect = Math.max(docsToCollect, 1);
for (String field : groupingSpec.getFields()) {
SchemaField schemaField = schema.getField(field);
String[] topGroupsParam = params.getParams(GroupParams.GROUP_DISTRIBUTED_TOPGROUPS_PREFIX + field);
if (topGroupsParam == null) {
topGroupsParam = new String[0];
}
List<SearchGroup<BytesRef>> topGroups = new ArrayList<>(topGroupsParam.length);
for (String topGroup : topGroupsParam) {
SearchGroup<BytesRef> searchGroup = new SearchGroup<>();
if (!topGroup.equals(TopGroupsShardRequestFactory.GROUP_NULL_VALUE)) {
BytesRefBuilder builder = new BytesRefBuilder();
schemaField.getType().readableToIndexed(topGroup, builder);
searchGroup.groupValue = builder.get();
}
topGroups.add(searchGroup);
}
secondPhaseBuilder.addCommandField(
new TopGroupsFieldCommand.Builder()
.setField(schemaField)
.setGroupSort(groupingSpec.getGroupSort())
.setSortWithinGroup(groupingSpec.getSortWithinGroup())
.setFirstPhaseGroups(topGroups)
.setMaxDocPerGroup(docsToCollect)
.setNeedScores(needScores)
.setNeedMaxScore(needScores)
.build()
);
}
for (String query : groupingSpec.getQueries()) {
secondPhaseBuilder.addCommandField(new Builder()
.setDocsToCollect(docsToCollect)
.setSort(groupingSpec.getGroupSort())
.setQuery(query, rb.req)
.setDocSet(searcher)
.build()
);
}
CommandHandler commandHandler = secondPhaseBuilder.build();
commandHandler.execute();
TopGroupsResultTransformer serializer = new TopGroupsResultTransformer(rb);
rsp.add("secondPhase", commandHandler.processResult(result, serializer));
rb.setResult(result);
}
private void doProcessGroupedSearch(ResponseBuilder rb, QueryCommand cmd, QueryResult result) throws IOException, SyntaxError {
GroupingSpecification groupingSpec = rb.getGroupingSpec();
assert null != groupingSpec : "GroupingSpecification is null";
SolrQueryRequest req = rb.req;
SolrQueryResponse rsp = rb.rsp;
SolrParams params = req.getParams();
SolrIndexSearcher searcher = req.getSearcher();
int maxDocsPercentageToCache = params.getInt(GroupParams.GROUP_CACHE_PERCENTAGE, 0);
boolean cacheSecondPassSearch = maxDocsPercentageToCache >= 1 && maxDocsPercentageToCache <= 100;
Grouping.TotalCount defaultTotalCount = groupingSpec.isIncludeGroupCount() ?
Grouping.TotalCount.grouped : Grouping.TotalCount.ungrouped;
int limitDefault = cmd.getLen(); // this is normally from "rows"
Grouping grouping =
new Grouping(searcher, result, cmd, cacheSecondPassSearch, maxDocsPercentageToCache, groupingSpec.isMain());
grouping.setGroupSort(groupingSpec.getGroupSort())
.setWithinGroupSort(groupingSpec.getSortWithinGroup())
.setDefaultFormat(groupingSpec.getResponseFormat())
.setLimitDefault(limitDefault)
.setDefaultTotalCount(defaultTotalCount)
.setDocsPerGroupDefault(groupingSpec.getWithinGroupLimit())
.setGroupOffsetDefault(groupingSpec.getWithinGroupOffset())
.setGetGroupedDocSet(groupingSpec.isTruncateGroups());
if (groupingSpec.getFields() != null) {
for (String field : groupingSpec.getFields()) {
grouping.addFieldCommand(field, rb.req);
}
}
if (groupingSpec.getFunctions() != null) {
for (String groupByStr : groupingSpec.getFunctions()) {
grouping.addFunctionCommand(groupByStr, rb.req);
}
}
if (groupingSpec.getQueries() != null) {
for (String groupByStr : groupingSpec.getQueries()) {
grouping.addQueryCommand(groupByStr, rb.req);
}
}
if( rb.isNeedDocList() || rb.isDebug() ){
// we need a single list of the returned docs
cmd.setFlags(SolrIndexSearcher.GET_DOCLIST);
}
grouping.execute();
if (grouping.isSignalCacheWarning()) {
rsp.add(
"cacheWarning",
String.format(Locale.ROOT, "Cache limit of %d percent relative to maxdoc has exceeded. Please increase cache size or disable caching.", maxDocsPercentageToCache)
);
}
rb.setResult(result);
if (grouping.mainResult != null) {
ResultContext ctx = new BasicResultContext(rb, grouping.mainResult);
rsp.addResponse(ctx);
rsp.getToLog().add("hits", grouping.mainResult.matches());
} else if (!grouping.getCommands().isEmpty()) { // Can never be empty since grouping.execute() checks for this.
rsp.add("grouped", result.groupedResults);
rsp.getToLog().add("hits", grouping.getCommands().get(0).getMatches());
}
}
private void doProcessUngroupedSearch(ResponseBuilder rb, QueryCommand cmd, QueryResult result) throws IOException {
SolrQueryRequest req = rb.req;
SolrQueryResponse rsp = rb.rsp;
SolrIndexSearcher searcher = req.getSearcher();
searcher.search(result, cmd);
rb.setResult(result);
ResultContext ctx = new BasicResultContext(rb);
rsp.addResponse(ctx);
rsp.getToLog().add("hits", rb.getResults().docList.matches());
if ( ! rb.req.getParams().getBool(ShardParams.IS_SHARD,false) ) {
if (null != rb.getNextCursorMark()) {
rb.rsp.add(CursorMarkParams.CURSOR_MARK_NEXT,
rb.getNextCursorMark().getSerializedTotem());
}
}
if(rb.mergeFieldHandler != null) {
rb.mergeFieldHandler.handleMergeFields(rb, searcher);
} else {
doFieldSortValues(rb, searcher);
}
doPrefetch(rb);
}
/** /**
* Fake scorer for a single document * Fake scorer for a single document
* *

View File

@ -69,7 +69,7 @@ public final class FastCharStream implements CharStream {
int charsRead = // fill space in buffer int charsRead = // fill space in buffer
input.read(buffer, newPosition, buffer.length-newPosition); input.read(buffer, newPosition, buffer.length-newPosition);
if (charsRead == -1) if (charsRead == -1)
throw new IOException("read past eof"); throw READ_PAST_EOF;
else else
bufferLength += charsRead; bufferLength += charsRead;
} }
@ -80,6 +80,11 @@ public final class FastCharStream implements CharStream {
return readChar(); return readChar();
} }
/**
* This Exception is used as a signal rather than an exceptional state.
*/
private static final IOException READ_PAST_EOF = new IOException("read past eof");
@Override @Override
public final void backup(int amount) { public final void backup(int amount) {
bufferPosition -= amount; bufferPosition -= amount;

View File

@ -767,7 +767,7 @@ public class QueryParser extends SolrQueryParserBase implements QueryParserConst
} }
static private final class LookaheadSuccess extends java.lang.Error { } static private final class LookaheadSuccess extends java.lang.Error { }
final private LookaheadSuccess jj_ls = new LookaheadSuccess(); static final private LookaheadSuccess jj_ls = new LookaheadSuccess();
private boolean jj_scan_token(int kind) { private boolean jj_scan_token(int kind) {
if (jj_scanpos == jj_lastpos) { if (jj_scanpos == jj_lastpos) {
jj_la--; jj_la--;

View File

@ -250,11 +250,12 @@ public abstract class AbstractEnumField extends PrimitiveFieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); SortField result = getSortField(field, SortField.Type.INT, top, Integer.MIN_VALUE, Integer.MAX_VALUE);
final Object missingValue = Integer.MIN_VALUE; if (null == result.getMissingValue()) {
SortField sf = new SortField(field.getName(), SortField.Type.INT, top); // special case default behavior: assume missing values are "below" all enum values
sf.setMissingValue(missingValue); result.setMissingValue(Integer.MIN_VALUE);
return sf; }
return result;
} }
@Override @Override

View File

@ -190,20 +190,7 @@ public class DatePointField extends PointField implements DateValueFieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortField(field, SortField.Type.LONG, top, Long.MIN_VALUE, Long.MAX_VALUE);
Object missingValue = null;
boolean sortMissingLast = field.sortMissingLast();
boolean sortMissingFirst = field.sortMissingFirst();
if (sortMissingLast) {
missingValue = top ? Long.MIN_VALUE : Long.MAX_VALUE;
} else if (sortMissingFirst) {
missingValue = top ? Long.MAX_VALUE : Long.MIN_VALUE;
}
SortField sf = new SortField(field.getName(), SortField.Type.LONG, top);
sf.setMissingValue(missingValue);
return sf;
} }
@Override @Override

View File

@ -39,7 +39,7 @@ import org.apache.solr.util.DateMathParser;
import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.Shape;
/** /**
* A field for indexed dates and date ranges. It's mostly compatible with TrieDateField. It has the potential to allow * A field for indexed dates and date ranges. It's mostly compatible with DatePointField. It has the potential to allow
* efficient faceting, similar to facet.enum. * efficient faceting, similar to facet.enum.
* *
* @see NumberRangePrefixTreeStrategy * @see NumberRangePrefixTreeStrategy
@ -75,7 +75,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
if (shape instanceof UnitNRShape) { if (shape instanceof UnitNRShape) {
UnitNRShape unitShape = (UnitNRShape) shape; UnitNRShape unitShape = (UnitNRShape) shape;
if (unitShape.getLevel() == tree.getMaxLevels()) { if (unitShape.getLevel() == tree.getMaxLevels()) {
//fully precise date. We can be fully compatible with TrieDateField (incl. 'Z') //fully precise date. We can be fully compatible with DatePointField (incl. 'Z')
return shape.toString() + 'Z'; return shape.toString() + 'Z';
} }
} }

View File

@ -134,20 +134,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortField(field, SortField.Type.DOUBLE, top, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
Object missingValue = null;
boolean sortMissingLast = field.sortMissingLast();
boolean sortMissingFirst = field.sortMissingFirst();
if (sortMissingLast) {
missingValue = top ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
} else if (sortMissingFirst) {
missingValue = top ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
SortField sf = new SortField(field.getName(), SortField.Type.DOUBLE, top);
sf.setMissingValue(missingValue);
return sf;
} }
@Override @Override

View File

@ -48,6 +48,7 @@ import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedSetSortField;
import org.apache.lucene.search.SortedNumericSelector; import org.apache.lucene.search.SortedNumericSelector;
import org.apache.lucene.search.SortedSetSelector; import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermInSetQuery;
@ -69,7 +70,6 @@ import org.apache.solr.query.SolrRangeQuery;
import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser; import org.apache.solr.search.QParser;
import org.apache.solr.search.QueryUtils; import org.apache.solr.search.QueryUtils;
import org.apache.solr.search.Sorting;
import org.apache.solr.uninverting.UninvertingReader; import org.apache.solr.uninverting.UninvertingReader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -662,17 +662,76 @@ public abstract class FieldType extends FieldProperties {
* Returns the SortField instance that should be used to sort fields * Returns the SortField instance that should be used to sort fields
* of this type. * of this type.
* @see SchemaField#checkSortability * @see SchemaField#checkSortability
* @see #getSortField(SchemaField,SortField.Type,boolean,Object,Object)
*/ */
public abstract SortField getSortField(SchemaField field, boolean top); public abstract SortField getSortField(SchemaField field, boolean top);
/**
* <p>A Helper utility method for use by subclasses.</p>
* <p>This method deals with:</p>
* <ul>
* <li>{@link SchemaField#checkSortability}</li>
* <li>Creating a {@link SortField} on <code>field</code> with the specified
* <code>reverse</code> &amp; <code>sortType</code></li>
* <li>Setting the {@link SortField#setMissingValue} to <code>missingLow</code> or <code>missingHigh</code>
* as appropriate based on the value of <code>reverse</code> and the
* <code>sortMissingFirst</code> &amp; <code>sortMissingLast</code> properties of the
* <code>field</code></li>
* </ul>
*
* @param field The SchemaField to sort on. May use <code>sortMissingFirst</code> or <code>sortMissingLast</code> or neither.
* @param sortType The sort Type of the underlying values in the <code>field</code>
* @param reverse True if natural order of the <code>sortType</code> should be reversed
* @param missingLow The <code>missingValue</code> to be used if the other params indicate that docs w/o values should sort as "low" as possible.
* @param missingHigh The <code>missingValue</code> to be used if the other params indicate that docs w/o values should sort as "high" as possible.
* @see #getSortedSetSortField
*/
protected static SortField getSortField(SchemaField field, SortField.Type sortType, boolean reverse,
Object missingLow, Object missingHigh) {
field.checkSortability();
SortField sf = new SortField(field.getName(), sortType, reverse);
applySetMissingValue(field, sf, missingLow, missingHigh);
return sf;
}
/**
* Same as {@link #getSortField} but using {@link SortedSetSortField}
*/
protected static SortField getSortedSetSortField(SchemaField field, SortedSetSelector.Type selector,
boolean reverse, Object missingLow, Object missingHigh) {
field.checkSortability();
SortField sf = new SortedSetSortField(field.getName(), reverse, selector);
applySetMissingValue(field, sf, missingLow, missingHigh);
return sf;
}
/**
* @see #getSortField
* @see #getSortedSetSortField
*/
private static void applySetMissingValue(SchemaField field, SortField sortField,
Object missingLow, Object missingHigh) {
final boolean reverse = sortField.getReverse();
if (field.sortMissingLast()) {
sortField.setMissingValue(reverse ? missingLow : missingHigh);
} else if (field.sortMissingFirst()) {
sortField.setMissingValue(reverse ? missingHigh : missingLow);
}
}
/** /**
* Utility usable by subclasses when they want to get basic String sorting * Utility usable by subclasses when they want to get basic String sorting
* using common checks. * using common checks.
* @see SchemaField#checkSortability * @see SchemaField#checkSortability
*/ */
protected SortField getStringSort(SchemaField field, boolean reverse) { protected SortField getStringSort(SchemaField field, boolean reverse) {
field.checkSortability(); return getSortField(field, SortField.Type.STRING, reverse, SortField.STRING_FIRST, SortField.STRING_LAST);
return Sorting.getStringSortField(field.name, reverse, field.sortMissingLast(),field.sortMissingFirst());
} }
/** called to get the default value source (normally, from the /** called to get the default value source (normally, from the

View File

@ -134,20 +134,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortField(field, SortField.Type.FLOAT, top, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
Object missingValue = null;
boolean sortMissingLast = field.sortMissingLast();
boolean sortMissingFirst = field.sortMissingFirst();
if (sortMissingLast) {
missingValue = top ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
} else if (sortMissingFirst) {
missingValue = top ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
}
SortField sf = new SortField(field.getName(), SortField.Type.FLOAT, top);
sf.setMissingValue(missingValue);
return sf;
} }
@Override @Override

View File

@ -132,20 +132,7 @@ public class IntPointField extends PointField implements IntValueFieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortField(field, SortField.Type.INT, top, Integer.MIN_VALUE, Integer.MAX_VALUE);
Object missingValue = null;
boolean sortMissingLast = field.sortMissingLast();
boolean sortMissingFirst = field.sortMissingFirst();
if (sortMissingLast) {
missingValue = top ? Integer.MIN_VALUE : Integer.MAX_VALUE;
} else if (sortMissingFirst) {
missingValue = top ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
SortField sf = new SortField(field.getName(), SortField.Type.INT, top);
sf.setMissingValue(missingValue);
return sf;
} }
@Override @Override

View File

@ -131,20 +131,7 @@ public class LongPointField extends PointField implements LongValueFieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortField(field, SortField.Type.LONG, top, Long.MIN_VALUE, Long.MAX_VALUE);
Object missingValue = null;
boolean sortMissingLast = field.sortMissingLast();
boolean sortMissingFirst = field.sortMissingFirst();
if (sortMissingLast) {
missingValue = top ? Long.MIN_VALUE : Long.MAX_VALUE;
} else if (sortMissingFirst) {
missingValue = top ? Long.MAX_VALUE : Long.MIN_VALUE;
}
SortField sf = new SortField(field.getName(), SortField.Type.LONG, top);
sf.setMissingValue(missingValue);
return sf;
} }
@Override @Override

View File

@ -34,13 +34,13 @@ import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource; import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.util.AttributeFactory; import org.apache.lucene.util.AttributeFactory;
import org.apache.lucene.util.AttributeSource.State; import org.apache.lucene.util.AttributeSource.State;
import org.apache.lucene.util.AttributeSource; import org.apache.lucene.util.AttributeSource;
import org.apache.solr.analysis.SolrAnalyzer; import org.apache.solr.analysis.SolrAnalyzer;
import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser; import org.apache.solr.search.QParser;
import org.apache.solr.search.Sorting;
import org.apache.solr.uninverting.UninvertingReader.Type; import org.apache.solr.uninverting.UninvertingReader.Type;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -132,8 +132,8 @@ public class PreAnalyzedField extends TextField implements HasImplicitIndexAnaly
@Override @Override
public SortField getSortField(SchemaField field, boolean top) { public SortField getSortField(SchemaField field, boolean top) {
field.checkSortability(); return getSortedSetSortField(field, SortedSetSelector.Type.MIN, top,
return Sorting.getTextSortField(field.getName(), top, field.sortMissingLast(), field.sortMissingFirst()); SortField.STRING_FIRST, SortField.STRING_LAST);
} }
@Override @Override

View File

@ -32,7 +32,6 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.query.SolrRangeQuery; import org.apache.solr.query.SolrRangeQuery;
import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser; import org.apache.solr.search.QParser;
import org.apache.solr.search.Sorting;
import org.apache.solr.uninverting.UninvertingReader.Type; import org.apache.solr.uninverting.UninvertingReader.Type;
/** <code>TextField</code> is the basic type for configurable text analysis. /** <code>TextField</code> is the basic type for configurable text analysis.
@ -108,8 +107,8 @@ public class TextField extends FieldType {
@Override @Override
public SortField getSortField(SchemaField field, boolean reverse) { public SortField getSortField(SchemaField field, boolean reverse) {
/* :TODO: maybe warn if isTokenized(), but doesn't use LimitTokenCountFilter in its chain? */ /* :TODO: maybe warn if isTokenized(), but doesn't use LimitTokenCountFilter in its chain? */
field.checkSortability(); return getSortedSetSortField(field, SortedSetSelector.Type.MIN, reverse,
return Sorting.getTextSortField(field.getName(), reverse, field.sortMissingLast(), field.sortMissingFirst()); SortField.STRING_FIRST, SortField.STRING_LAST);
} }
@Override @Override

View File

@ -171,50 +171,14 @@ public class TrieField extends NumericFieldType {
switch (type) { switch (type) {
case INTEGER: case INTEGER:
if( sortMissingLast ) { return getSortField(field, SortField.Type.INT, top, Integer.MIN_VALUE, Integer.MAX_VALUE);
missingValue = top ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
else if( sortMissingFirst ) {
missingValue = top ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
sf = new SortField( field.getName(), SortField.Type.INT, top);
sf.setMissingValue(missingValue);
return sf;
case FLOAT: case FLOAT:
if( sortMissingLast ) { return getSortField(field, SortField.Type.FLOAT, top, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
missingValue = top ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
}
else if( sortMissingFirst ) {
missingValue = top ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
}
sf = new SortField( field.getName(), SortField.Type.FLOAT, top);
sf.setMissingValue(missingValue);
return sf;
case DATE: // fallthrough case DATE: // fallthrough
case LONG: case LONG:
if( sortMissingLast ) { return getSortField(field, SortField.Type.LONG, top, Long.MIN_VALUE, Long.MAX_VALUE);
missingValue = top ? Long.MIN_VALUE : Long.MAX_VALUE;
}
else if( sortMissingFirst ) {
missingValue = top ? Long.MAX_VALUE : Long.MIN_VALUE;
}
sf = new SortField( field.getName(), SortField.Type.LONG, top);
sf.setMissingValue(missingValue);
return sf;
case DOUBLE: case DOUBLE:
if( sortMissingLast ) { return getSortField(field, SortField.Type.DOUBLE, top, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
missingValue = top ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
}
else if( sortMissingFirst ) {
missingValue = top ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
sf = new SortField( field.getName(), SortField.Type.DOUBLE, top);
sf.setMissingValue(missingValue);
return sf;
default: default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + field.name); throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + field.name);
} }

View File

@ -25,6 +25,7 @@ import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField; import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.facet.AggValueSource; import org.apache.solr.search.facet.AggValueSource;
import org.apache.solr.search.function.FieldNameValueSource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -33,6 +34,7 @@ public class FunctionQParser extends QParser {
public static final int FLAG_CONSUME_DELIMITER = 0x01; // consume delimiter after parsing arg public static final int FLAG_CONSUME_DELIMITER = 0x01; // consume delimiter after parsing arg
public static final int FLAG_IS_AGG = 0x02; public static final int FLAG_IS_AGG = 0x02;
public static final int FLAG_USE_FIELDNAME_SOURCE = 0x04; // When a field name is encountered, use the placeholder FieldNameValueSource instead of resolving to a real ValueSource
public static final int FLAG_DEFAULT = FLAG_CONSUME_DELIMITER; public static final int FLAG_DEFAULT = FLAG_CONSUME_DELIMITER;
/** @lucene.internal */ /** @lucene.internal */
@ -373,11 +375,16 @@ public class FunctionQParser extends QParser {
valueSource = new BoolConstValueSource(true); valueSource = new BoolConstValueSource(true);
} else if ("false".equals(id)) { } else if ("false".equals(id)) {
valueSource = new BoolConstValueSource(false); valueSource = new BoolConstValueSource(false);
} else {
if ((flags & FLAG_USE_FIELDNAME_SOURCE) != 0) {
// Don't try to create a ValueSource for the field, just use a placeholder.
valueSource = new FieldNameValueSource(id);
} else { } else {
SchemaField f = req.getSchema().getField(id); SchemaField f = req.getSchema().getField(id);
valueSource = f.getType().getValueSource(f, this); valueSource = f.getType().getValueSource(f, this);
} }
} }
}
} }

View File

@ -16,13 +16,9 @@
*/ */
package org.apache.solr.search; package org.apache.solr.search;
import org.apache.lucene.search.Query;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequest;
import java.util.List;
/** /**
* Parse Solr's variant on the Lucene QueryParser syntax. * Parse Solr's variant on the Lucene QueryParser syntax.
* <br>Other parameters:<ul> * <br>Other parameters:<ul>
@ -42,53 +38,3 @@ public class LuceneQParserPlugin extends QParserPlugin {
} }
} }
@Deprecated
class OldLuceneQParser extends LuceneQParser {
String sortStr;
public OldLuceneQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
super(qstr, localParams, params, req);
}
@Override
public Query parse() throws SyntaxError {
// handle legacy "query;sort" syntax
if (getLocalParams() == null) {
String qstr = getString();
if (qstr == null || qstr.length() == 0)
return null;
sortStr = getParams().get(CommonParams.SORT);
if (sortStr == null) {
// sort may be legacy form, included in the query string
List<String> commands = StrUtils.splitSmart(qstr,';');
if (commands.size() == 2) {
qstr = commands.get(0);
sortStr = commands.get(1);
} else if (commands.size() == 1) {
// This is need to support the case where someone sends: "q=query;"
qstr = commands.get(0);
}
else if (commands.size() > 2) {
throw new SyntaxError("If you want to use multiple ';' in the query, use the 'sort' param.");
}
}
setString(qstr);
}
return super.parse();
}
@Override
public SortSpec getSortSpec(boolean useGlobal) throws SyntaxError {
SortSpec sort = super.getSortSpec(useGlobal);
if (sortStr != null && sortStr.length()>0 && sort.getSort()==null) {
SortSpec oldSort = SortSpecParsing.parseSortSpec(sortStr, getReq());
if( oldSort.getSort() != null ) {
sort.setSortAndFields(oldSort.getSort(), oldSort.getSchemaFields());
}
}
return sort;
}
}

View File

@ -16,15 +16,16 @@
*/ */
package org.apache.solr.search; package org.apache.solr.search;
import org.apache.solr.schema.FieldType;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
/** /**
* Extra lucene sorting utilities &amp; convenience methods * Extra lucene sorting utilities &amp; convenience methods
* *
* *
* * @deprecated custom {@link FieldType}s should use the helper methods in the base class. Other usage should leverage th underling lucene {@link SortField} classes directly.
*/ */
@Deprecated
public class Sorting { public class Sorting {
@ -37,14 +38,19 @@ public class Sorting {
* @param nullLast true if null should come last, regardless of sort order * @param nullLast true if null should come last, regardless of sort order
* @param nullFirst true if null should come first, regardless of sort order * @param nullFirst true if null should come first, regardless of sort order
* @return SortField * @return SortField
* @deprecated custom {@link FieldType}s should use {@link FieldType#getSortField}. Other usage should leverage th underling lucene {@link SortField} classes directly.
*/ */
@Deprecated
public static SortField getStringSortField(String fieldName, boolean reverse, boolean nullLast, boolean nullFirst) { public static SortField getStringSortField(String fieldName, boolean reverse, boolean nullLast, boolean nullFirst) {
SortField sortField = new SortField(fieldName, SortField.Type.STRING, reverse); SortField sortField = new SortField(fieldName, SortField.Type.STRING, reverse);
applyMissingFirstLast(sortField, reverse, nullLast, nullFirst); applyMissingFirstLast(sortField, reverse, nullLast, nullFirst);
return sortField; return sortField;
} }
/** Like {@link #getStringSortField}) except safe for tokenized fields */ /** Like {@link #getStringSortField}) except safe for tokenized fields
* @deprecated custom {@link FieldType}s should use {@link FieldType#getSortedSetSortField}. Other usage should leverage th underling lucene {@link SortedSetSortField} classes directly.
*/
@Deprecated
public static SortField getTextSortField(String fieldName, boolean reverse, boolean nullLast, boolean nullFirst) { public static SortField getTextSortField(String fieldName, boolean reverse, boolean nullLast, boolean nullFirst) {
SortField sortField = new SortedSetSortField(fieldName, reverse); SortField sortField = new SortedSetSortField(fieldName, reverse);
applyMissingFirstLast(sortField, reverse, nullLast, nullFirst); applyMissingFirstLast(sortField, reverse, nullLast, nullFirst);

View File

@ -1017,14 +1017,14 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
addParser("agg_min", new ValueSourceParser() { addParser("agg_min", new ValueSourceParser() {
@Override @Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError { public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new MinMaxAgg("min", fp.parseValueSource()); return new MinMaxAgg("min", fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
} }
}); });
addParser("agg_max", new ValueSourceParser() { addParser("agg_max", new ValueSourceParser() {
@Override @Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError { public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new MinMaxAgg("max", fp.parseValueSource()); return new MinMaxAgg("max", fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
} }
}); });

View File

@ -18,6 +18,7 @@ package org.apache.solr.search.facet;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.MultiDocValues; import org.apache.lucene.index.MultiDocValues;
@ -25,9 +26,12 @@ import org.apache.lucene.index.OrdinalMap;
import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.LongValues; import org.apache.lucene.util.LongValues;
import org.apache.solr.common.SolrException;
import org.apache.solr.schema.SchemaField; import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.StrFieldSource; import org.apache.solr.schema.StrFieldSource;
import org.apache.solr.search.function.FieldNameValueSource;
public class MinMaxAgg extends SimpleAggValueSource { public class MinMaxAgg extends SimpleAggValueSource {
final int minmax; // a multiplier to reverse the normal order of compare if this is max instead of min (i.e. max will be -1) final int minmax; // a multiplier to reverse the normal order of compare if this is max instead of min (i.e. max will be -1)
@ -41,28 +45,46 @@ public class MinMaxAgg extends SimpleAggValueSource {
public SlotAcc createSlotAcc(FacetContext fcontext, int numDocs, int numSlots) throws IOException { public SlotAcc createSlotAcc(FacetContext fcontext, int numDocs, int numSlots) throws IOException {
ValueSource vs = getArg(); ValueSource vs = getArg();
if (vs instanceof StrFieldSource) { SchemaField sf = null;
String field = ((StrFieldSource) vs).getField();
SchemaField sf = fcontext.qcontext.searcher().getSchema().getField(field); if (vs instanceof FieldNameValueSource) {
String field = ((FieldNameValueSource)vs).getFieldName();
sf = fcontext.qcontext.searcher().getSchema().getField(field);
if (sf.multiValued() || sf.getType().multiValuedFieldCache()) { if (sf.multiValued() || sf.getType().multiValuedFieldCache()) {
if (sf.hasDocValues()) { vs = null;
// dv throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "min/max aggregations can't be used on multi-valued field " + field);
} else { } else {
// uif vs = sf.getType().getValueSource(sf, null);
} }
} else { }
if (vs instanceof StrFieldSource) {
return new SingleValuedOrdAcc(fcontext, sf, numSlots); return new SingleValuedOrdAcc(fcontext, sf, numSlots);
} }
// Since functions don't currently have types, we rely on the type of the field
if (sf != null && sf.getType().getNumberType() != null) {
switch (sf.getType().getNumberType()) {
case FLOAT:
case DOUBLE:
return new DFuncAcc(vs, fcontext, numSlots);
case INTEGER:
case LONG:
return new LFuncAcc(vs, fcontext, numSlots);
case DATE:
return new DateFuncAcc(vs, fcontext, numSlots);
}
} }
// numeric functions // numeric functions
return new ValSlotAcc(vs, fcontext, numSlots); return new DFuncAcc(vs, fcontext, numSlots);
} }
@Override @Override
public FacetMerger createFacetMerger(Object prototype) { public FacetMerger createFacetMerger(Object prototype) {
if (prototype instanceof Number) if (prototype instanceof Double)
return new NumericMerger(); return new NumericMerger(); // still use NumericMerger to handle NaN?
else if (prototype instanceof Comparable) { else if (prototype instanceof Comparable) {
return new ComparableMerger(); return new ComparableMerger();
} else { } else {
@ -114,8 +136,8 @@ public class MinMaxAgg extends SimpleAggValueSource {
} }
} }
class ValSlotAcc extends DoubleFuncSlotAcc { class DFuncAcc extends DoubleFuncSlotAcc {
public ValSlotAcc(ValueSource values, FacetContext fcontext, int numSlots) { public DFuncAcc(ValueSource values, FacetContext fcontext, int numSlots) {
super(values, fcontext, numSlots, Double.NaN); super(values, fcontext, numSlots, Double.NaN);
} }
@ -129,6 +151,101 @@ public class MinMaxAgg extends SimpleAggValueSource {
result[slotNum] = val; result[slotNum] = val;
} }
} }
@Override
public Object getValue(int slot) {
double val = result[slot];
if (Double.isNaN(val)) {
return null;
} else {
return val;
}
}
}
class LFuncAcc extends LongFuncSlotAcc {
FixedBitSet exists;
public LFuncAcc(ValueSource values, FacetContext fcontext, int numSlots) {
super(values, fcontext, numSlots, 0);
exists = new FixedBitSet(numSlots);
}
@Override
public void collect(int doc, int slotNum) throws IOException {
long val = values.longVal(doc);
if (val == 0 && !values.exists(doc)) return; // depend on fact that non existing values return 0 for func query
long currVal = result[slotNum];
if (currVal == 0 && !exists.get(slotNum)) {
exists.set(slotNum);
result[slotNum] = val;
} else if (Long.compare(val, currVal) * minmax < 0) {
result[slotNum] = val;
}
}
@Override
public Object getValue(int slot) {
long val = result[slot];
if (val == 0 && !exists.get(slot)) {
return null;
} else {
return val;
}
}
@Override
public void resize(Resizer resizer) {
super.resize(resizer);
exists = resizer.resize(exists);
}
@Override
public int compare(int slotA, int slotB) {
long a = result[slotA];
long b = result[slotB];
boolean ea = a != 0 || exists.get(slotA);
boolean eb = b != 0 || exists.get(slotB);
if (ea != eb) {
if (ea) return 1; // a exists and b doesn't TODO: we need context to be able to sort missing last! SOLR-10618
if (eb) return -1; // b exists and a is missing
}
return Long.compare(a, b);
}
@Override
public void reset() {
super.reset();
exists.clear(0, exists.length());
}
}
class DateFuncAcc extends LongFuncSlotAcc {
private static final long MISSING = Long.MIN_VALUE;
public DateFuncAcc(ValueSource values, FacetContext fcontext, int numSlots) {
super(values, fcontext, numSlots, MISSING);
}
@Override
public void collect(int doc, int slotNum) throws IOException {
long val = values.longVal(doc);
if (val == 0 && !values.exists(doc)) return; // depend on fact that non existing values return 0 for func query
long currVal = result[slotNum];
if (Long.compare(val, currVal) * minmax < 0 || currVal == MISSING) {
result[slotNum] = val;
}
}
// let compare be the default for now (since we can't yet correctly handle sortMissingLast
@Override
public Object getValue(int slot) {
return result[slot] == MISSING ? null : new Date(result[slot]);
}
} }

View File

@ -16,14 +16,6 @@
*/ */
package org.apache.solr.search.facet; package org.apache.solr.search.facet;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.SolrIndexSearcher;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array; import java.lang.reflect.Array;
@ -32,6 +24,16 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.FixedBitSet;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.SolrIndexSearcher;
/** /**
* Accumulates statistics separated by a slot number. * Accumulates statistics separated by a slot number.
* There is a separate statistic per slot. The slot is usually an ordinal into a set of values, e.g. tracking a count * There is a separate statistic per slot. The slot is usually an ordinal into a set of values, e.g. tracking a count
@ -140,6 +142,38 @@ public abstract class SlotAcc implements Closeable {
return values; return values;
} }
public long[] resize(long[] old, long defaultValue) {
long[] values = new long[getNewSize()];
if (defaultValue != 0) {
Arrays.fill(values, 0, values.length, defaultValue);
}
for (int i = 0; i < old.length; i++) {
long val = old[i];
if (val != defaultValue) {
int newSlot = getNewSlot(i);
if (newSlot >= 0) {
values[newSlot] = val;
}
}
}
return values;
}
public FixedBitSet resize(FixedBitSet old) {
FixedBitSet values = new FixedBitSet(getNewSize());
int oldSize = old.length();
for(int oldSlot = 0;;) {
oldSlot = values.nextSetBit(oldSlot);
if (oldSlot == DocIdSetIterator.NO_MORE_DOCS) break;
int newSlot = getNewSlot(oldSlot);
values.set(newSlot);
if (++oldSlot >= oldSize) break;
}
return values;
}
public <T> T[] resize(T[] old, T defaultValue) { public <T> T[] resize(T[] old, T defaultValue) {
T[] values = (T[]) Array.newInstance(old.getClass().getComponentType(), getNewSize()); T[] values = (T[]) Array.newInstance(old.getClass().getComponentType(), getNewSize());
if (defaultValue != null) { if (defaultValue != null) {
@ -222,6 +256,40 @@ abstract class DoubleFuncSlotAcc extends FuncSlotAcc {
} }
} }
abstract class LongFuncSlotAcc extends FuncSlotAcc {
long[] result;
long initialValue;
public LongFuncSlotAcc(ValueSource values, FacetContext fcontext, int numSlots, long initialValue) {
super(values, fcontext, numSlots);
this.initialValue = initialValue;
result = new long[numSlots];
if (initialValue != 0) {
reset();
}
}
@Override
public int compare(int slotA, int slotB) {
return Long.compare(result[slotA], result[slotB]);
}
@Override
public Object getValue(int slot) {
return result[slot];
}
@Override
public void reset() {
Arrays.fill(result, initialValue);
}
@Override
public void resize(Resizer resizer) {
result = resizer.resize(result, initialValue);
}
}
abstract class IntSlotAcc extends SlotAcc { abstract class IntSlotAcc extends SlotAcc {
int[] result; // use LongArray32 int[] result; // use LongArray32
int initialValue; int initialValue;

View File

@ -0,0 +1,60 @@
/*
* 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.search.function;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
/** Placeholder value source.
* @lucene.internal */
public class FieldNameValueSource extends ValueSource {
private String fieldName;
public FieldNameValueSource(String fieldName) {
this.fieldName = fieldName;
}
public String getFieldName() {
return fieldName;
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
throw new UnsupportedOperationException("FieldNameValueSource should not be directly used: " + this);
}
@Override
public boolean equals(Object o) {
return o instanceof FieldNameValueSource && fieldName.equals(((FieldNameValueSource)o).getFieldName());
}
@Override
public int hashCode() {
return fieldName.hashCode();
}
@Override
public String description() {
return "FIELDNAME(" + fieldName + ")";
}
}

View File

@ -35,7 +35,7 @@ import static org.apache.solr.update.processor.FieldValueMutatingUpdateProcessor
* <p>For example, with the configuration listed below any documents * <p>For example, with the configuration listed below any documents
* containing String values (such as "<code>abcdef</code>" or * containing String values (such as "<code>abcdef</code>" or
* "<code>xyz</code>") in a field declared in the schema using * "<code>xyz</code>") in a field declared in the schema using
* <code>TrieIntField</code> or <code>TrieLongField</code> * <code>IntPointField</code> or <code>LongPointField</code>
* would have those Strings replaced with the length of those fields as an * would have those Strings replaced with the length of those fields as an
* Integer * Integer
* (ie: <code>6</code> and <code>3</code> respectively) * (ie: <code>6</code> and <code>3</code> respectively)
@ -43,8 +43,8 @@ import static org.apache.solr.update.processor.FieldValueMutatingUpdateProcessor
* <pre class="prettyprint"> * <pre class="prettyprint">
* &lt;processor class="solr.FieldLengthUpdateProcessorFactory"&gt; * &lt;processor class="solr.FieldLengthUpdateProcessorFactory"&gt;
* &lt;arr name="typeClass"&gt; * &lt;arr name="typeClass"&gt;
* &lt;str&gt;solr.TrieIntField&lt;/str&gt; * &lt;str&gt;solr.IntPointField&lt;/str&gt;
* &lt;str&gt;solr.TrieLongField&lt;/str&gt; * &lt;str&gt;solr.LongPointField&lt;/str&gt;
* &lt;/arr&gt; * &lt;/arr&gt;
* &lt;/processor&gt;</pre> * &lt;/processor&gt;</pre>
*/ */

View File

@ -79,7 +79,7 @@ import static org.apache.solr.update.processor.FieldMutatingUpdateProcessor.SELE
* In the ExampleFieldMutatingUpdateProcessorFactory configured below, * In the ExampleFieldMutatingUpdateProcessorFactory configured below,
* fields will be mutated if the name starts with "foo" <i>or</i> "bar"; * fields will be mutated if the name starts with "foo" <i>or</i> "bar";
* <b>unless</b> the field name contains the substring "SKIP" <i>or</i> * <b>unless</b> the field name contains the substring "SKIP" <i>or</i>
* the fieldType is (or subclasses) TrieDateField. Meaning a field named * the fieldType is (or subclasses) DatePointField. Meaning a field named
* "foo_SKIP" is guaranteed not to be selected, but a field named "bar_smith" * "foo_SKIP" is guaranteed not to be selected, but a field named "bar_smith"
* that uses StrField will be selected. * that uses StrField will be selected.
* </p> * </p>
@ -92,7 +92,7 @@ import static org.apache.solr.update.processor.FieldMutatingUpdateProcessor.SELE
* &lt;str name="fieldRegex"&gt;.*SKIP.*&lt;/str&gt; * &lt;str name="fieldRegex"&gt;.*SKIP.*&lt;/str&gt;
* &lt;/lst&gt; * &lt;/lst&gt;
* &lt;lst name="exclude"&gt; * &lt;lst name="exclude"&gt;
* &lt;str name="typeClass"&gt;solr.TrieDateField&lt;/str&gt; * &lt;str name="typeClass"&gt;solr.DatePointField&lt;/str&gt;
* &lt;/lst&gt; * &lt;/lst&gt;
* &lt;/processor&gt;</pre> * &lt;/processor&gt;</pre>
* *

View File

@ -47,8 +47,8 @@ import org.slf4j.LoggerFactory;
* </p> * </p>
* <p> * <p>
* The default selection behavior is to mutate both those fields that don't match * The default selection behavior is to mutate both those fields that don't match
* a schema field, as well as those fields that match a schema field with a field * a schema field, as well as those fields that match a schema field with a date
* type that uses class solr.TrieDateField. * field type.
* </p> * </p>
* <p> * <p>
* If all values are parseable as dates (or are already Date), then the field will * If all values are parseable as dates (or are already Date), then the field will

View File

@ -38,8 +38,8 @@ import java.util.Locale;
* </p> * </p>
* <p> * <p>
* The default selection behavior is to mutate both those fields that don't match * The default selection behavior is to mutate both those fields that don't match
* a schema field, as well as those fields that match a schema field with a field * a schema field, as well as those fields that match a schema field with a double
* type that uses class solr.TrieDoubleField. * field type.
* </p> * </p>
* <p> * <p>
* If all values are parseable as double (or are already Double), then the field * If all values are parseable as double (or are already Double), then the field

View File

@ -38,8 +38,8 @@ import java.util.Locale;
* </p> * </p>
* <p> * <p>
* The default selection behavior is to mutate both those fields that don't match * The default selection behavior is to mutate both those fields that don't match
* a schema field, as well as those fields that match a schema field with a field * a schema field, as well as those fields that match a schema field with a float
* type that uses class solr.TrieFloatField. * field type.
* </p> * </p>
* <p> * <p>
* If all values are parseable as float (or are already Float), then the field * If all values are parseable as float (or are already Float), then the field

View File

@ -35,8 +35,8 @@ import java.util.Locale;
* </p> * </p>
* <p> * <p>
* The default selection behavior is to mutate both those fields that don't match * The default selection behavior is to mutate both those fields that don't match
* a schema field, as well as those fields that match a schema field with a field * a schema field, as well as those fields that match a schema field with an int
* type that uses class solr.TrieIntField. * field type.
* </p> * </p>
* <p> * <p>
* If all values are parseable as int (or are already Integer), then the field * If all values are parseable as int (or are already Integer), then the field

View File

@ -35,8 +35,8 @@ import java.util.Locale;
* </p> * </p>
* <p> * <p>
* The default selection behavior is to mutate both those fields that don't match * The default selection behavior is to mutate both those fields that don't match
* a schema field, as well as those fields that match a schema field with a field * a schema field, as well as those fields that match a schema field with a long
* type that uses class solr.TrieLongField. * field type.
* </p> * </p>
* <p> * <p>
* If all values are parseable as long (or are already Long), then the field * If all values are parseable as long (or are already Long), then the field

View File

@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* TODO
*/
package org.apache.solr.util.configuration.providers;

View File

@ -18,12 +18,22 @@
<schema name="tiny" version="1.1"> <schema name="tiny" version="1.1">
<field name="id" type="string" indexed="true" stored="true" required="true"/> <field name="id" type="string" indexed="true" stored="true" required="true"/>
<field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/> <field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/>
<!-- Test EnumField and EnumFieldType --> <!-- Test EnumField and EnumFieldType -->
<field name="severity" type="severityType" indexed="${solr.tests.EnumFieldTest.indexed}" stored="true" multiValued="false" docValues="${solr.tests.numeric.dv}"/> <field name="severity" type="severityType" indexed="${solr.tests.EnumFieldTest.indexed}" stored="true" multiValued="false" docValues="${solr.tests.numeric.dv}"/>
<!-- NOTE: because these test sortMissingLast/sortMissingFirst, we force indexed="true" so we don't get
random errors on schema init about inconsistent properties -->
<field name="severity_missingLast" type="severityType" indexed="true" stored="true" multiValued="false" docValues="${solr.tests.numeric.dv}" sortMissingLast="true"/>
<field name="severity_missingFirst" type="severityType" indexed="true" stored="true" multiValued="false" docValues="${solr.tests.numeric.dv}" sortMissingFirst="true"/>
<field name="severity_mv" type="severityType" indexed="${solr.tests.EnumFieldTest.indexed}" stored="true" multiValued="true" docValues="${solr.tests.numeric.dv}"/> <field name="severity_mv" type="severityType" indexed="${solr.tests.EnumFieldTest.indexed}" stored="true" multiValued="true" docValues="${solr.tests.numeric.dv}"/>
<field name="text" type="text" indexed="true" stored="true" multiValued="true"/> <field name="text" type="text" indexed="true" stored="true" multiValued="true"/>
<uniqueKey>id</uniqueKey> <uniqueKey>id</uniqueKey>
<copyField source="severity" dest="severity_missingLast" />
<copyField source="severity" dest="severity_missingFirst" />
<fieldType name="text" class="solr.TextField"> <fieldType name="text" class="solr.TextField">
<analyzer> <analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/> <tokenizer class="solr.WhitespaceTokenizerFactory"/>

View File

@ -489,9 +489,6 @@
<queryParser name="foo" class="FooQParserPlugin"/> <queryParser name="foo" class="FooQParserPlugin"/>
<!-- deprecated parser, delete once class is deleted in Solr 8.0 -->
<queryParser name="lucenePlusSort" class="solr.OldLuceneQParserPlugin"/>
<updateRequestProcessorChain name="dedupe"> <updateRequestProcessorChain name="dedupe">
<processor class="org.apache.solr.update.processor.SignatureUpdateProcessorFactory"> <processor class="org.apache.solr.update.processor.SignatureUpdateProcessorFactory">
<bool name="enabled">false</bool> <bool name="enabled">false</bool>

View File

@ -869,7 +869,7 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 {
// testing everything from query level is hard because // testing everything from query level is hard because
// time marches on ... and there is no easy way to reach into the // time marches on ... and there is no easy way to reach into the
// bowels of TrieDateField and muck with the definition of "now" // bowels of DatePointField and muck with the definition of "now"
// ... // ...
// BUT: we can test that crazy combinations of "NOW" all work correctly, // BUT: we can test that crazy combinations of "NOW" all work correctly,
// assuming the test doesn't take too long to run... // assuming the test doesn't take too long to run...

View File

@ -43,7 +43,6 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
SolrQueryRequest req = null; SolrQueryRequest req = null;
Map<String,String> args = new HashMap<>(); Map<String,String> args = new HashMap<>();
lrf.args.put(CommonParams.VERSION,"2.2"); lrf.args.put(CommonParams.VERSION,"2.2");
lrf.args.put("defType","lucenePlusSort");
// compact the index, keep things from getting out of hand // compact the index, keep things from getting out of hand
@ -215,16 +214,16 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"*[count(//doc)=0]" ,"*[count(//doc)=0]"
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("defType","lucenePlusSort"); args.put("sort","val_s1 asc");
req = new LocalSolrQueryRequest(h.getCore(), "val_s:[a TO z];val_s1 asc", req = new LocalSolrQueryRequest(h.getCore(), "val_s:[a TO z]",
"/select", 0, 0 , args); "/select", 0, 0 , args);
assertQ(req assertQ(req
,"//*[@numFound='3'] " ,"//*[@numFound='3'] "
,"*[count(//doc)=0]" ,"*[count(//doc)=0]"
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("defType","lucenePlusSort"); args.put("sort","val_s1 desc");
req = new LocalSolrQueryRequest(h.getCore(), "val_s:[a TO z];val_s1 desc", req = new LocalSolrQueryRequest(h.getCore(), "val_s:[a TO z]",
"/select", 0, 0 , args); "/select", 0, 0 , args);
assertQ(req assertQ(req
,"//*[@numFound='3'] " ,"//*[@numFound='3'] "
@ -518,11 +517,11 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"//@numFound[.='1'] " ,"//@numFound[.='1'] "
,"//int[.='-2147483648']" ,"//int[.='-2147483648']"
); );
assertQ(req("id:44;num_i1 asc;") assertQ(req("q", "id:44", "sort","num_i1 asc")
,"//doc[1]/int[.='-2147483648'] " ,"//doc[1]/int[.='-2147483648'] "
,"//doc[last()]/int[.='2147483647']" ,"//doc[last()]/int[.='2147483647']"
); );
assertQ(req("id:44;num_i1 desc;") assertQ(req("q","id:44","sort","num_i1 desc")
,"//doc[1]/int[.='2147483647'] " ,"//doc[1]/int[.='2147483647'] "
,"//doc[last()]/int[.='-2147483648']" ,"//doc[last()]/int[.='-2147483648']"
); );
@ -561,11 +560,11 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"//@numFound[.='1'] " ,"//@numFound[.='1'] "
,"//long[.='-9223372036854775808']" ,"//long[.='-9223372036854775808']"
); );
assertQ(req("id:44;num_l1 asc;") assertQ(req("q","id:44","sort","num_l1 asc")
,"//doc[1]/long[.='-9223372036854775808'] " ,"//doc[1]/long[.='-9223372036854775808'] "
,"//doc[last()]/long[.='9223372036854775807']" ,"//doc[last()]/long[.='9223372036854775807']"
); );
assertQ(req("id:44;num_l1 desc;") assertQ(req("q","id:44", "sort", "num_l1 desc")
,"//doc[1]/long[.='9223372036854775807'] " ,"//doc[1]/long[.='9223372036854775807'] "
,"//doc[last()]/long[.='-9223372036854775808']" ,"//doc[last()]/long[.='-9223372036854775808']"
); );
@ -611,11 +610,11 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
assertQ(req("num_f1:\"-1e20\"") assertQ(req("num_f1:\"-1e20\"")
,"//@numFound[.='1']" ,"//@numFound[.='1']"
); );
assertQ(req("id:44;num_f1 asc;") assertQ(req("q", "id:44", "sort", "num_f1 asc")
,"//doc[1]/float[.='-Infinity'] " ,"//doc[1]/float[.='-Infinity'] "
,"//doc[last()]/float[.='NaN']" ,"//doc[last()]/float[.='NaN']"
); );
assertQ(req("id:44;num_f1 desc;") assertQ(req("q", "id:44", "sort","num_f1 desc")
,"//doc[1]/float[.='NaN'] " ,"//doc[1]/float[.='NaN'] "
,"//doc[last()]/float[.='-Infinity']" ,"//doc[last()]/float[.='-Infinity']"
); );
@ -663,11 +662,11 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
assertQ(req("num_d1:\"1e-100\"") assertQ(req("num_d1:\"1e-100\"")
,"//@numFound[.='1']" ,"//@numFound[.='1']"
); );
assertQ(req("id:44;num_d1 asc;") assertQ(req("q", "id:44", "sort", "num_d1 asc")
,"//doc[1]/double[.='-Infinity'] " ,"//doc[1]/double[.='-Infinity'] "
,"//doc[last()]/double[.='NaN']" ,"//doc[last()]/double[.='NaN']"
); );
assertQ(req("id:44;num_d1 desc;") assertQ(req("q","id:44","sort","num_d1 desc")
,"//doc[1]/double[.='NaN'] " ,"//doc[1]/double[.='NaN'] "
,"//doc[last()]/double[.='-Infinity']" ,"//doc[last()]/double[.='-Infinity']"
); );
@ -693,27 +692,27 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
,"*[count(//doc)=6]" ,"*[count(//doc)=6]"
); );
assertQ(req("id:44; a_i1 asc,b_i1 desc") assertQ(req("q","id:44", "sort", "a_i1 asc,b_i1 desc")
,"*[count(//doc)=6] " ,"*[count(//doc)=6] "
,"//doc[3]/int[.='100'] " ,"//doc[3]/int[.='100'] "
,"//doc[4]/int[.='50']" ,"//doc[4]/int[.='50']"
); );
assertQ(req("id:44;a_i1 asc , b_i1 asc;") assertQ(req("q","id:44", "sort", "a_i1 asc , b_i1 asc")
,"*[count(//doc)=6] " ,"*[count(//doc)=6] "
,"//doc[3]/int[.='50'] " ,"//doc[3]/int[.='50'] "
,"//doc[4]/int[.='100']" ,"//doc[4]/int[.='100']"
); );
assertQ(req("id:44;a_i1 asc;") assertQ(req("q", "id:44", "sort", "a_i1 asc")
,"*[count(//doc)=6] " ,"*[count(//doc)=6] "
,"//doc[1]/int[.='-1'] " ,"//doc[1]/int[.='-1'] "
,"//doc[last()]/int[.='15']" ,"//doc[last()]/int[.='15']"
); );
assertQ(req("id:44;a_i1 asc , score top;") assertQ(req("q","id:44","sort","a_i1 asc , score top")
,"*[count(//doc)=6] " ,"*[count(//doc)=6] "
,"//doc[1]/int[.='-1'] " ,"//doc[1]/int[.='-1'] "
,"//doc[last()]/int[.='15']" ,"//doc[last()]/int[.='15']"
); );
assertQ(req("id:44; score top , a_i1 top, b_i1 bottom ;") assertQ(req("q","id:44","sort","score top , a_i1 top, b_i1 bottom ")
,"*[count(//doc)=6] " ,"*[count(//doc)=6] "
,"//doc[last()]/int[.='-1'] " ,"//doc[last()]/int[.='-1'] "
,"//doc[1]/int[.='15'] " ,"//doc[1]/int[.='15'] "
@ -736,36 +735,36 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
assertQ(req("id_i:[1000 TO 1010]") assertQ(req("id_i:[1000 TO 1010]")
,"*[count(//doc)=7]" ,"*[count(//doc)=7]"
); );
assertQ(req("id_i:[1000 TO 1010]; b_i1 asc") assertQ(req("q","id_i:[1000 TO 1010]","sort","b_i1 asc")
,"*[count(//doc)=7] " ,"*[count(//doc)=7] "
,"//doc[1]/int[.='50'] " ,"//doc[1]/int[.='50'] "
,"//doc[2]/int[.='100']" ,"//doc[2]/int[.='100']"
); );
assertQ(req("id_i:[1000 TO 1010]; b_i1 desc") assertQ(req("q","id_i:[1000 TO 1010]","sort"," b_i1 desc")
,"*[count(//doc)=7] " ,"*[count(//doc)=7] "
,"//doc[1]/int[.='100'] " ,"//doc[1]/int[.='100'] "
,"//doc[2]/int[.='50']" ,"//doc[2]/int[.='50']"
); );
assertQ(req("id_i:[1000 TO 1010]; a_i1 asc,b_i1 desc") assertQ(req("q","id_i:[1000 TO 1010]","sort"," a_i1 asc,b_i1 desc")
,"*[count(//doc)=7] " ,"*[count(//doc)=7] "
,"//doc[3]/int[@name='b_i1' and .='100'] " ,"//doc[3]/int[@name='b_i1' and .='100'] "
,"//doc[4]/int[@name='b_i1' and .='50'] " ,"//doc[4]/int[@name='b_i1' and .='50'] "
,"//doc[5]/arr[@name='id_i' and .='1000']" ,"//doc[5]/arr[@name='id_i' and .='1000']"
); );
assertQ(req("id_i:[1000 TO 1010]; a_i1 asc,b_i1 asc") assertQ(req("q","id_i:[1000 TO 1010]","sort"," a_i1 asc,b_i1 asc")
,"*[count(//doc)=7] " ,"*[count(//doc)=7] "
,"//doc[3]/int[@name='b_i1' and .='50'] " ,"//doc[3]/int[@name='b_i1' and .='50'] "
,"//doc[4]/int[@name='b_i1' and .='100'] " ,"//doc[4]/int[@name='b_i1' and .='100'] "
,"//doc[5]/arr[@name='id_i' and .='1000']" ,"//doc[5]/arr[@name='id_i' and .='1000']"
); );
// nullfirst tests // nullfirst tests
assertQ(req("id_i:[1000 TO 1002]; nullfirst asc") assertQ(req("q","id_i:[1000 TO 1002]","sort"," nullfirst asc")
,"*[count(//doc)=3] " ,"*[count(//doc)=3] "
,"//doc[1]/arr[@name='id_i' and .='1002']" ,"//doc[1]/arr[@name='id_i' and .='1002']"
,"//doc[2]/arr[@name='id_i' and .='1001'] " ,"//doc[2]/arr[@name='id_i' and .='1001'] "
,"//doc[3]/arr[@name='id_i' and .='1000']" ,"//doc[3]/arr[@name='id_i' and .='1000']"
); );
assertQ(req("id_i:[1000 TO 1002]; nullfirst desc") assertQ(req("q","id_i:[1000 TO 1002]","sort"," nullfirst desc")
,"*[count(//doc)=3] " ,"*[count(//doc)=3] "
,"//doc[1]/arr[@name='id_i' and .='1002']" ,"//doc[1]/arr[@name='id_i' and .='1002']"
,"//doc[2]/arr[@name='id_i' and .='1000'] " ,"//doc[2]/arr[@name='id_i' and .='1000'] "
@ -779,16 +778,16 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
// Sort parsing exception tests. (SOLR-6, SOLR-99) // Sort parsing exception tests. (SOLR-6, SOLR-99)
assertQEx( "can not sort unindexed fields", assertQEx( "can not sort unindexed fields",
req( "id_i:1000; shouldbeunindexed asc" ), 400 ); req( "q","id_i:1000", "sort", "shouldbeunindexed asc" ), 400 );
assertQEx( "invalid query format", assertQEx( "invalid query format",
req( "id_i:1000; nullfirst" ), 400 ); req( "q","id_i:1000", "sort", "nullfirst" ), 400 );
assertQEx( "unknown sort field", assertQEx( "unknown sort field",
req( "id_i:1000; abcde12345 asc" ), 400 ); req( "q","id_i:1000", "sort", "abcde12345 asc" ), 400 );
assertQEx( "unknown sort order", assertQEx( "unknown sort order",
req( "id_i:1000; nullfirst aaa" ), 400 ); req( "q","id_i:1000", "sort", "nullfirst aaa" ), 400 );
resetExceptionIgnores(); resetExceptionIgnores();
@ -1166,32 +1165,31 @@ public class ConvertedLegacyTest extends SolrTestCaseJ4 {
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("fl","score "); args.put("fl","score ");
args.put("defType","lucenePlusSort"); args.put("sort","id desc");
req = new LocalSolrQueryRequest(h.getCore(), "id:44;id desc;", req = new LocalSolrQueryRequest(h.getCore(), "id:44",
"/select", 0, 10, args); "/select", 0, 10, args);
assertQ(req assertQ(req
,"//result[@maxScore>0]" ,"//result[@maxScore>0]"
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("fl","score "); args.put("fl","score ");
args.put("defType","lucenePlusSort"); req = new LocalSolrQueryRequest(h.getCore(), "id:44",
req = new LocalSolrQueryRequest(h.getCore(), "id:44;",
"/select", 0, 10, args); "/select", 0, 10, args);
assertQ(req assertQ(req
,"//@maxScore = //doc/float[@name='score']" ,"//@maxScore = //doc/float[@name='score']"
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("fl","score "); args.put("fl","score ");
args.put("defType","lucenePlusSort"); args.put("sort","id desc");
req = new LocalSolrQueryRequest(h.getCore(), "id:44;id desc;", req = new LocalSolrQueryRequest(h.getCore(), "id:44",
"/select", 0, 10, args); "/select", 0, 10, args);
assertQ(req assertQ(req
,"//@maxScore = //doc/float[@name='score']" ,"//@maxScore = //doc/float[@name='score']"
); );
args = new HashMap<>(); args = new HashMap<>();
args.put("fl","*,score"); args.put("fl","*,score");
args.put("defType","lucenePlusSort"); args.put("sort","id desc");
req = new LocalSolrQueryRequest(h.getCore(), "id:44;id desc;", req = new LocalSolrQueryRequest(h.getCore(), "id:44",
"/select", 0, 0 , args); "/select", 0, 0 , args);
assertQ(req assertQ(req
,"//result[@maxScore>0]" ,"//result[@maxScore>0]"

View File

@ -30,10 +30,10 @@ public class TestDistributedMissingSort extends BaseDistributedSearchTestCase {
schemaString = "schema-distributed-missing-sort.xml"; schemaString = "schema-distributed-missing-sort.xml";
} }
String sint1_ml = "one_i1_ml"; // TrieIntField, sortMissingLast=true, multiValued=false String sint1_ml = "one_i1_ml"; // int field, sortMissingLast=true, multiValued=false
String sint1_mf = "two_i1_mf"; // TrieIntField, sortMissingFirst=true, multiValued=false String sint1_mf = "two_i1_mf"; // int field, sortMissingFirst=true, multiValued=false
String long1_ml = "three_l1_ml"; // TrieLongField, sortMissingLast=true, multiValued=false String long1_ml = "three_l1_ml"; // long field, sortMissingLast=true, multiValued=false
String long1_mf = "four_l1_mf"; // TrieLongField, sortMissingFirst=true, multiValued=false String long1_mf = "four_l1_mf"; // long field, sortMissingFirst=true, multiValued=false
String string1_ml = "five_s1_ml"; // StringField, sortMissingLast=true, multiValued=false String string1_ml = "five_s1_ml"; // StringField, sortMissingLast=true, multiValued=false
String string1_mf = "six_s1_mf"; // StringField, sortMissingFirst=true, multiValued=false String string1_mf = "six_s1_mf"; // StringField, sortMissingFirst=true, multiValued=false

View File

@ -239,6 +239,7 @@ public class CdcrBootstrapTest extends SolrTestCaseJ4 {
} }
} }
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-11278")
public void testBootstrapWithContinousIndexingOnSourceCluster() throws Exception { public void testBootstrapWithContinousIndexingOnSourceCluster() throws Exception {
// start the target first so that we know its zkhost // start the target first so that we know its zkhost
MiniSolrCloudCluster target = new MiniSolrCloudCluster(1, createTempDir("cdcr-target"), buildJettyConfig("/solr")); MiniSolrCloudCluster target = new MiniSolrCloudCluster(1, createTempDir("cdcr-target"), buildJettyConfig("/solr"));

View File

@ -781,7 +781,6 @@ public class TestLazyCores extends SolrTestCaseJ4 {
} }
} }
@BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-10101")
// Insure that when a core is aged out of the transient cache, any uncommitted docs are preserved. // Insure that when a core is aged out of the transient cache, any uncommitted docs are preserved.
// Note, this needs FS-based indexes to persist! // Note, this needs FS-based indexes to persist!
// Cores 2, 3, 6, 7, 8, 9 are transient // Cores 2, 3, 6, 7, 8, 9 are transient
@ -814,7 +813,8 @@ public class TestLazyCores extends SolrTestCaseJ4 {
openCores.clear(); openCores.clear();
// We still should have 6, 7, 8, 9 loaded, their reference counts have NOT dropped to zero // We still should have 6, 7, 8, 9 loaded, their reference counts have NOT dropped to zero
checkInCores(cc, "collection6", "collection7", "collection8", "collection9"); checkInCores(cc, "collection1", "collection5",
"collection6", "collection7", "collection8", "collection9");
for (String coreName : coreList) { for (String coreName : coreList) {
// The point of this test is to insure that when cores are aged out and re-opened // The point of this test is to insure that when cores are aged out and re-opened

View File

@ -63,14 +63,14 @@ public class SearchHandlerTest extends AbstractSolrTestCase {
); );
// Using legacy ';' param // Using legacy ';' param
assertQ(req("q", "title:test; val_s1 desc", "defType","lucenePlusSort") assertQ(req("q", "title:test", "sort","val_s1 desc")
,"//*[@numFound='3']" ,"//*[@numFound='3']"
,"//result/doc[1]/str[@name='id'][.='12']" ,"//result/doc[1]/str[@name='id'][.='12']"
,"//result/doc[2]/str[@name='id'][.='11']" ,"//result/doc[2]/str[@name='id'][.='11']"
,"//result/doc[3]/str[@name='id'][.='10']" ,"//result/doc[3]/str[@name='id'][.='10']"
); );
assertQ(req("q", "title:test; val_s1 asc", "defType","lucenePlusSort") assertQ(req("q", "title:test", "sort", "val_s1 asc")
,"//*[@numFound='3']" ,"//*[@numFound='3']"
,"//result/doc[1]/str[@name='id'][.='10']" ,"//result/doc[1]/str[@name='id'][.='10']"
,"//result/doc[2]/str[@name='id'][.='11']" ,"//result/doc[2]/str[@name='id'][.='11']"

View File

@ -37,7 +37,6 @@ import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.solr.schema; package org.apache.solr.schema;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -385,6 +386,21 @@ public class EnumFieldTest extends SolrTestCaseJ4 {
"//doc[4]/str[@name='" + FIELD_NAME + "']/text()='High'", "//doc[4]/str[@name='" + FIELD_NAME + "']/text()='High'",
"//doc[5]/str[@name='" + FIELD_NAME + "']/text()='Critical'" "//doc[5]/str[@name='" + FIELD_NAME + "']/text()='Critical'"
); );
// missing first....
for (String dir : Arrays.asList("asc", "desc")) {
assertQ(req("fl", "id", "q", "*:*", "sort", FIELD_NAME + "_missingFirst " + dir + ", id desc")
, "//doc[1]/str[@name='id']/text()='9'"
, "//doc[2]/str[@name='id']/text()='8'"
);
}
// missing last...
for (String dir : Arrays.asList("asc", "desc")) {
assertQ(req("fl", "id", "q", "*:*", "sort", FIELD_NAME + "_missingLast " + dir + ", id desc")
, "//doc[6]/str[@name='id']/text()='9'"
, "//doc[7]/str[@name='id']/text()='8'"
);
}
} }
@Test @Test

View File

@ -3330,7 +3330,14 @@ public class TestPointFields extends SolrTestCaseJ4 {
private void doTestDoublePointFunctionQuery(String field) throws Exception { private void doTestDoublePointFunctionQuery(String field) throws Exception {
assertTrue(h.getCore().getLatestSchema().getField(field).getType() instanceof PointField); assertTrue(h.getCore().getLatestSchema().getField(field).getType() instanceof PointField);
int numVals = 10 * RANDOM_MULTIPLIER; int numVals = 10 * RANDOM_MULTIPLIER;
List<Double> values = getRandomDoubles(numVals, false); // Restrict values to float range; otherwise conversion to float will cause truncation -> undefined results
List<Double> values = getRandomList(numVals, false, () -> {
Float f = Float.NaN;
while (f.isNaN()) {
f = Float.intBitsToFloat(random().nextInt());
}
return f.doubleValue();
});
String assertNumFound = "//*[@numFound='" + numVals + "']"; String assertNumFound = "//*[@numFound='" + numVals + "']";
String[] idAscXpathChecks = new String[numVals + 1]; String[] idAscXpathChecks = new String[numVals + 1];
String[] idAscNegXpathChecks = new String[numVals + 1]; String[] idAscNegXpathChecks = new String[numVals + 1];

View File

@ -230,15 +230,15 @@ public class CursorMarkTest extends SolrTestCaseJ4 {
random().nextBytes(randBytes); random().nextBytes(randBytes);
val = new BytesRef(randBytes); val = new BytesRef(randBytes);
} else if (fieldName.contains("int")) { } else if (fieldName.contains("int")) {
val = random().nextInt(); // TrieIntField val = random().nextInt();
} else if (fieldName.contains("long")) { } else if (fieldName.contains("long")) {
val = random().nextLong(); // TrieLongField val = random().nextLong();
} else if (fieldName.contains("float")) { } else if (fieldName.contains("float")) {
val = random().nextFloat() * random().nextInt(); // TrieFloatField val = random().nextFloat() * random().nextInt();
} else if (fieldName.contains("double")) { } else if (fieldName.contains("double")) {
val = random().nextDouble() * random().nextInt(); // TrieDoubleField val = random().nextDouble() * random().nextInt();
} else if (fieldName.contains("date")) { } else if (fieldName.contains("date")) {
val = random().nextLong(); // TrieDateField val = random().nextLong();
} else if (fieldName.startsWith("currency")) { } else if (fieldName.startsWith("currency")) {
val = random().nextDouble(); val = random().nextDouble();
} else if (fieldName.startsWith("uuid")) { } else if (fieldName.startsWith("uuid")) {

View File

@ -94,14 +94,6 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
" +apache +solr"); " +apache +solr");
} }
@Deprecated
public void testQueryLucenePlusSort() throws Exception {
assertQueryEquals("lucenePlusSort",
"apache solr", "apache solr", "apache solr ; score desc");
assertQueryEquals("lucenePlusSort",
"+apache +solr", "apache AND solr", " +apache +solr; score desc");
}
public void testQueryPrefix() throws Exception { public void testQueryPrefix() throws Exception {
SolrQueryRequest req = req("myField","foo_s"); SolrQueryRequest req = req("myField","foo_s");
try { try {

View File

@ -43,7 +43,6 @@ public class QueryParsingTest extends SolrTestCaseJ4 {
SolrQueryRequest req = req("df", "text"); SolrQueryRequest req = req("df", "text");
final String[] parsersTested = new String[] { final String[] parsersTested = new String[] {
OldLuceneQParserPlugin.NAME,
LuceneQParserPlugin.NAME, LuceneQParserPlugin.NAME,
DisMaxQParserPlugin.NAME, DisMaxQParserPlugin.NAME,
ExtendedDismaxQParserPlugin.NAME ExtendedDismaxQParserPlugin.NAME

View File

@ -278,7 +278,7 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 {
q = qParser.getQuery(); q = qParser.getQuery();
assertEquals(26, ((TermInSetQuery)q).getTermData().size()); assertEquals(26, ((TermInSetQuery)q).getTermData().size());
// large numeric filter query should use TermsQuery (for trie fields) // large numeric filter query should use TermsQuery
qParser = QParser.getParser("foo_ti:(1 2 3 4 5 6 7 8 9 10 20 19 18 17 16 15 14 13 12 11)", req); qParser = QParser.getParser("foo_ti:(1 2 3 4 5 6 7 8 9 10 20 19 18 17 16 15 14 13 12 11)", req);
qParser.setIsFilter(true); // this may change in the future qParser.setIsFilter(true); // this may change in the future
qParser.setParams(params); qParser.setParams(params);

View File

@ -271,9 +271,9 @@ public class TestSort extends SolrTestCaseJ4 {
if (r.nextBoolean()) sfields.add( new SortField(null, SortField.Type.SCORE)); if (r.nextBoolean()) sfields.add( new SortField(null, SortField.Type.SCORE));
// hit both use-cases of sort-missing-last // hit both use-cases of sort-missing-last
sfields.add( Sorting.getStringSortField("f", reverse, sortMissingLast, sortMissingFirst) ); sfields.add( getStringSortField("f", reverse, sortMissingLast, sortMissingFirst) );
if (secondary) { if (secondary) {
sfields.add( Sorting.getStringSortField("f2", reverse2, sortMissingLast2, sortMissingFirst2) ); sfields.add( getStringSortField("f2", reverse2, sortMissingLast2, sortMissingFirst2) );
} }
if (r.nextBoolean()) sfields.add( new SortField(null, SortField.Type.SCORE)); if (r.nextBoolean()) sfields.add( new SortField(null, SortField.Type.SCORE));
@ -355,5 +355,20 @@ public class TestSort extends SolrTestCaseJ4 {
return new BitDocIdSet(obs); return new BitDocIdSet(obs);
} }
private static SortField getStringSortField(String fieldName, boolean reverse, boolean nullLast, boolean nullFirst) {
SortField sortField = new SortField(fieldName, SortField.Type.STRING, reverse);
// 4 cases:
// missingFirst / forward: default lucene behavior
// missingFirst / reverse: set sortMissingLast
// missingLast / forward: set sortMissingLast
// missingLast / reverse: default lucene behavior
if (nullFirst && reverse) {
sortField.setMissingValue(SortField.STRING_LAST);
} else if (nullLast && !reverse) {
sortField.setMissingValue(SortField.STRING_LAST);
}
return sortField;
}
} }

Some files were not shown because too many files have changed in this diff Show More