From 7ac9fe568e71b36e0034b3f705f245b493d46e36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:16:00 +0000 Subject: [PATCH 1/5] Initial plan From 064b5ed98e4ce8d81bb588ab6165faf167177634 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:20:30 +0000 Subject: [PATCH 2/5] Implement FORMAT function with MySQL-style numeric formatting Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/55functions.js | 53 ++++++++++++++++++++++++ test/test105-B.js | 100 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 test/test105-B.js diff --git a/src/55functions.js b/src/55functions.js index 6b0de2e14b..440ce90265 100644 --- a/src/55functions.js +++ b/src/55functions.js @@ -500,3 +500,56 @@ stdfn.NEWID = lut[(d3 >> 24) & 0xff] ); }; + +/** + * FORMAT(value, decimals[, locale]) + * + * Formats a number with thousands separators and specified decimal places. + * This combines features from MySQL FORMAT() and T-SQL FORMAT() functions. + * + * MySQL-style: FORMAT(number, decimals) - formats with comma thousands separators + * + * @param {number|string} value - The number to format (can be string that converts to number) + * @param {number} decimals - Number of decimal places + * @param {string} locale - Optional locale (reserved for future use) + * @returns {string|null} Formatted number string or null if value is null + */ +stdfn.FORMAT = function (value, decimals, locale) { + // Handle null values + if (value === null || value === undefined) { + return null; + } + + // Convert to number + var num = Number(value); + + // Handle NaN + if (isNaN(num)) { + return null; + } + + // Round to specified decimal places + var factor = Math.pow(10, decimals); + num = Math.round(num * factor) / factor; + + // Split into integer and decimal parts + var negative = num < 0; + var parts = Math.abs(num).toFixed(decimals).split('.'); + var integerPart = parts[0]; + var decimalPart = parts[1]; + + // Add thousands separators + var formatted = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); + + // Add decimal part if needed + if (decimals > 0) { + formatted += '.' + decimalPart; + } + + // Add negative sign if needed + if (negative) { + formatted = '-' + formatted; + } + + return formatted; +}; diff --git a/test/test105-B.js b/test/test105-B.js new file mode 100644 index 0000000000..d3534b8308 --- /dev/null +++ b/test/test105-B.js @@ -0,0 +1,100 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 105-B - FORMAT function', function () { + const test = '105B'; + + before(function () { + alasql('create database test' + test); + alasql('use test' + test); + }); + + after(function () { + alasql('drop database test' + test); + }); + + it('A) MySQL-style numeric formatting - FORMAT(number, decimals)', function () { + // Basic number formatting with 2 decimal places + var res = alasql('SELECT VALUE FORMAT(12332.123456, 2)'); + assert.equal(res, '12,332.12'); + + // Single decimal place + res = alasql('SELECT VALUE FORMAT(12332.2, 1)'); + assert.equal(res, '12,332.2'); + + // No decimal places + res = alasql('SELECT VALUE FORMAT(12332.2, 0)'); + assert.equal(res, '12,332'); + + // Small number + res = alasql('SELECT VALUE FORMAT(123.456, 2)'); + assert.equal(res, '123.46'); + }); + + it('B) FORMAT with negative numbers', function () { + var res = alasql('SELECT VALUE FORMAT(-12332.123456, 2)'); + assert.equal(res, '-12,332.12'); + + res = alasql('SELECT VALUE FORMAT(-999.99, 0)'); + assert.equal(res, '-1,000'); + }); + + it('C) FORMAT with zero and NULL', function () { + var res = alasql('SELECT VALUE FORMAT(0, 2)'); + assert.equal(res, '0.00'); + + res = alasql('SELECT VALUE FORMAT(NULL, 2)'); + assert.equal(res, null); + }); + + it('D) FORMAT in table queries', function () { + alasql('CREATE TABLE prices (id INT, price DECIMAL(10,2))'); + alasql('INSERT INTO prices VALUES (1, 1234.567), (2, 98765.4321), (3, 0.99)'); + + var res = alasql('SELECT id, FORMAT(price, 2) AS formatted FROM prices ORDER BY id'); + assert.deepEqual(res, [ + {id: 1, formatted: '1,234.57'}, + {id: 2, formatted: '98,765.43'}, + {id: 3, formatted: '0.99'}, + ]); + + alasql('DROP TABLE prices'); + }); + + it('E) FORMAT with different decimal precision', function () { + var res = alasql('SELECT VALUE FORMAT(1234.56789, 0)'); + assert.equal(res, '1,235'); + + res = alasql('SELECT VALUE FORMAT(1234.56789, 1)'); + assert.equal(res, '1,234.6'); + + res = alasql('SELECT VALUE FORMAT(1234.56789, 3)'); + assert.equal(res, '1,234.568'); + + res = alasql('SELECT VALUE FORMAT(1234.56789, 4)'); + assert.equal(res, '1,234.5679'); + }); + + it('F) FORMAT with very large numbers', function () { + var res = alasql('SELECT VALUE FORMAT(1234567890.123, 2)'); + assert.equal(res, '1,234,567,890.12'); + + res = alasql('SELECT VALUE FORMAT(999999999999.99, 2)'); + assert.equal(res, '999,999,999,999.99'); + }); + + it('G) FORMAT with numbers less than 1', function () { + var res = alasql('SELECT VALUE FORMAT(0.123456, 2)'); + assert.equal(res, '0.12'); + + res = alasql('SELECT VALUE FORMAT(0.999, 0)'); + assert.equal(res, '1'); + }); + + it('H) FORMAT with string numbers', function () { + var res = alasql("SELECT VALUE FORMAT('1234.567', 2)"); + assert.equal(res, '1,234.57'); + }); +}); From f035d6888eb5b09790e68f428aba3e726ada0262 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:22:54 +0000 Subject: [PATCH 3/5] Address code review feedback: simplify rounding logic and fix code style Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/55functions.js | 6 +----- test/test105-B.js | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/55functions.js b/src/55functions.js index 440ce90265..67fce5afc2 100644 --- a/src/55functions.js +++ b/src/55functions.js @@ -528,11 +528,7 @@ stdfn.FORMAT = function (value, decimals, locale) { return null; } - // Round to specified decimal places - var factor = Math.pow(10, decimals); - num = Math.round(num * factor) / factor; - - // Split into integer and decimal parts + // Split into integer and decimal parts using toFixed for proper rounding var negative = num < 0; var parts = Math.abs(num).toFixed(decimals).split('.'); var integerPart = parts[0]; diff --git a/test/test105-B.js b/test/test105-B.js index d3534b8308..e14fed9ba2 100644 --- a/test/test105-B.js +++ b/test/test105-B.js @@ -4,7 +4,7 @@ if (typeof exports === 'object') { } describe('Test 105-B - FORMAT function', function () { - const test = '105B'; + var test = '105B'; before(function () { alasql('create database test' + test); From 7fe3a163735e152d6b79ce0f7ed0ee3842f0dde0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:26:25 +0000 Subject: [PATCH 4/5] Use const for test variable to follow modern JavaScript practices Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- test/test105-B.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test105-B.js b/test/test105-B.js index e14fed9ba2..d3534b8308 100644 --- a/test/test105-B.js +++ b/test/test105-B.js @@ -4,7 +4,7 @@ if (typeof exports === 'object') { } describe('Test 105-B - FORMAT function', function () { - var test = '105B'; + const test = '105B'; before(function () { alasql('create database test' + test); From b60a612f233331230d141254b9006d56bc504780 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:22:29 +0000 Subject: [PATCH 5/5] Refactor FORMAT function to be more concise using destructuring Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/55functions.js | 47 ++++++---------------------------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/src/55functions.js b/src/55functions.js index 67fce5afc2..fff826784d 100644 --- a/src/55functions.js +++ b/src/55functions.js @@ -503,49 +503,14 @@ stdfn.NEWID = /** * FORMAT(value, decimals[, locale]) - * - * Formats a number with thousands separators and specified decimal places. - * This combines features from MySQL FORMAT() and T-SQL FORMAT() functions. - * - * MySQL-style: FORMAT(number, decimals) - formats with comma thousands separators - * - * @param {number|string} value - The number to format (can be string that converts to number) - * @param {number} decimals - Number of decimal places - * @param {string} locale - Optional locale (reserved for future use) - * @returns {string|null} Formatted number string or null if value is null + * MySQL-style: formats number with comma thousands separators and decimal places */ stdfn.FORMAT = function (value, decimals, locale) { - // Handle null values - if (value === null || value === undefined) { - return null; - } - - // Convert to number + if (value == null) return null; var num = Number(value); - - // Handle NaN - if (isNaN(num)) { - return null; - } - - // Split into integer and decimal parts using toFixed for proper rounding - var negative = num < 0; - var parts = Math.abs(num).toFixed(decimals).split('.'); - var integerPart = parts[0]; - var decimalPart = parts[1]; - - // Add thousands separators + if (isNaN(num)) return null; + var [integerPart, decimalPart] = Math.abs(num).toFixed(decimals).split('.'); var formatted = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); - - // Add decimal part if needed - if (decimals > 0) { - formatted += '.' + decimalPart; - } - - // Add negative sign if needed - if (negative) { - formatted = '-' + formatted; - } - - return formatted; + if (decimals > 0) formatted += '.' + decimalPart; + return (num < 0 ? '-' : '') + formatted; };