fix for bug #391697: add escapeStringForLIKE to mozIStorageStatement r=sdwilsh
authorsspitzer@mozilla.org
Fri, 10 Aug 2007 15:57:02 -0700
changeset 4502 e80c8f021fd88b95ac36e6bc62afbfc9f86d29dd
parent 4501 0bf7c9a3cbd987bc1dc703256b1b44859e768081
child 4503 6658258c1b80357a131e0937522fcb6a8aca7e75
push idunknown
push userunknown
push dateunknown
reviewerssdwilsh
bugs391697
milestone1.9a8pre
fix for bug #391697: add escapeStringForLIKE to mozIStorageStatement r=sdwilsh
storage/public/mozIStorageStatement.idl
storage/src/mozStorageStatement.cpp
storage/test/unit/test_like_escape.js
--- a/storage/public/mozIStorageStatement.idl
+++ b/storage/public/mozIStorageStatement.idl
@@ -40,17 +40,17 @@
 #include "mozIStorageValueArray.idl"
 
 interface mozIStorageConnection;
 interface mozIStorageDataSet;
 interface nsISimpleEnumerator;
 
 [ptr] native sqlite3stmtptr(struct sqlite3_stmt);
 
-[scriptable, uuid(98379cef-b1da-4731-8556-402f0e55eb9f)]
+[scriptable, uuid(80e5210f-c0da-4f37-b5f9-d9925a75612a)]
 interface mozIStorageStatement : mozIStorageValueArray {
   /**
    * Initialize this query with the given SQL statement.
    *
    */
   void initialize(in mozIStorageConnection aDBConnection,
                   in AUTF8String aSQLStatement);
 
@@ -145,9 +145,23 @@ interface mozIStorageStatement : mozISto
    */
   const long MOZ_STORAGE_STATEMENT_INVALID = 0;
   const long MOZ_STORAGE_STATEMENT_READY = 1;
   const long MOZ_STORAGE_STATEMENT_EXECUTING = 2;
 
   readonly attribute long state;
 
   [noscript,notxpcom] sqlite3stmtptr getNativeStatementPointer();
+
+  /**
+   * Escape a string for SQL LIKE search.
+   *
+   * @param     aValue the string to escape for SQL LIKE 
+   * @param     aEscapeChar the escape character
+   * @returns   an AString of an escaped version of aValue
+   *            (%, _ and the escape char are escaped with the escape char)
+   *            For example, we will convert "foo/bar_baz%20cheese" 
+   *            into "foo//bar/_baz/%20cheese" (if the escape char is '/').
+   * @note      consumers will have to use same escape char
+   *            when doing statements such as:   ...LIKE '?1' ESCAPE '/'...
+   */
+  AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar);
 };
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -692,8 +692,28 @@ mozStorageStatement::GetIsNull(PRUint32 
 
     if (t == VALUE_TYPE_NULL)
         *_retval = PR_TRUE;
     else
         *_retval = PR_FALSE;
 
     return NS_OK;
 }
+
+/* AString escapeStringForLIKE(in AString aValue, in char aEscapeChar); */
+NS_IMETHODIMP
+mozStorageStatement::EscapeStringForLIKE(const nsAString & aValue, 
+                                         const PRUnichar aEscapeChar, 
+                                         nsAString &aEscapedString)
+{
+    const PRUnichar MATCH_ALL('%');
+    const PRUnichar MATCH_ONE('_');
+
+    aEscapedString.Truncate(0);
+
+    for (PRInt32 i = 0; i < aValue.Length(); i++) {
+        if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL || 
+            aValue[i] == MATCH_ONE)
+            aEscapedString += aEscapeChar;
+        aEscapedString += aValue[i];
+    }
+    return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_like_escape.js
@@ -0,0 +1,88 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Storage Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Seth Spitzer <sspitzer@mozilla.org> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const LATIN1_AE = "\xc6";
+const LATIN1_ae = "\xe6"; 
+
+function setup()
+{
+  getOpenedDatabase().createTable("t1", "x TEXT");
+
+  var stmt = createStatement("INSERT INTO t1 (x) VALUES ('foo/bar_baz%20cheese')");
+  stmt.execute();
+
+  stmt = createStatement("INSERT INTO t1 (x) VALUES (?1)");
+  // insert LATIN_ae, but search on LATIN_AE
+  stmt.bindStringParameter(0, "foo%20" + LATIN1_ae + "/_bar");
+  stmt.execute();
+}
+    
+function test_escape_for_like_ascii()
+{
+  var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?1 ESCAPE '/'");
+  var paramForLike = stmt.escapeStringForLIKE("oo/bar_baz%20chees", '/');
+  // verify that we escaped / _ and %
+  do_check_eq(paramForLike, "oo//bar/_baz/%20chees");
+  // prepend and append with % for "contains"
+  stmt.bindStringParameter(0, "%" + paramForLike + "%"); 
+  stmt.executeStep();
+  do_check_eq("foo/bar_baz%20cheese", stmt.getString(0));
+}
+
+function test_escape_for_like_non_ascii()
+{
+  var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?1 ESCAPE '/'");
+  var paramForLike = stmt.escapeStringForLIKE("oo%20" + LATIN1_AE + "/_ba", '/');
+  // verify that we escaped / _ and %
+  do_check_eq(paramForLike, "oo/%20" + LATIN1_AE + "///_ba");
+  // prepend and append with % for "contains"
+  stmt.bindStringParameter(0, "%" + paramForLike + "%");
+  stmt.executeStep();
+  do_check_eq("foo%20" + LATIN1_ae + "/_bar", stmt.getString(0));
+}
+
+var tests = [test_escape_for_like_ascii, test_escape_for_like_non_ascii];
+
+function run_test()
+{
+  setup();
+
+  for (var i = 0; i < tests.length; i++)
+    tests[i]();
+    
+  cleanup();
+}