diff src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java @ 427:e79bfbd0553e

Added support for escaped wildcards (\% en \_) in String arguments of DatabaseMetaData methods which return a ResultSet, such as getTables(), getColumns(), etc. When you do not want the characters % or _ to be interpreted as wildcards but as normal characters you can prefix them with a backslash (so \% and \_). Note: be sure all wildcards characters in the String argument are escaped else the search must still use a LIKE operator instead of an = comparison operator. This fixes: https://github.com/MonetDB/monetdb-java/issues/3
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Wed, 03 Feb 2021 19:41:44 +0100 (2021-02-03)
parents 95d15f1d750d
children 3924a8516d7b
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -4089,8 +4089,13 @@ public class MonetDatabaseMetaData
 	}
 
 	/**
-	 * Returns a SQL match part string where depending on the input value we
-	 * compose an exact match (use =) or match with wildcards (use LIKE) or IS NULL
+	 * Returns a SQL match part string where depending on the input value we compose
+	 * - an exact string match (using = comparator) or
+	 * - a match with wildcards (using LIKE comparator) or
+	 * - when input is missing (using IS NULL comparator)
+	 *
+	 * Note that wildcard characters can be escaped by prefix \ (a backslash)
+	 * When ALL wildcard characters are escaped then we remove the \'s and compose an = comparator string match.
 	 *
 	 * @param in the string to match
 	 * @return the SQL match part string
@@ -4099,12 +4104,67 @@ public class MonetDatabaseMetaData
 		if (in == null)
 			return "IS NULL";
 
-		String cmp = "= ";
-		// check if SQL wildcards are used in the input, if so use LIKE
-		if (in.contains("%") || in.contains("_"))
-			cmp = "LIKE ";
-
-		return cmp + MonetWrapper.sq(in);
+		// Scan input string for SQL wildcard characters: % and _
+		// When they are all prefixed by a backslash then the backslash is removed (to allow usage of = comparator)
+		// else it needs to be interpreted as a wildcard and we need to use LIKE instead of = comparator.
+		// A backslash can be escaped by using two backslashes.
+		final int len = in.length();
+		final StringBuilder sb = new StringBuilder(len);
+		boolean removed_bs = false;
+		boolean use_like = false;
+		boolean escaped = false;
+		try {
+			// parse all characters in input to find if the wildcards are escaped by a \.
+			// note: the escape character \ can also be escaped, so \\.
+			for (int i = 0; i < len; i++) {
+				char c = in.charAt(i);
+				switch(c) {
+				case '\\':
+					if (escaped) {
+						// copy the 2 backslash characters as in the original string
+	 					sb.append(c).append(c);
+	 					escaped = false;
+	 				} else {
+	 					escaped = true;
+	 				}
+	 				break;
+				case '%':
+				case '_':
+					if (escaped) {
+						removed_bs = true;
+	 					escaped = false;
+	 				} else {
+						use_like = true;
+						i = len; /* no need to scan further */
+					}
+	 				sb.append(c);
+	 				break;
+				default:
+	 				if (escaped) {
+	 					sb.append('\\');	// also copy the preceding escape found before this character
+	 					escaped = false;
+	 				}
+	 				sb.append(c);
+	 				break;
+	 			}
+			}
+		} catch (IndexOutOfBoundsException iob) {
+			/* ignore */
+		}
+
+		if (use_like) {
+		// for debug: System.out.println("input: " + in + " changed into: " + "LIKE " + MonetWrapper.sq(in));
+			// we found a non-escaped wildcard character, use like and the original input
+			return "LIKE " + MonetWrapper.sq(in);
+		}
+		if (removed_bs) {
+		// for debug: System.out.println("input: " + in + " changed into: " + "= " + MonetWrapper.sq(sb.toString()));
+			// we found only escaped wildcard character(s),
+			// use the edited string without the ecapes before the wildcard character(s) so an equals match can be done (its is faster than LIKE)
+			return "= " + MonetWrapper.sq(sb.toString());
+		}
+		// for debug: System.out.println("input: " + in + " changed into: " + "= " + MonetWrapper.sq(in));
+		return "= " + MonetWrapper.sq(in);
 	}
 
 	//== end helper methods