diff --git a/src/15utility.js b/src/15utility.js index 5f2c40559b..b89d6451f9 100755 --- a/src/15utility.js +++ b/src/15utility.js @@ -934,6 +934,18 @@ var arrayIntersectDeep = (utils.arrayIntersectDeep = function (a, b) { return r; }); +/** + SUBSTRING with MySQL semantics for a non-positive start: a start of 0 returns + '', a negative start counts from the end of the string, and a start that + reaches before the string start returns ''. `len` is optional. + */ +var substr = (utils.substr = function substr(str, start, len) { + if (start == 0) return ''; + var from = start < 0 ? str.length + start : start - 1; + if (from < 0) return ''; + return len === undefined ? str.substr(from) : str.substr(from, len); +}); + /** Deep clone objects */ diff --git a/src/55functions.js b/src/55functions.js index 9bdc42ff24..627d0cc773 100644 --- a/src/55functions.js +++ b/src/55functions.js @@ -186,8 +186,8 @@ stdlib.SUBSTRING = stdlib.SUBSTR = stdlib.MID = function (a, b, c) { - if (arguments.length == 2) return und(a, 'y.substr(' + b + '-1)'); - else if (arguments.length == 3) return und(a, 'y.substr(' + b + '-1,' + c + ')'); + if (arguments.length == 2) return und(a, 'alasql.utils.substr(y,' + b + ')'); + else if (arguments.length == 3) return und(a, 'alasql.utils.substr(y,' + b + ',' + c + ')'); }; stdfn.REGEXP_LIKE = function (a, b, c) { diff --git a/test/test-substring-negative-start.js b/test/test-substring-negative-start.js new file mode 100644 index 0000000000..a07e86b525 --- /dev/null +++ b/test/test-substring-negative-start.js @@ -0,0 +1,35 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('SUBSTRING with non-positive start position', function () { + it('follows MySQL semantics for start = 0', function () { + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', 0, 3)"), ''); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', 0)"), ''); + }); + + it('follows MySQL semantics for a negative start (counts from the end)', function () { + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -1, 3)"), 'f'); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -2, 3)"), 'ef'); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -2)"), 'ef'); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -6)"), 'abcdef'); + }); + + it('returns empty when a negative start reaches past the string start', function () { + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -7, 3)"), ''); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -10, 3)"), ''); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', -7)"), ''); + }); + + it('leaves positive start positions unchanged', function () { + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', 1, 3)"), 'abc'); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', 2, 3)"), 'bcd'); + assert.strictEqual(alasql("SELECT VALUE SUBSTRING('abcdef', 4)"), 'def'); + }); + + it('applies the same rules to the SUBSTR and MID aliases', function () { + assert.strictEqual(alasql("SELECT VALUE SUBSTR('abcdef', 0, 3)"), ''); + assert.strictEqual(alasql("SELECT VALUE MID('abcdef', -2, 3)"), 'ef'); + }); +});