diff --git a/src/40select.js b/src/40select.js index da8f6a8f42..53a1b98335 100755 --- a/src/40select.js +++ b/src/40select.js @@ -525,7 +525,42 @@ function modify(query, res) { var modifier = query.modifier || alasql.options.modifier; var columns = query.columns; - if (typeof columns === 'undefined' || columns.length == 0) { + + // If dirtyColumns is true, we need to merge columns from data with existing columns + // This happens when SELECT * is used with dynamic data sources (like parameters) + if (query.dirtyColumns && res.length > 0) { + var allcol = {}; + // First, scan the data to find all column names + for (var i = Math.min(res.length, alasql.options.columnlookup || 10) - 1; 0 <= i; i--) { + for (var key in res[i]) { + allcol[key] = true; + } + } + + // Create columns from data + var dataColumns = Object.keys(allcol).map(function (columnid) { + return {columnid: columnid}; + }); + + // If we don't have any columns yet, just use the data columns + if (!columns || columns.length === 0) { + columns = dataColumns; + } else { + // We have some columns (e.g., from explicit column expressions), + // merge them with data columns, avoiding duplicates + var existingColumnIds = {}; + columns.forEach(function (col) { + existingColumnIds[col.columnid] = true; + }); + + // Add data columns that aren't already in the list + dataColumns.forEach(function (col) { + if (!existingColumnIds[col.columnid]) { + columns.push(col); + } + }); + } + } else if (typeof columns === 'undefined' || columns.length === 0) { // Try to create columns if (res.length > 0) { var allcol = {}; diff --git a/test/test2070.js b/test/test2070.js new file mode 100644 index 0000000000..73849df138 --- /dev/null +++ b/test/test2070.js @@ -0,0 +1,103 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 2070 - RECORDSET OF with wildcard and additional columns', function () { + it('1. RECORDSET OF SELECT t.*, additional_column returns all columns', function (done) { + var data = [ + {a: 1, b: 10}, + {a: 2, b: 20}, + {a: 1, b: 30}, + ]; + var res = alasql('RECORDSET OF SELECT t.*, 1 as rn FROM ? t', [data]); + + var expected = { + columns: [{columnid: 'rn'}, {columnid: 'a'}, {columnid: 'b'}], + data: [ + {rn: 1, a: 1, b: 10}, + {rn: 1, a: 2, b: 20}, + {rn: 1, a: 1, b: 30}, + ], + }; + + assert.deepEqual(res, expected); + done(); + }); + + it('2. RECORDSET OF SELECT *, additional_column returns all columns', function (done) { + var data = [ + {a: 1, b: 10}, + {a: 2, b: 20}, + ]; + var res = alasql('RECORDSET OF SELECT *, 1 as rn FROM ? t', [data]); + + var expected = { + columns: [{columnid: 'rn'}, {columnid: 'a'}, {columnid: 'b'}], + data: [ + {rn: 1, a: 1, b: 10}, + {rn: 1, a: 2, b: 20}, + ], + }; + + assert.deepEqual(res, expected); + done(); + }); + + it('3. RECORDSET OF SELECT t.* still works correctly', function (done) { + var data = [ + {a: 1, b: 10}, + {a: 2, b: 20}, + ]; + var res = alasql('RECORDSET OF SELECT t.* FROM ? t', [data]); + + var expected = { + columns: [{columnid: 'a'}, {columnid: 'b'}], + data: [ + {a: 1, b: 10}, + {a: 2, b: 20}, + ], + }; + + assert.deepEqual(res, expected); + done(); + }); + + it('4. RECORDSET OF SELECT explicit columns works correctly', function (done) { + var data = [ + {a: 1, b: 10}, + {a: 2, b: 20}, + ]; + var res = alasql('RECORDSET OF SELECT a, b, 1 as rn FROM ? t', [data]); + + var expected = { + columns: [{columnid: 'a'}, {columnid: 'b'}, {columnid: 'rn'}], + data: [ + {a: 1, b: 10, rn: 1}, + {a: 2, b: 20, rn: 1}, + ], + }; + + assert.deepEqual(res, expected); + done(); + }); + + it('5. RECORDSET OF SELECT with multiple additional columns', function (done) { + var data = [ + {a: 1, b: 10}, + {a: 2, b: 20}, + ]; + var res = alasql('RECORDSET OF SELECT t.*, 1 as rn, 2 as seq FROM ? t', [data]); + + var expected = { + columns: [{columnid: 'rn'}, {columnid: 'seq'}, {columnid: 'a'}, {columnid: 'b'}], + data: [ + {rn: 1, seq: 2, a: 1, b: 10}, + {rn: 1, seq: 2, a: 2, b: 20}, + ], + }; + + assert.deepEqual(res, expected); + done(); + }); +});