From f40478dd95cc60da3535e527f6d0f2d851bf28bf Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Tue, 1 Apr 2014 17:58:07 -0400 Subject: [PATCH] Implemented LIKE operator with escape char for PropertyFilter --- lib/persistence.js | 52 ++++++++++++++++++++++++++++++-- lib/persistence.store.sql.js | 10 ++++++ test/browser/test.persistence.js | 24 ++++++++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/lib/persistence.js b/lib/persistence.js index d89be18..a32a652 100755 --- a/lib/persistence.js +++ b/lib/persistence.js @@ -1319,6 +1319,44 @@ persistence.get = function(arg1, arg2) { } } + function stringLike(s, pattern, escapeChar) { + escapeChar = escapeChar || ''; + + function isSpecial(c) { + return c === '%' || c === '_'; + } + + function isEscapeChar(c) { + return escapeChar && c === escapeChar; + } + + function patternToRe() { + var re = '^'; + var c; + for (var i = 0, len = pattern.length; i < len; i++) { + c = pattern[i]; + if (isEscapeChar(c)) { + c = pattern[i + 1]; + if (isSpecial(c) || isEscapeChar(c)) { + re += c; + i++; + } + } else if (isSpecial(c)) { + re += '.'; + if (c === '%') { + re += '*'; + } + } else { + re += '[' + c + ']'; + } + } + re += '$'; + return new RegExp(re, 'i'); + } + + return patternToRe().test(s); + } + ////////////////// QUERY COLLECTIONS \\\\\\\\\\\\\\\\\\\\\\\ function Subscription(obj, eventType, fn) { @@ -1488,10 +1526,11 @@ persistence.get = function(arg1, arg2) { * @param operator the operator to compare with * @param value the literal value to compare to */ - function PropertyFilter (property, operator, value) { + function PropertyFilter (property, operator, value, escapeChar) { this.property = property; this.operator = operator.toLowerCase(); this.value = value; + this.escapeChar = escapeChar; } PropertyFilter.prototype.match = function (o) { @@ -1529,6 +1568,12 @@ persistence.get = function(arg1, arg2) { case 'not in': return !arrayContains(value, propValue); break; + case 'like': + return stringLike(propValue, value, this.escapeChar); + break; + case 'not like': + return !stringLike(propValue, value, this.escapeChar); + break; } }; @@ -1705,12 +1750,13 @@ persistence.get = function(arg1, arg2) { * @param property the property to filter on * @param operator the operator to use * @param value the literal value that the property should match + * @param escapeChar the escape character in pattern * @return the query collection with the filter added */ - QueryCollection.prototype.filter = function (property, operator, value) { + QueryCollection.prototype.filter = function (property, operator, value, escapeChar) { var c = this.clone(true); c._filter = new AndFilter(this._filter, new PropertyFilter(property, - operator, value)); + operator, value, escapeChar)); // Add global listener (TODO: memory leak waiting to happen!) var session = this._session; c = session.uniqueQueryCollection(c); diff --git a/lib/persistence.store.sql.js b/lib/persistence.store.sql.js index ddc7b4a..d0e53ce 100755 --- a/lib/persistence.store.sql.js +++ b/lib/persistence.store.sql.js @@ -561,6 +561,16 @@ function config(persistence, dialect) { } else { return aliasPrefix + '`' + this.property + "` NOT IN (" + qs.join(', ') + ")"; } + } else if (this.operator === 'like' || this.operator === 'not like') { + var result = aliasPrefix + '`' + this.property + '` ' + this.operator.toUpperCase() + ' ' + tm.outVar('?', sqlType); + values.push(this.value); + + if (this.escapeChar) { + result += ' ESCAPE ' + tm.outVar('?', sqlType); + values.push(this.escapeChar); + } + + return result; } else { var value = this.value; if(value === true || value === false) { diff --git a/test/browser/test.persistence.js b/test/browser/test.persistence.js index 633dd0c..ac9d8f6 100644 --- a/test/browser/test.persistence.js +++ b/test/browser/test.persistence.js @@ -330,7 +330,29 @@ $(document).ready(function(){ equals(results.length, 2, "'in' filter test"); coll.filter("name", "not in", ["q", "x"]).list(function(results) { equals(results.length, 24, "'not in' filter test"); - callback(); + coll.filter("name", "like", "%a%").list(function(results) { + equals(results.length, 1, "'like %a%' filter test"); + coll.filter("name", "like", "%a").list(function(results) { + equals(results.length, 1, "'like %a' filter test"); + coll.filter("name", "like", "a%").list(function(results) { + equals(results.length, 1, "'like a%' filter test"); + coll.filter("name", "like", "_").list(function(results) { + equals(results.length, 26, "'like _' filter test"); + coll.filter("name", "not like", "%a%").list(function(results) { + equals(results.length, 25, "'not like %a%' filter test"); + coll.filter("name", "not like", "_").list(function(results) { + equals(results.length, 0, "'not like _' filter test"); + coll.add(new Task({name: 'Task %'})); + coll.filter("name", "like", "%!%", "!").list(function(results) { + equals(results.length, 1, "'like with escape' filter test"); + callback(); + }); + }); + }); + }); + }); + }); + }); }); }); });