changeset 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 3da79315783b
children 0b808a4d4f1f
files ChangeLog src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java tests/JDBC_API_Tester.java
diffstat 3 files changed, 88 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,16 @@
 # ChangeLog file for monetdb-java
 # This file is updated with Maddlog
 
+* Wed Feb  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- 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
+
 * Thu Jan 28 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Corrected the ordering of the output of DatabaseMetaData methods
   getImportedKeys(), getExportedKeys() and getCrossReference(). In cases
--- 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
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -677,22 +677,26 @@ final public class JDBC_API_Tester {
 			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION\n" +
 			"null	sys	schemas	SYSTEM TABLE	null	null	null	null	null	null\n");
 
-			compareResultSet(dbmd.getColumns(null, "sys", "table_types", null), "getColumns(null, sys, table_types, null)",
+			compareResultSet(dbmd.getColumns(null, "sys", "table\\_types", null), "getColumns(null, sys, table\\_types, null)",
 			"Resultset with 24 columns\n" +
 			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	NUM_PREC_RADIX	NULLABLE	REMARKS	COLUMN_DEF	SQL_DATA_TYPE	SQL_DATETIME_SUB	CHAR_OCTET_LENGTH	ORDINAL_POSITION	IS_NULLABLE	SCOPE_CATALOG	SCOPE_SCHEMA	SCOPE_TABLE	SOURCE_DATA_TYPE	IS_AUTOINCREMENT	IS_GENERATEDCOLUMN\n" +
 			"null	sys	table_types	table_type_id	5	smallint	16	0	0	2	0	null	null	0	0	null	1	NO	null	null	null	null	NO	NO\n" +
 			"null	sys	table_types	table_type_name	12	varchar	25	0	0	0	0	null	null	0	0	25	2	NO	null	null	null	null	NO	NO\n");
 
-			compareResultSet(dbmd.getPrimaryKeys(null, "sys", "table_types"), "getPrimaryKeys(null, sys, table_types)",
+			compareResultSet(dbmd.getPrimaryKeys(null, "sys", "table\\_types"), "getPrimaryKeys(null, sys, table\\_types)",
 			"Resultset with 6 columns\n" +
 			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	KEY_SEQ	PK_NAME\n" +
 			"null	sys	table_types	table_type_id	1	table_types_table_type_id_pkey\n");
 
-			compareResultSet(dbmd.getCrossReference(null, "sys", "tables", null, "sys", "table_types"), "getCrossReference(null, sys, tables, null, sys, table_types)",
+			compareResultSet(dbmd.getExportedKeys(null, "sys", "table\\_types"), "getExportedKeys(null, sys, table\\_types)",
 			"Resultset with 14 columns\n" +
 			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n");
 
-			compareResultSet(dbmd.getImportedKeys(null, "sys", "table_types"), "getImportedKeys(null, sys, table_types)",
+			compareResultSet(dbmd.getCrossReference(null, "sys", "tables", null, "sys", "table\\_types"), "getCrossReference(null, sys, tables, null, sys, table\\_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n");
+
+			compareResultSet(dbmd.getImportedKeys(null, "sys", "table\\_types"), "getImportedKeys(null, sys, table\\_types)",
 			"Resultset with 14 columns\n" +
 			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n");
 
@@ -702,12 +706,12 @@ final public class JDBC_API_Tester {
 			"null	sys	key_types	false	null	key_types_key_type_id_pkey	2	1	key_type_id	null	3	0	null\n" +
 			"null	sys	key_types	false	null	key_types_key_type_name_unique	2	1	key_type_name	null	3	0	null\n");
 
-			compareResultSet(dbmd.getTablePrivileges(null, "sys", "table_types"), "getTablePrivileges(null, sys, table_types)",
+			compareResultSet(dbmd.getTablePrivileges(null, "sys", "table\\_types"), "getTablePrivileges(null, sys, table\\_types)",
 			"Resultset with 7 columns\n" +
 			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE\n" +
 			"null	sys	table_types	monetdb	public	SELECT	NO\n");
 
-			compareResultSet(dbmd.getColumnPrivileges(null, "sys", "table_types", null), "getColumnPrivileges(null, sys, table_types, null)",
+			compareResultSet(dbmd.getColumnPrivileges(null, "sys", "table\\_types", null), "getColumnPrivileges(null, sys, table\\_types, null)",
 			"Resultset with 8 columns\n" +
 			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE\n");