diff --git a/src/69while.js b/src/69while.js index 4371b640fa..d692fa9eeb 100755 --- a/src/69while.js +++ b/src/69while.js @@ -1,11 +1,24 @@ /* // -// CREATE VIEW for Alasql.js +// WHILE, BREAK, CONTINUE, and BEGIN...END for Alasql.js // Date: 03.11.2014 // (c) 2014, Andrey Gershun // */ +// Control flow exception types +yy.BreakException = function () { + this.message = 'BREAK'; +}; +yy.BreakException.prototype = Object.create(Error.prototype); +yy.BreakException.prototype.constructor = yy.BreakException; + +yy.ContinueException = function () { + this.message = 'CONTINUE'; +}; +yy.ContinueException.prototype = Object.create(Error.prototype); +yy.ContinueException.prototype.constructor = yy.ContinueException; + yy.While = function (params) { return Object.assign(this, params); }; @@ -41,8 +54,18 @@ yy.While.prototype.execute = function (databaseid, params, cb) { loop(); } else { while (fn(params, alasql)) { - var res1 = self.loopstat.execute(databaseid, params); - res.push(res1); + try { + var res1 = self.loopstat.execute(databaseid, params); + res.push(res1); + } catch (err) { + if (err instanceof yy.BreakException) { + break; + } else if (err instanceof yy.ContinueException) { + continue; + } else { + throw err; + } + } } } return res; @@ -57,9 +80,7 @@ yy.Break.prototype.toString = function () { }; yy.Break.prototype.execute = function (databaseid, params, cb, scope) { - var res = 1; - if (cb) res = cb(res); - return res; + throw new yy.BreakException(); }; yy.Continue = function (params) { @@ -71,9 +92,7 @@ yy.Continue.prototype.toString = function () { }; yy.Continue.prototype.execute = function (databaseid, params, cb, scope) { - var res = 1; - if (cb) res = cb(res); - return res; + throw new yy.ContinueException(); }; yy.BeginEnd = function (params) { @@ -88,15 +107,24 @@ yy.BeginEnd.prototype.execute = function (databaseid, params, cb, scope) { var self = this; var res = []; - var idx = 0; - runone(); - function runone() { - self.statements[idx].execute(databaseid, params, function (data) { - res.push(data); - idx++; - if (idx < self.statements.length) return runone(); - if (cb) res = cb(res); - }); + if (cb) { + // Asynchronous execution with callback + var idx = 0; + runone(); + function runone() { + self.statements[idx].execute(databaseid, params, function (data) { + res.push(data); + idx++; + if (idx < self.statements.length) return runone(); + if (cb) res = cb(res); + }); + } + } else { + // Synchronous execution + for (var i = 0; i < self.statements.length; i++) { + var res1 = self.statements[i].execute(databaseid, params); + res.push(res1); + } } return res; }; diff --git a/test/test37.js b/test/test37.js new file mode 100644 index 0000000000..3674c1ccd8 --- /dev/null +++ b/test/test37.js @@ -0,0 +1,148 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 37 - WHILE with BREAK and CONTINUE statements', function () { + /** + * SQL-99 standard defines: + * - WHILE condition DO statement END WHILE + * - LOOP can be terminated with BREAK/LEAVE + * - CONTINUE/ITERATE skips to next iteration + * + * For compatibility with T-SQL syntax: + * - WHILE condition BEGIN statements END + * - BREAK exits the loop + * - CONTINUE skips to next iteration + */ + + it('1. Simple WHILE loop without BREAK/CONTINUE', function () { + var res = alasql('SET @a = 0; WHILE @a < 3 BEGIN SET @a = @a + 1; END; SELECT @a as a'); + assert.deepEqual(res[res.length - 1], [{a: 3}]); + }); + + it('2. WHILE with BREAK - exit loop early', function () { + var res = alasql( + 'SET @a = 0; WHILE @a < 10 BEGIN SET @a = @a + 1; IF @a = 5 BREAK; END; SELECT @a as a' + ); + assert.deepEqual(res[res.length - 1], [{a: 5}]); + }); + + it('3. WHILE with CONTINUE - skip iteration', function () { + var res = alasql( + 'SET @mysum = 0; SET @i = 0; WHILE @i < 5 BEGIN SET @i = @i + 1; IF @i = 3 CONTINUE; SET @mysum = @mysum + @i; END; SELECT @mysum as mysum' + ); + // Should sum: 1 + 2 + 4 + 5 = 12 (skipping 3) + assert.deepEqual(res[res.length - 1], [{mysum: 12}]); + }); + + it('4. BREAK immediately (condition true, break on first iteration)', function () { + var res = alasql( + 'SET @a = 1; WHILE @a < 10 BEGIN IF @a = 1 BREAK; SET @a = @a + 1; END; SELECT @a as a' + ); + assert.deepEqual(res[res.length - 1], [{a: 1}]); + }); + + it('5. Example from issue - WHILE with BREAK and CONTINUE (original logic)', function () { + // Using the exact example from the issue + var res = alasql(` + SET @a = 1; + WHILE @a < 10 + BEGIN + IF @a = 8 BREAK; + SET @a = @a + 1; + IF @a = 5 CONTINUE; + SET @a = @a + 1; + END; + SELECT @a as a + `); + // This example demonstrates the syntax but the CONTINUE never actually triggers + // because by the time @a is checked against 5, it's been incremented past 5 + // The BREAK does trigger when @a reaches 8 + assert.deepEqual(res[res.length - 1], [{a: 11}]); + }); + + it('5b. Better example - WHILE with BREAK and CONTINUE that actually triggers', function () { + // Modified to show CONTINUE actually working + var res = alasql(` + SET @a = 0; + SET @result = 0; + WHILE @a < 10 + BEGIN + SET @a = @a + 1; + IF @a = 8 BREAK; + IF @a % 2 = 0 CONTINUE; + SET @result = @result + @a; + END; + SELECT @result as result + `); + // Adds odd numbers: 1 + 3 + 5 + 7 = 16 + assert.deepEqual(res[res.length - 1], [{result: 16}]); + }); + + it('6. CONTINUE multiple times in one loop', function () { + var res = alasql(` + SET @cnt = 0; + SET @i = 0; + WHILE @i < 10 + BEGIN + SET @i = @i + 1; + IF @i % 2 = 0 CONTINUE; + SET @cnt = @cnt + 1; + END; + SELECT @cnt as cnt + `); + // Count only odd numbers: 1, 3, 5, 7, 9 = 5 + assert.deepEqual(res[res.length - 1], [{cnt: 5}]); + }); + + it('7. WHILE with nested IF and BREAK', function () { + var res = alasql(` + SET @x = 0; + WHILE @x < 100 + BEGIN + SET @x = @x + 1; + IF @x > 5 + BEGIN + IF @x = 7 BREAK; + END; + END; + SELECT @x as x + `); + assert.deepEqual(res[res.length - 1], [{x: 7}]); + }); + + it('8. WHILE loop that completes without BREAK', function () { + var res = alasql(` + SET @mysum = 0; + SET @i = 1; + WHILE @i <= 5 + BEGIN + SET @mysum = @mysum + @i; + SET @i = @i + 1; + IF @i > 100 BREAK; + END; + SELECT @mysum as mysum + `); + // Sum: 1 + 2 + 3 + 4 + 5 = 15 + assert.deepEqual(res[res.length - 1], [{mysum: 15}]); + }); + + it('9. Empty loop with immediate BREAK', function () { + var res = alasql('SET @a = 1; WHILE @a < 100 BEGIN BREAK; END; SELECT @a as a'); + assert.deepEqual(res[res.length - 1], [{a: 1}]); + }); + + it('10. CONTINUE at end of loop (should work like normal iteration)', function () { + var res = alasql(` + SET @a = 0; + WHILE @a < 3 + BEGIN + SET @a = @a + 1; + CONTINUE; + END; + SELECT @a as a + `); + assert.deepEqual(res[res.length - 1], [{a: 3}]); + }); +});