Skip to content
Snippets Groups Projects
Commit e33030d6 authored by Daniel Hornung's avatar Daniel Hornung Committed by Henrik tom Woerden
Browse files

Improving file query

parent eb840a5f
No related branches found
No related tags found
No related merge requests found
...@@ -20,4 +20,5 @@ writing tests. ...@@ -20,4 +20,5 @@ writing tests.
### Running tests with Maven ### ### Running tests with Maven ###
- Automatic testing can be done with `make test` or, after compilation, `mvn - Automatic testing can be done with `make test` or, after compilation, `mvn
test`. test`.
- Tests of single modules can be started with `mvn test -Dtest=TextClass` - Tests of single modules can be started with `mvn test -Dtest=TestClass`
- Test of a single method `footest`: `mvn test -Dtest=TestClass#footest`
...@@ -349,8 +349,9 @@ ID: ...@@ -349,8 +349,9 @@ ID:
[Ii][Dd] [Ii][Dd]
; ;
SLASH: // Multiple slashes should be allowed in paths instead of a single slash.
'/' SLASHES:
'/'+
; ;
STAR: STAR:
......
...@@ -456,8 +456,8 @@ location returns [String str] ...@@ -456,8 +456,8 @@ location returns [String str]
: :
atom {$str = $atom.str;} atom {$str = $atom.str;}
| |
SLASH ? SLASHES ?
((WHICH | WITH)+ SLASH |( TXT | COLON | HYPHEN | NUM | DOT | ESC_STAR | ESC_BS | ESC_REGEXP_END | STAR )+ SLASH ?)* {$str = $text; } ((WHICH | WITH)+ SLASHES |( TXT | COLON | HYPHEN | NUM | DOT | ESC_STAR | ESC_BS | ESC_REGEXP_END | STAR )+ SLASHES ?)* {$str = $text; }
; ;
atom returns [String str] atom returns [String str]
......
...@@ -25,6 +25,8 @@ package caosdb.server.query; ...@@ -25,6 +25,8 @@ package caosdb.server.query;
import static java.sql.Types.VARCHAR; import static java.sql.Types.VARCHAR;
import caosdb.server.query.Query.QueryException; import caosdb.server.query.Query.QueryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
...@@ -34,47 +36,73 @@ import org.jdom2.Element; ...@@ -34,47 +36,73 @@ import org.jdom2.Element;
public class StoredAt implements EntityFilterInterface { public class StoredAt implements EntityFilterInterface {
public static final java.util.regex.Pattern PATTERN = public static final java.util.regex.Pattern PATTERN =
Pattern.compile("((?:(?:\\\\[\\*\\\\])|[^\\*\\\\])+)?(\\*{2,})?(\\*)?"); // heading and escaped * and \ removed, then
private final String location; // (part of directory name: no \, no *)?(**)?(*)?
Pattern.compile("((?:(?:\\\\[\\*\\\\])|[^\\*\\\\])+)?(\\*{2,})?(\\*)?");
private String location;
private final String likeLocation;
private boolean pattern_matching = false; private boolean pattern_matching = false;
public StoredAt(final String loc) { public StoredAt(final String loc) {
final String[] dirs = loc.split("/"); Path locPath = Paths.get(loc);
final StringBuffer sb = new StringBuffer(); this.location =
locPath.normalize().toString().replaceFirst("^/", "") + (loc.endsWith("/") ? "/" : "");
// if (dirs[0].length() == 0) { this.pattern_matching = requiresPatternMatching();
// sb.append("/");
// }
for (final String dir : dirs) {
final Matcher m = PATTERN.matcher(dir);
sb.append("/");
while (m.find()) {
// part of a directory name
if (m.group(1) != null) {
sb.append(m.group(1).replace("%", "\\%").replace("_", "\\_").replaceAll("\\\\\\*", "*"));
}
// **
if (m.group(2) != null) {
this.pattern_matching = true;
sb.append("%");
}
// * if (this.pattern_matching) {
if (m.group(3) != null) { this.likeLocation = convertLikeLocation();
this.pattern_matching = true; } else {
sb.append("%%"); this.likeLocation = null;
}
}
} }
}
if (loc.endsWith("/")) { /**
sb.append("/"); * Does some special character escaping for LIKE query
*
* <p>The following rules should take place in the future (for now, only a subset is implemented
* correctly):
*
* <ul>
* <li>\* -> * (No special meaning in SQL, even numbers of \ are ignored.)
* <li>* -> %% (Wildcard, to be converted to a more complicated regex without / later.
* Exception: at the beginning of a search expression, convert to %.)
* <li>** -> % (Wildcard, also ***, ****, ... -> %)
* <li>_ -> \_ (Prevent special meaning in SQL)
* <li>% -> \% (Prevent special meaning in SQL)
* <li>Heading `/` is removed
* </ul>
*
* @return The converted String.
*/
String convertLikeLocation() {
final String evenB = "(^|[^b*])(bb)*";
final String oddB = evenB + "b";
final String singleEscapedAst = "b\\*";
final String findSingleAst =
(evenB + "(" + singleEscapedAst + ")?" + "\\*([^*]|$)").replace("b", "\\\\");
final String findMultipleAst =
(evenB + "(" + singleEscapedAst + ")?" + "\\*{2,}").replace("b", "\\\\");
java.util.regex.Pattern singlePat = Pattern.compile(findSingleAst);
java.util.regex.Pattern multiplePat = Pattern.compile(findMultipleAst);
// Simple SQL escape replacements
String converted = this.location.replace("%", "\\%").replace("_", "\\_");
// * -> %%, has to run mutliple times, because otherwise the separation between patterns would
// need to be larger than one character
while (singlePat.matcher(converted).find()) {
converted = converted.replaceAll(findSingleAst, "$1$2$3%%$4");
} }
// ** -> %
converted = converted.replaceAll(findMultipleAst, "$1$2$3%");
// All remaining asterisks are now guaranteed to have an odd number of backslashes in front
this.location = // So we can now do \* -> *
sb.toString().replaceFirst("^/", "").replaceFirst("^%{2,}", "%").replaceFirst("^/", ""); converted = converted.replaceAll(singleEscapedAst.replace("b", "\\\\"), "*");
// Final exceptions
converted = converted.replaceFirst("^%{2,}", "%"); // .replaceFirst("^/", "");
return converted;
} }
@Override @Override
...@@ -99,14 +127,14 @@ public class StoredAt implements EntityFilterInterface { ...@@ -99,14 +127,14 @@ public class StoredAt implements EntityFilterInterface {
if (this.location == null) { // location if (this.location == null) { // location
callSAT.setNull(3, VARCHAR); callSAT.setNull(3, VARCHAR);
} else { } else {
callSAT.setString(3, this.location); callSAT.setString(3, (this.pattern_matching ? this.likeLocation : this.location));
} }
callSAT.setString(4, (this.pattern_matching ? "LIKE" : "=")); callSAT.setString(4, (this.pattern_matching ? "LIKE" : "="));
callSAT.execute(); callSAT.execute();
// ... and then cancel out the wrong guys via regexp // ... and then cancel out the wrong guys ('*' matching '/') via regexp
if (this.pattern_matching && this.location.contains("%%")) { if (this.pattern_matching && this.likeLocation.contains("%%")) {
// make a MySQL-conform regexp for this.location: // make a MySQL-conform regexp for this.location:
// 1) escape literal dots (. = \.) // 1) escape literal dots (. = \.)
...@@ -116,7 +144,7 @@ public class StoredAt implements EntityFilterInterface { ...@@ -116,7 +144,7 @@ public class StoredAt implements EntityFilterInterface {
// 5) replace non-escaped %% with [^/]* // 5) replace non-escaped %% with [^/]*
// 6) replace non-escaped % with .* // 6) replace non-escaped % with .*
final String nloc = final String nloc =
Pattern.quote(this.location.replace(".", "\\.")) Pattern.quote(this.likeLocation.replace(".", "\\."))
.replaceFirst("..$", "\\$") .replaceFirst("..$", "\\$")
.replaceFirst("^..", "^") .replaceFirst("^..", "^")
.replaceAll("(?<!\\\\)%%", "[^/]*") .replaceAll("(?<!\\\\)%%", "[^/]*")
...@@ -139,6 +167,20 @@ public class StoredAt implements EntityFilterInterface { ...@@ -139,6 +167,20 @@ public class StoredAt implements EntityFilterInterface {
query.addBenchmark(this.getClass().getSimpleName(), System.currentTimeMillis() - t1); query.addBenchmark(this.getClass().getSimpleName(), System.currentTimeMillis() - t1);
} }
/**
* Tests if the location String is intended for pattern matching.
*
* @return True if the location requires pattern matching for a LIKE query, false otherwise.
*/
boolean requiresPatternMatching() {
// Looking for *, preceded by no or an even number of backslashes
String requiredPatternStr = "(^|[^b])(bb)*\\*";
requiredPatternStr = requiredPatternStr.replace("b", "\\\\");
java.util.regex.Pattern REQPATTERN = Pattern.compile(requiredPatternStr);
final Matcher m = REQPATTERN.matcher(this.location);
return m.find();
}
@Override @Override
public Element toElement() { public Element toElement() {
final Element ret = new Element("StoredAt"); final Element ret = new Element("StoredAt");
...@@ -146,8 +188,13 @@ public class StoredAt implements EntityFilterInterface { ...@@ -146,8 +188,13 @@ public class StoredAt implements EntityFilterInterface {
return ret; return ret;
} }
/**
* For testing purposes, create a string representing the corresponding query.
*
* @return The String "SAT(<location>)", where "<location>" is the query string.
*/
@Override @Override
public String toString() { public String toString() {
return "SAT(" + this.location + ")"; return "SAT(" + (this.pattern_matching ? this.likeLocation : this.location) + ")";
} }
} }
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment