Skip to content

Commit f597e41

Browse files
committed
[refactor] fn:matches no longer talks directly to the new range index,
instead if the new range index is enabled and appropriate indexes exist then calls to `fn:matches` will be rewritten as calls to `range:matches`.
1 parent 0484b85 commit f597e41

11 files changed

Lines changed: 312 additions & 187 deletions

File tree

extensions/indexes/range/src/org/exist/indexing/range/RangeIndexWorker.java

Lines changed: 97 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,18 @@
6666
import org.exist.xmldb.XmldbURI;
6767
import org.exist.xquery.*;
6868
import org.exist.xquery.modules.range.RangeQueryRewriter;
69+
import org.exist.xquery.regex.JDK15RegexTranslator;
70+
import org.exist.xquery.regex.RegexSyntaxException;
6971
import org.exist.xquery.value.*;
7072
import org.w3c.dom.Node;
7173
import org.w3c.dom.NodeList;
7274

7375
import java.io.IOException;
7476
import java.io.StringReader;
7577
import java.util.*;
78+
import java.util.regex.Pattern;
79+
80+
import static org.exist.indexing.QueryableRangeIndex.OperatorType.*;
7681

7782
/**
7883
* The main worker class for the range index.
@@ -115,49 +120,58 @@ public RangeIndexWorker(RangeIndex index, DBBroker broker) {
115120
this.broker = broker;
116121
}
117122

118-
public Query toQuery(String field, QName qname, AtomicValue content, Operator operator, DocumentSet docs) throws XPathException {
123+
public Query toQuery(final String field, final QName qname, final AtomicValue content, final Operator operator, final DocumentSet docs) throws XPathException {
119124
final int type = content.getType();
120-
BytesRef bytes;
125+
final OperatorType operatorType = operator.getType();
126+
121127
if (Type.subTypeOf(type, Type.STRING)) {
122-
BytesRef key = null;
123-
if (operator != Operator.MATCH) {
124-
key = analyzeContent(field, qname, content.getStringValue(), docs);
125-
}
126-
WildcardQuery query;
127-
switch (operator) {
128+
final BytesRef key = operator.getType() != OperatorType.MATCH ? analyzeContent(field, qname, content.getStringValue(), docs) : null;
129+
130+
switch (operator.getType()) {
128131
case EQ:
129132
return new TermQuery(new Term(field, key));
130133
case STARTS_WITH:
131134
return new PrefixQuery(new Term(field, key));
135+
132136
case ENDS_WITH:
133-
bytes = new BytesRef("*");
134-
bytes.append(key);
135-
query = new WildcardQuery(new Term(field, bytes));
136-
query.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
137-
return query;
137+
final BytesRef endsWithBytes = new BytesRef("*");
138+
endsWithBytes.append(key);
139+
final WildcardQuery wildcardEndsWithQuery = new WildcardQuery(new Term(field, endsWithBytes));
140+
wildcardEndsWithQuery.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
141+
return wildcardEndsWithQuery;
142+
138143
case CONTAINS:
139-
bytes = new BytesRef("*");
140-
bytes.append(key);
141-
bytes.append(new BytesRef("*"));
142-
query = new WildcardQuery(new Term(field, bytes));
143-
query.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
144-
return query;
144+
final BytesRef containsBytes = new BytesRef("*");
145+
containsBytes.append(key);
146+
containsBytes.append(new BytesRef("*"));
147+
final WildcardQuery wildcardContatinsQuery = new WildcardQuery(new Term(field, containsBytes));
148+
wildcardContatinsQuery.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
149+
return wildcardContatinsQuery;
150+
145151
case MATCH:
146-
RegexpQuery regexpQuery = new RegexpQuery(new Term(field, content.getStringValue()));
147-
regexpQuery.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
148-
return regexpQuery;
152+
final RegexQuery regexQuery = new RegexQuery(new Term(field, translateRegexp(content.getStringValue())));
153+
final int flags;
154+
if(((MatchOperator)operator).getRegexFlags().isPresent()) {
155+
flags = parseRegexFlags(((MatchOperator)operator).getRegexFlags().get());
156+
} else {
157+
flags = 0;
158+
}
159+
regexQuery.setRegexImplementation(new JavaUtilRegexCapabilities(flags));
160+
regexQuery.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
161+
return regexQuery;
149162
}
150163
}
151-
if (operator == Operator.EQ) {
164+
165+
if (operatorType == EQ) {
152166
return new TermQuery(new Term(field, TypeConversion.convertToBytes(content)));
153167
}
154-
final boolean includeUpper = operator == Operator.LE;
155-
final boolean includeLower = operator == Operator.GE;
168+
final boolean includeUpper = operatorType == LE;
169+
final boolean includeLower = operatorType == GE;
156170
switch (type) {
157171
case Type.INTEGER:
158172
case Type.LONG:
159173
case Type.UNSIGNED_LONG:
160-
if (operator == Operator.LT || operator == Operator.LE) {
174+
if (operatorType == LT || operatorType == LE) {
161175
return NumericRangeQuery.newLongRange(field, null, ((NumericValue)content).getLong(), includeLower, includeUpper);
162176
} else {
163177
return NumericRangeQuery.newLongRange(field, ((NumericValue)content).getLong(), null, includeLower, includeUpper);
@@ -166,48 +180,91 @@ public Query toQuery(String field, QName qname, AtomicValue content, Operator op
166180
case Type.UNSIGNED_INT:
167181
case Type.SHORT:
168182
case Type.UNSIGNED_SHORT:
169-
if (operator == Operator.LT || operator == Operator.LE) {
183+
if (operatorType == LT || operatorType == LE) {
170184
return NumericRangeQuery.newIntRange(field, null, ((NumericValue) content).getInt(), includeLower, includeUpper);
171185
} else {
172186
return NumericRangeQuery.newIntRange(field, ((NumericValue) content).getInt(), null, includeLower, includeUpper);
173187
}
174188
case Type.DECIMAL:
175189
case Type.DOUBLE:
176-
if (operator == Operator.LT || operator == Operator.LE) {
190+
if (operatorType == LT || operatorType == LE) {
177191
return NumericRangeQuery.newDoubleRange(field, null, ((NumericValue) content).getDouble(), includeLower, includeUpper);
178192
} else {
179193
return NumericRangeQuery.newDoubleRange(field, ((NumericValue) content).getDouble(), null, includeLower, includeUpper);
180194
}
181195
case Type.FLOAT:
182-
if (operator == Operator.LT || operator == Operator.LE) {
196+
if (operatorType == LT || operatorType == LE) {
183197
return NumericRangeQuery.newFloatRange(field, null, (float) ((NumericValue) content).getDouble(), includeLower, includeUpper);
184198
} else {
185199
return NumericRangeQuery.newFloatRange(field, (float) ((NumericValue) content).getDouble(), null, includeLower, includeUpper);
186200
}
187201
case Type.DATE:
188202
long dl = TypeConversion.dateToLong((DateValue) content);
189-
if (operator == Operator.LT || operator == Operator.LE) {
203+
if (operatorType == LT || operatorType == LE) {
190204
return NumericRangeQuery.newLongRange(field, null, dl, includeLower, includeUpper);
191205
} else {
192206
return NumericRangeQuery.newLongRange(field, dl, null, includeLower, includeUpper);
193207
}
194208
case Type.TIME:
195209
long tl = TypeConversion.timeToLong((TimeValue) content);
196-
if (operator == Operator.LT || operator == Operator.LE) {
210+
if (operatorType == LT || operatorType == LE) {
197211
return NumericRangeQuery.newLongRange(field, null, tl, includeLower, includeUpper);
198212
} else {
199213
return NumericRangeQuery.newLongRange(field, tl, null, includeLower, includeUpper);
200214
}
201215
case Type.DATE_TIME:
202216
default:
203-
if (operator == Operator.LT || operator == Operator.LE) {
217+
if (operatorType == LT || operatorType == LE) {
204218
return new TermRangeQuery(field, null, TypeConversion.convertToBytes(content), includeLower, includeUpper);
205219
} else {
206220
return new TermRangeQuery(field, TypeConversion.convertToBytes(content), null, includeLower, includeUpper);
207221
}
208222
}
209223
}
210224

225+
/**
226+
* Translates fn:matches style regex into
227+
* regex suitable for {@link java.util.regex.Pattern}
228+
*/
229+
private String translateRegexp(final String pattern) throws XPathException {
230+
try {
231+
final int xmlVersion = 11;
232+
final boolean ignoreWhitespace = false;
233+
final boolean caseBlind = false;
234+
return JDK15RegexTranslator.translate(pattern, xmlVersion, true, ignoreWhitespace, caseBlind);
235+
} catch (final RegexSyntaxException e) {
236+
throw new XPathException("Conversion from XPath2 to Java regular expression syntax failed: " + e.getMessage(), e);
237+
}
238+
}
239+
240+
/**
241+
* Parses XPath fn:matches style regex flags
242+
* into flags suitable for {@link java.util.regex.Pattern}
243+
*/
244+
private int parseRegexFlags(final String s) throws XPathException {
245+
int flags = 0;
246+
for(int i = 0; i < s.length(); i++) {
247+
final char ch = s.charAt(i);
248+
switch(ch) {
249+
case 'm':
250+
flags |= Pattern.MULTILINE;
251+
break;
252+
case 'i':
253+
flags = flags | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
254+
break;
255+
case 'x':
256+
flags |= Pattern.COMMENTS;
257+
break;
258+
case 's':
259+
flags |= Pattern.DOTALL;
260+
break;
261+
default:
262+
throw new XPathException("err:FORX0001: Invalid regular expression flag: " + ch);
263+
}
264+
}
265+
return flags;
266+
}
267+
211268
@Override
212269
public String getIndexId() {
213270
return index.getIndexId();
@@ -501,39 +558,16 @@ private void write() {
501558
}
502559

503560
@Override
504-
public NodeSet match(final int contextId, final DocumentSet docs, final NodeSet contextSet, List<QName> qnames, final String regex, final int flags, final int axis) throws IOException, XPathException {
561+
public NodeSet query(final int contextId, final DocumentSet docs, final NodeSet contextSet, final List<QName> qnames, final AtomicValue[] keys, final Operator operator, final int axis) throws IOException, XPathException {
505562
return index.withSearcher(searcher -> {
506-
List<QName> definedIndexes = getDefinedIndexes(qnames);
563+
final List<QName> definedIndexes = getDefinedIndexes(qnames);
507564
NodeSet resultSet = NodeSet.EMPTY_SET;
508565
for (final QName qname : definedIndexes) {
509-
String field = LuceneUtil.encodeQName(qname, index.getBrokerPool().getSymbols());
510-
final RegexQuery query = new RegexQuery(new Term(field, regex));
511-
query.setRegexImplementation(new JavaUtilRegexCapabilities(flags));
512-
513-
if (contextSet != null && contextSet.hasOne() && contextSet.getItemType() != Type.DOCUMENT) {
514-
NodesFilter filter = new NodesFilter(contextSet);
515-
filter.init(searcher.getIndexReader());
516-
FilteredQuery filtered = new FilteredQuery(query, filter, FilteredQuery.LEAP_FROG_FILTER_FIRST_STRATEGY);
517-
resultSet = doQuery(contextId, docs, contextSet, axis, searcher, null, filtered, null);
518-
} else {
519-
resultSet = doQuery(contextId, docs, contextSet, axis, searcher, null, query, null);
520-
}
521-
}
522-
return resultSet;
523-
});
524-
}
525-
526-
@Override
527-
public NodeSet query(int contextId, DocumentSet docs, NodeSet contextSet, List<QName> qnames, AtomicValue[] keys, Operator operator, int axis) throws IOException, XPathException {
528-
return index.withSearcher(searcher -> {
529-
List<QName> definedIndexes = getDefinedIndexes(qnames);
530-
NodeSet resultSet = NodeSet.EMPTY_SET;
531-
for (QName qname : definedIndexes) {
532-
Query query;
533-
String field = LuceneUtil.encodeQName(qname, index.getBrokerPool().getSymbols());
566+
final Query query;
567+
final String field = LuceneUtil.encodeQName(qname, index.getBrokerPool().getSymbols());
534568
if (keys.length > 1) {
535569
BooleanQuery bool = new BooleanQuery();
536-
for (AtomicValue key : keys) {
570+
for (final AtomicValue key : keys) {
537571
bool.add(toQuery(field, qname, key, operator, docs), BooleanClause.Occur.SHOULD);
538572
}
539573
query = bool;
@@ -542,9 +576,9 @@ public NodeSet query(int contextId, DocumentSet docs, NodeSet contextSet, List<Q
542576
}
543577

544578
if (contextSet != null && contextSet.hasOne() && contextSet.getItemType() != Type.DOCUMENT) {
545-
NodesFilter filter = new NodesFilter(contextSet);
579+
final NodesFilter filter = new NodesFilter(contextSet);
546580
filter.init(searcher.getIndexReader());
547-
FilteredQuery filtered = new FilteredQuery(query, filter, FilteredQuery.LEAP_FROG_FILTER_FIRST_STRATEGY);
581+
final FilteredQuery filtered = new FilteredQuery(query, filter, FilteredQuery.LEAP_FROG_FILTER_FIRST_STRATEGY);
548582
resultSet = doQuery(contextId, docs, contextSet, axis, searcher, null, filtered, null);
549583
} else {
550584
resultSet = doQuery(contextId, docs, contextSet, axis, searcher, null, query, null);
@@ -554,7 +588,7 @@ public NodeSet query(int contextId, DocumentSet docs, NodeSet contextSet, List<Q
554588
});
555589
}
556590

557-
public NodeSet queryField(int contextId, DocumentSet docs, NodeSet contextSet, Sequence fields, Sequence[] keys, Operator[] operators, int axis) throws IOException, XPathException {
591+
public NodeSet queryField(final int contextId, final DocumentSet docs, final NodeSet contextSet, final Sequence fields, final Sequence[] keys, final Operator[] operators, final int axis) throws IOException, XPathException {
558592
return index.withSearcher(searcher -> {
559593
BooleanQuery query = new BooleanQuery();
560594
int j = 0;

extensions/indexes/range/src/org/exist/xquery/modules/range/FieldLookup.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public NodeSet preSelect(Sequence contextSequence, boolean useContext) throws XP
199199
operators = new QueryableRangeIndex.Operator[operatorSeq.getItemCount()];
200200
int i = 0;
201201
for (SequenceIterator si = operatorSeq.iterate(); si.hasNext(); i++) {
202-
operators[i] = QueryableRangeIndex.Operator.fromName(si.nextItem().getStringValue());
202+
operators[i] = QueryableRangeIndex.OperatorFactory.fromName(si.nextItem().getStringValue());
203203
}
204204
j++;
205205
} else {
@@ -265,7 +265,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
265265
operators = new QueryableRangeIndex.Operator[operatorSeq.getItemCount()];
266266
int i = 0;
267267
for (SequenceIterator si = operatorSeq.iterate(); si.hasNext(); i++) {
268-
operators[i] = QueryableRangeIndex.Operator.fromName(si.nextItem().getStringValue());
268+
operators[i] = QueryableRangeIndex.OperatorFactory.fromName(si.nextItem().getStringValue());
269269
}
270270
j++;
271271
} else {
@@ -322,7 +322,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
322322

323323
private QueryableRangeIndex.Operator getOperator() {
324324
final String calledAs = getSignature().getName().getLocalPart();
325-
return QueryableRangeIndex.Operator.fromName(calledAs.substring("field-".length()));
325+
return QueryableRangeIndex.OperatorFactory.fromName(calledAs.substring("field-".length()));
326326
}
327327

328328
public int getType(Sequence contextSequence, String field) {

0 commit comments

Comments
 (0)