6666import org .exist .xmldb .XmldbURI ;
6767import org .exist .xquery .*;
6868import org .exist .xquery .modules .range .RangeQueryRewriter ;
69+ import org .exist .xquery .regex .JDK15RegexTranslator ;
70+ import org .exist .xquery .regex .RegexSyntaxException ;
6971import org .exist .xquery .value .*;
7072import org .w3c .dom .Node ;
7173import org .w3c .dom .NodeList ;
7274
7375import java .io .IOException ;
7476import java .io .StringReader ;
7577import 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 ;
0 commit comments