|
20 | 20 | package org.elasticsearch.index.query;
|
21 | 21 |
|
22 | 22 | import org.apache.lucene.index.IndexOptions;
|
| 23 | +import org.apache.lucene.index.Term; |
23 | 24 | import org.apache.lucene.queries.XIntervals;
|
24 | 25 | import org.apache.lucene.queries.intervals.FilteredIntervalsSource;
|
25 | 26 | import org.apache.lucene.queries.intervals.IntervalIterator;
|
26 | 27 | import org.apache.lucene.queries.intervals.Intervals;
|
27 | 28 | import org.apache.lucene.queries.intervals.IntervalsSource;
|
| 29 | +import org.apache.lucene.search.FuzzyQuery; |
28 | 30 | import org.apache.lucene.util.BytesRef;
|
| 31 | +import org.apache.lucene.util.automaton.CompiledAutomaton; |
29 | 32 | import org.elasticsearch.Version;
|
30 | 33 | import org.elasticsearch.common.ParseField;
|
31 | 34 | import org.elasticsearch.common.ParsingException;
|
32 | 35 | import org.elasticsearch.common.io.stream.NamedWriteable;
|
33 | 36 | import org.elasticsearch.common.io.stream.StreamInput;
|
34 | 37 | import org.elasticsearch.common.io.stream.StreamOutput;
|
35 | 38 | import org.elasticsearch.common.io.stream.Writeable;
|
| 39 | +import org.elasticsearch.common.unit.Fuzziness; |
36 | 40 | import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
| 41 | +import org.elasticsearch.common.xcontent.ObjectParser; |
37 | 42 | import org.elasticsearch.common.xcontent.ToXContentFragment;
|
38 | 43 | import org.elasticsearch.common.xcontent.ToXContentObject;
|
39 | 44 | import org.elasticsearch.common.xcontent.XContentBuilder;
|
@@ -85,6 +90,8 @@ public static IntervalsSourceProvider fromXContent(XContentParser parser) throws
|
85 | 90 | return Prefix.fromXContent(parser);
|
86 | 91 | case "wildcard":
|
87 | 92 | return Wildcard.fromXContent(parser);
|
| 93 | + case "fuzzy": |
| 94 | + return Fuzzy.fromXContent(parser); |
88 | 95 | }
|
89 | 96 | throw new ParsingException(parser.getTokenLocation(),
|
90 | 97 | "Unknown interval type [" + parser.currentName() + "], expecting one of [match, any_of, all_of, prefix, wildcard]");
|
@@ -691,6 +698,148 @@ String getUseField() {
|
691 | 698 | }
|
692 | 699 | }
|
693 | 700 |
|
| 701 | + public static class Fuzzy extends IntervalsSourceProvider { |
| 702 | + |
| 703 | + public static final String NAME = "fuzzy"; |
| 704 | + |
| 705 | + private final String term; |
| 706 | + private final int prefixLength; |
| 707 | + private final boolean transpositions; |
| 708 | + private final Fuzziness fuzziness; |
| 709 | + private final String analyzer; |
| 710 | + private final String useField; |
| 711 | + |
| 712 | + public Fuzzy(String term, int prefixLength, boolean transpositions, Fuzziness fuzziness, String analyzer, String useField) { |
| 713 | + this.term = term; |
| 714 | + this.prefixLength = prefixLength; |
| 715 | + this.transpositions = transpositions; |
| 716 | + this.fuzziness = fuzziness; |
| 717 | + this.analyzer = analyzer; |
| 718 | + this.useField = useField; |
| 719 | + } |
| 720 | + |
| 721 | + public Fuzzy(StreamInput in) throws IOException { |
| 722 | + this.term = in.readString(); |
| 723 | + this.prefixLength = in.readVInt(); |
| 724 | + this.transpositions = in.readBoolean(); |
| 725 | + this.fuzziness = new Fuzziness(in); |
| 726 | + this.analyzer = in.readOptionalString(); |
| 727 | + this.useField = in.readOptionalString(); |
| 728 | + } |
| 729 | + |
| 730 | + @Override |
| 731 | + public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) { |
| 732 | + NamedAnalyzer analyzer = fieldType.searchAnalyzer(); |
| 733 | + if (this.analyzer != null) { |
| 734 | + analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer); |
| 735 | + } |
| 736 | + IntervalsSource source; |
| 737 | + if (useField != null) { |
| 738 | + fieldType = context.fieldMapper(useField); |
| 739 | + assert fieldType != null; |
| 740 | + checkPositions(fieldType); |
| 741 | + if (this.analyzer == null) { |
| 742 | + analyzer = fieldType.searchAnalyzer(); |
| 743 | + } |
| 744 | + } |
| 745 | + checkPositions(fieldType); |
| 746 | + BytesRef normalizedTerm = analyzer.normalize(fieldType.name(), term); |
| 747 | + FuzzyQuery fq = new FuzzyQuery(new Term(fieldType.name(), normalizedTerm), |
| 748 | + fuzziness.asDistance(term), prefixLength, 128, transpositions); |
| 749 | + CompiledAutomaton ca = new CompiledAutomaton(fq.toAutomaton()); |
| 750 | + source = XIntervals.multiterm(ca, term); |
| 751 | + if (useField != null) { |
| 752 | + source = Intervals.fixField(useField, source); |
| 753 | + } |
| 754 | + return source; |
| 755 | + } |
| 756 | + |
| 757 | + private void checkPositions(MappedFieldType type) { |
| 758 | + if (type.indexOptions().compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) < 0) { |
| 759 | + throw new IllegalArgumentException("Cannot create intervals over field [" + type.name() + "] with no positions indexed"); |
| 760 | + } |
| 761 | + } |
| 762 | + |
| 763 | + @Override |
| 764 | + public void extractFields(Set<String> fields) { |
| 765 | + if (useField != null) { |
| 766 | + fields.add(useField); |
| 767 | + } |
| 768 | + } |
| 769 | + |
| 770 | + @Override |
| 771 | + public boolean equals(Object o) { |
| 772 | + if (this == o) return true; |
| 773 | + if (o == null || getClass() != o.getClass()) return false; |
| 774 | + Fuzzy fuzzy = (Fuzzy) o; |
| 775 | + return prefixLength == fuzzy.prefixLength && |
| 776 | + transpositions == fuzzy.transpositions && |
| 777 | + Objects.equals(term, fuzzy.term) && |
| 778 | + Objects.equals(fuzziness, fuzzy.fuzziness) && |
| 779 | + Objects.equals(analyzer, fuzzy.analyzer) && |
| 780 | + Objects.equals(useField, fuzzy.useField); |
| 781 | + } |
| 782 | + |
| 783 | + @Override |
| 784 | + public int hashCode() { |
| 785 | + return Objects.hash(term, prefixLength, transpositions, fuzziness, analyzer, useField); |
| 786 | + } |
| 787 | + |
| 788 | + @Override |
| 789 | + public String getWriteableName() { |
| 790 | + return NAME; |
| 791 | + } |
| 792 | + |
| 793 | + @Override |
| 794 | + public void writeTo(StreamOutput out) throws IOException { |
| 795 | + out.writeString(term); |
| 796 | + out.writeVInt(prefixLength); |
| 797 | + out.writeBoolean(transpositions); |
| 798 | + fuzziness.writeTo(out); |
| 799 | + out.writeOptionalString(analyzer); |
| 800 | + out.writeOptionalString(useField); |
| 801 | + } |
| 802 | + |
| 803 | + @Override |
| 804 | + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { |
| 805 | + builder.startObject(NAME); |
| 806 | + builder.field("term", term); |
| 807 | + builder.field("prefix_length", prefixLength); |
| 808 | + builder.field("transpositions", transpositions); |
| 809 | + fuzziness.toXContent(builder, params); |
| 810 | + if (analyzer != null) { |
| 811 | + builder.field("analyzer", analyzer); |
| 812 | + } |
| 813 | + if (useField != null) { |
| 814 | + builder.field("use_field", useField); |
| 815 | + } |
| 816 | + builder.endObject(); |
| 817 | + return builder; |
| 818 | + } |
| 819 | + |
| 820 | + private static final ConstructingObjectParser<Fuzzy, Void> PARSER = new ConstructingObjectParser<>(NAME, args -> { |
| 821 | + String term = (String) args[0]; |
| 822 | + int prefixLength = (args[1] == null) ? FuzzyQueryBuilder.DEFAULT_PREFIX_LENGTH : (int) args[1]; |
| 823 | + boolean transpositions = (args[2] == null) ? FuzzyQueryBuilder.DEFAULT_TRANSPOSITIONS : (boolean) args[2]; |
| 824 | + Fuzziness fuzziness = (args[3] == null) ? FuzzyQueryBuilder.DEFAULT_FUZZINESS : (Fuzziness) args[3]; |
| 825 | + String analyzer = (String) args[4]; |
| 826 | + String useField = (String) args[5]; |
| 827 | + return new Fuzzy(term, prefixLength, transpositions, fuzziness, analyzer, useField); |
| 828 | + }); |
| 829 | + static { |
| 830 | + PARSER.declareString(constructorArg(), new ParseField("term")); |
| 831 | + PARSER.declareInt(optionalConstructorArg(), new ParseField("prefix_length")); |
| 832 | + PARSER.declareBoolean(optionalConstructorArg(), new ParseField("transpositions")); |
| 833 | + PARSER.declareField(optionalConstructorArg(), (p, c) -> Fuzziness.parse(p), Fuzziness.FIELD, ObjectParser.ValueType.VALUE); |
| 834 | + PARSER.declareString(optionalConstructorArg(), new ParseField("analyzer")); |
| 835 | + PARSER.declareString(optionalConstructorArg(), new ParseField("use_field")); |
| 836 | + } |
| 837 | + |
| 838 | + public static Fuzzy fromXContent(XContentParser parser) throws IOException { |
| 839 | + return PARSER.parse(parser, null); |
| 840 | + } |
| 841 | + } |
| 842 | + |
694 | 843 | static class ScriptFilterSource extends FilteredIntervalsSource {
|
695 | 844 |
|
696 | 845 | final IntervalFilterScript script;
|
|
0 commit comments