diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..7c71a0e5b7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +# Commits to leave out of git blame: formatting only. GitHub reads this file; a clone sets +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Phase 1 of the ES6 conversion, step 10: the one Prettier run over the converted modules +fac32eef19572baff3c9e0ef3e6b44c13cc3025d diff --git a/.prettierignore b/.prettierignore index a1d91a81d0..7e16c8c293 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,19 +7,9 @@ test/test238.json build/ test/lib/ +test/coverage/ src/generated/ -# Converted modules keep their old indentation until the end of Phase 1 (context/roadmap-phase-1.md) -src/engine/ -src/utils/ -src/data/ -src/select/ -src/expression/ -src/functions/ -src/graph/ -src/statements/ -src/console/ -src/dialects/ -src/formats/ -src/storage/ -src/worker/ +# The head and tail of the worker concatenation (build.js) are not files Prettier can parse +src/worker/start.js +src/worker/finish.js diff --git a/docs/src-map.md b/docs/src-map.md index 2e74424551..0a161272cb 100644 --- a/docs/src-map.md +++ b/docs/src-map.md @@ -1,6 +1,6 @@ # Source map of the ES6 conversion -One row per numbered file that has moved (`context/roadmap.md`, "The source map"): where its code lives now, what the module exports, and what the wrap added. A fix made against the old file on `develop` is redone in the module this table names when the merge cannot carry it. The body of a moved file keeps its indentation until the end of Phase 1, so git sees the move; the similarity column is `git diff -M --stat` between the file before the move and the module after the wrap. +One row per numbered file that has moved (`context/roadmap.md`, "The source map"): where its code lives now, what the module exports, and what the wrap added. The similarity column is git's figure at the wrap commit, before step 10 formatted every converted module with Prettier in one commit (its hash is in `.git-blame-ignore-revs`, so blame still reaches the numbered files). A fix made against the old file on `develop` is redone in the module this table names when the merge cannot carry it. The body of a moved file keeps its indentation until the end of Phase 1, so git sees the move; the similarity column is `git diff -M --stat` between the file before the move and the module after the wrap. | Old file | Module | Exports | Installed | Added by the wrap | Similarity | | --- | --- | --- | --- | --- | --- | diff --git a/package.json b/package.json index ad2deadd53..0785c8fc1a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "test-ci": "(yarn test-format-all || 1) && yarn test-only && yarn install-g && alasql 'select 1 as Succes'", "test-only": "bun test --dots --preload ./test/bun-setup.js ./test/test[0-9]*.js ./test/test-*.js", "test-browser": "node test/browserTestRunner.js 7387", - "test-parity": "yarn build-only && bun test/parity/run.js record ref --ref --if-missing && bun test/parity/run.js record new --once && bun test/parity/run.js compare ref new", + "test-parity": "yarn build-only && bun test/parity/run.js record ref-044ab07a --ref 044ab07a --if-missing && bun test/parity/run.js record new --once && bun test/parity/run.js compare ref-044ab07a new", "test-parity-smoke": "node test/parity/smoke.js --node=$(which node) --pack", "test-cover": "# istanbul cover -x 'lib/zt/zt.js' --dir test/coverage _mocha", "build": "yarn format && yarn build-only", @@ -40,7 +40,7 @@ "format": "yarn pretty-since-dev --write", "format-all": "yarn pretty-all --write", "test-format": "yarn pretty-since-dev --list-different || (echo 'Please correct file formatting using `yarn format` and try again.' && exit 1)", - "test-format-all": "prettier --list-different '{.,src,test}/*.{js,ts,json}' || (echo 'Please correct file formatting using `yarn format-all` and try again.' && exit 1)", + "test-format-all": "prettier --list-different '{.,src,test}/**/*.{js,ts,json}' || (echo 'Please correct file formatting using `yarn format-all` and try again.' && exit 1)", "pretty-since-dev": "{ git diff --name-only --diff-filter=d origin/develop ; git diff --name-only --diff-filter=d --staged origin/develop ; } | sort | uniq | grep -vE '^dist/|^test/coverage|^lib/|.min.js$' | grep -E '\\.(scss|css|js|ts|vue|json)$' | xargs npx prettier", "pretty-commit": "{ git diff --name-only --diff-filter=d ; git diff --name-only --diff-filter=d --staged ; } | sort | uniq | grep -vE '^dist/|^test/coverage|^lib/|.min.js$' | grep -E '\\.(scss|css|js|ts|vue|json)$' | xargs npx prettier", "pretty-all": "git ls-tree --full-tree --name-only -r HEAD | grep -vE '^dist/|^test/coverage|^lib/|.min.js$' | grep -E '\\.(scss|css|js|ts|vue|json)$' | xargs npx prettier", diff --git a/src/console/console.js b/src/console/console.js index 264e4e1768..f77f04bc54 100755 --- a/src/console/console.js +++ b/src/console/console.js @@ -1,317 +1,317 @@ export default function install(alasql, internal) { var utils = alasql.utils; var JSONtoString = internal.JSONtoString; -alasql.test = function (name, times, fn) { - if (arguments.length === 0) { - alasql.log(alasql.con.results); - return; - } - - var tm = Date.now(); + alasql.test = function (name, times, fn) { + if (arguments.length === 0) { + alasql.log(alasql.con.results); + return; + } - if (arguments.length === 1) { - fn(); - alasql.con.log(Date.now() - tm); - return; - } + var tm = Date.now(); - if (arguments.length === 2) { - fn = times; - times = 1; - } + if (arguments.length === 1) { + fn(); + alasql.con.log(Date.now() - tm); + return; + } - for (var i = 0; i < times; i++) { - fn(); - } - alasql.con.results[name] = Date.now() - tm; -}; + if (arguments.length === 2) { + fn = times; + times = 1; + } -// Console -// alasql.log = function(sql, params) { -// var res; -// if(typeof sql == "string") { -// res = alasql(sql, params); -// } else { -// res = sql; -// }; -// if(Array.isArray(res)) { -// if(console.table) { -// console.table(res); -// } else { -// console.log(res); -// } -// } else { -// console.log(res); -// } -// }; + for (var i = 0; i < times; i++) { + fn(); + } + alasql.con.results[name] = Date.now() - tm; + }; -/* global alasql, yy, utils */ + // Console + // alasql.log = function(sql, params) { + // var res; + // if(typeof sql == "string") { + // res = alasql(sql, params); + // } else { + // res = sql; + // }; + // if(Array.isArray(res)) { + // if(console.table) { + // console.table(res); + // } else { + // console.log(res); + // } + // } else { + // console.log(res); + // } + // }; -// Console -alasql.log = function (sql, params) { - var olduseid = alasql.useid; - var target = alasql.options.logtarget; - // For node other - if (utils.isNode) { - target = 'console'; - } + /* global alasql, yy, utils */ - var res; - if (typeof sql === 'string') { - res = alasql(sql, params); - } else { - res = sql; - } + // Console + alasql.log = function (sql, params) { + var olduseid = alasql.useid; + var target = alasql.options.logtarget; + // For node other + if (utils.isNode) { + target = 'console'; + } - // For Node and console.output - if (target === 'console' || utils.isNode) { - if (typeof sql === 'string' && alasql.options.logprompt) { - console.log(olduseid + '>', sql); + var res; + if (typeof sql === 'string') { + res = alasql(sql, params); + } else { + res = sql; } - if (Array.isArray(res)) { - if (console.table) { - // For Chrome and other consoles - console.table(res); + // For Node and console.output + if (target === 'console' || utils.isNode) { + if (typeof sql === 'string' && alasql.options.logprompt) { + console.log(olduseid + '>', sql); + } + + if (Array.isArray(res)) { + if (console.table) { + // For Chrome and other consoles + console.table(res); + } else { + // Add print procedure + console.log(JSONtoString(res)); + } } else { - // Add print procedure console.log(JSONtoString(res)); } } else { - console.log(JSONtoString(res)); - } - } else { - var el; - if (target === 'output') { - el = document.getElementsByTagName('output')[0]; - } else { - if (typeof target === 'string') { - el = document.getElementById(target); + var el; + if (target === 'output') { + el = document.getElementsByTagName('output')[0]; } else { - // in case of DOM - el = target; + if (typeof target === 'string') { + el = document.getElementById(target); + } else { + // in case of DOM + el = target; + } } - } - var s = ''; + var s = ''; - if (typeof sql === 'string' && alasql.options.logprompt) { - // s += '

'+olduseid+'> '+alasql.pretty(sql)+'

'; - s += '
' + alasql.pretty(sql) + '
'; - } + if (typeof sql === 'string' && alasql.options.logprompt) { + // s += '

'+olduseid+'> '+alasql.pretty(sql)+'

'; + s += '
' + alasql.pretty(sql) + '
'; + } - if (Array.isArray(res)) { - if (res.length === 0) { - s += '

[ ]

'; - } else if (typeof res[0] !== 'object' || Array.isArray(res[0])) { - for (var i = 0, ilen = res.length; i < ilen; i++) { - s += '

' + loghtml(res[i]) + '

'; + if (Array.isArray(res)) { + if (res.length === 0) { + s += '

[ ]

'; + } else if (typeof res[0] !== 'object' || Array.isArray(res[0])) { + for (var i = 0, ilen = res.length; i < ilen; i++) { + s += '

' + loghtml(res[i]) + '

'; + } + } else { + s += loghtml(res); } } else { s += loghtml(res); } - } else { - s += loghtml(res); + el.innerHTML += s; } - el.innerHTML += s; - } -}; + }; -alasql.clear = function () { - var target = alasql.options.logtarget; - // For node other + alasql.clear = function () { + var target = alasql.options.logtarget; + // For node other - if (utils.isNode || utils.isMeteorServer) { - if (console.clear) { - console.clear(); - } - } else { - var el; - if (target === 'output') { - el = document.getElementsByTagName('output')[0]; + if (utils.isNode || utils.isMeteorServer) { + if (console.clear) { + console.clear(); + } } else { - if (typeof target === 'string') { - el = document.getElementById(target); + var el; + if (target === 'output') { + el = document.getElementsByTagName('output')[0]; } else { - // in case of DOM - el = target; + if (typeof target === 'string') { + el = document.getElementById(target); + } else { + // in case of DOM + el = target; + } } + el.innerHTML = ''; } - el.innerHTML = ''; - } -}; + }; -alasql.write = function (s) { - // console.log('write',s); - var target = alasql.options.logtarget; - // For node other - if (utils.isNode || utils.isMeteorServer) { - if (console.log) { - console.log(s); - } - } else { - var el; - if (target === 'output') { - el = document.getElementsByTagName('output')[0]; + alasql.write = function (s) { + // console.log('write',s); + var target = alasql.options.logtarget; + // For node other + if (utils.isNode || utils.isMeteorServer) { + if (console.log) { + console.log(s); + } } else { - if (typeof target === 'string') { - el = document.getElementById(target); + var el; + if (target === 'output') { + el = document.getElementsByTagName('output')[0]; } else { - // in case of DOM - el = target; + if (typeof target === 'string') { + el = document.getElementById(target); + } else { + // in case of DOM + el = target; + } } + el.innerHTML += s; } - el.innerHTML += s; - } -}; + }; -function loghtml(res) { - // console.log(res); - var s = ''; - if (res === undefined) { - s += 'undefined'; - } else if (Array.isArray(res)) { - s += ''; - s += ''; - var cols = []; - for (var colid in res[0]) { - cols.push(colid); - } - s += '
#'; - cols.forEach(function (colid) { - s += '' + colid; - }); - for (var i = 0, ilen = res.length; i < ilen; i++) { - s += '
' + (i + 1); + function loghtml(res) { + // console.log(res); + var s = ''; + if (res === undefined) { + s += 'undefined'; + } else if (Array.isArray(res)) { + s += ''; + s += ''; + var cols = []; + for (var colid in res[0]) { + cols.push(colid); + } + s += '
#'; cols.forEach(function (colid) { - s += ' '; - if (res[i][colid] == +res[i][colid]) { - // jshint ignore:line - s += '
'; - if (typeof res[i][colid] === 'undefined') { - s += 'NULL'; - } else { - s += res[i][colid]; - } - s += '
'; - } else { - if (typeof res[i][colid] === 'undefined') { - s += 'NULL'; - } else if (typeof res[i][colid] === 'string') { - s += res[i][colid]; + s += '
' + colid; + }); + for (var i = 0, ilen = res.length; i < ilen; i++) { + s += '
' + (i + 1); + cols.forEach(function (colid) { + s += ' '; + if (res[i][colid] == +res[i][colid]) { + // jshint ignore:line + s += '
'; + if (typeof res[i][colid] === 'undefined') { + s += 'NULL'; + } else { + s += res[i][colid]; + } + s += '
'; } else { - s += JSONtoString(res[i][colid]); + if (typeof res[i][colid] === 'undefined') { + s += 'NULL'; + } else if (typeof res[i][colid] === 'string') { + s += res[i][colid]; + } else { + s += JSONtoString(res[i][colid]); + } + // s += res[i][colid]; } - // s += res[i][colid]; - } - }); - } - - s += '
'; - } else { - s += '

' + JSONtoString(res) + '

'; - } - // if() {} + }); + } - // if(typeof res == 'object') { - // s += '

'+JSON.stringify(res)+'

'; - // } else { - // } - return s; -} + s += '
'; + } else { + s += '

' + JSONtoString(res) + '

'; + } + // if() {} -function scrollTo(element, to, duration) { - if (duration <= 0) { - return; + // if(typeof res == 'object') { + // s += '

'+JSON.stringify(res)+'

'; + // } else { + // } + return s; } - var difference = to - element.scrollTop; - var perTick = (difference / duration) * 10; - setTimeout(function () { - if (element.scrollTop === to) { + function scrollTo(element, to, duration) { + if (duration <= 0) { return; } - element.scrollTop = element.scrollTop + perTick; - scrollTo(element, to, duration - 10); - }, 10); -} + var difference = to - element.scrollTop; + var perTick = (difference / duration) * 10; -alasql.prompt = function (el, useidel, firstsql) { - if (utils.isNode) { - throw new Error('The prompt not realized for Node.js'); + setTimeout(function () { + if (element.scrollTop === to) { + return; + } + element.scrollTop = element.scrollTop + perTick; + scrollTo(element, to, duration - 10); + }, 10); } - var prompti = 0; + alasql.prompt = function (el, useidel, firstsql) { + if (utils.isNode) { + throw new Error('The prompt not realized for Node.js'); + } - if (typeof el === 'string') { - el = document.getElementById(el); - } + var prompti = 0; - if (typeof useidel === 'string') { - useidel = document.getElementById(useidel); - } - - useidel.textContent = alasql.useid; + if (typeof el === 'string') { + el = document.getElementById(el); + } - if (firstsql) { - alasql.prompthistory.push(firstsql); - prompti = alasql.prompthistory.length; - try { - var tm = Date.now(); - alasql.log(firstsql); - alasql.write('

' + (Date.now() - tm) + ' ms

'); - } catch (err) { - alasql.write('

' + alasql.useid + '> ' + firstsql + '

'); - alasql.write('

' + err + '

'); + if (typeof useidel === 'string') { + useidel = document.getElementById(useidel); } - } - var y = el.getBoundingClientRect().top + document.getElementsByTagName('body')[0].scrollTop; - scrollTo(document.getElementsByTagName('body')[0], y, 500); + useidel.textContent = alasql.useid; - el.onkeydown = function (event) { - if (event.which === 13) { - var sql = el.value; - var olduseid = alasql.useid; - el.value = ''; - alasql.prompthistory.push(sql); + if (firstsql) { + alasql.prompthistory.push(firstsql); prompti = alasql.prompthistory.length; try { var tm = Date.now(); - alasql.log(sql); + alasql.log(firstsql); alasql.write('

' + (Date.now() - tm) + ' ms

'); } catch (err) { - alasql.write('

' + olduseid + '> ' + alasql.pretty(sql, false) + '

'); + alasql.write('

' + alasql.useid + '> ' + firstsql + '

'); alasql.write('

' + err + '

'); } - el.focus(); - // console.log(el.getBoundingClientRect().top); - useidel.textContent = alasql.useid; - var y = el.getBoundingClientRect().top + document.getElementsByTagName('body')[0].scrollTop; - scrollTo(document.getElementsByTagName('body')[0], y, 500); - } else if (event.which === 38) { - prompti--; - if (prompti < 0) { - prompti = 0; - } - if (alasql.prompthistory[prompti]) { - el.value = alasql.prompthistory[prompti]; - event.preventDefault(); - } - } else if (event.which === 40) { - prompti++; - if (prompti >= alasql.prompthistory.length) { - prompti = alasql.prompthistory.length; + } + + var y = el.getBoundingClientRect().top + document.getElementsByTagName('body')[0].scrollTop; + scrollTo(document.getElementsByTagName('body')[0], y, 500); + + el.onkeydown = function (event) { + if (event.which === 13) { + var sql = el.value; + var olduseid = alasql.useid; el.value = ''; - } else if (alasql.prompthistory[prompti]) { - el.value = alasql.prompthistory[prompti]; - event.preventDefault(); + alasql.prompthistory.push(sql); + prompti = alasql.prompthistory.length; + try { + var tm = Date.now(); + alasql.log(sql); + alasql.write('

' + (Date.now() - tm) + ' ms

'); + } catch (err) { + alasql.write('

' + olduseid + '> ' + alasql.pretty(sql, false) + '

'); + alasql.write('

' + err + '

'); + } + el.focus(); + // console.log(el.getBoundingClientRect().top); + useidel.textContent = alasql.useid; + var y = el.getBoundingClientRect().top + document.getElementsByTagName('body')[0].scrollTop; + scrollTo(document.getElementsByTagName('body')[0], y, 500); + } else if (event.which === 38) { + prompti--; + if (prompti < 0) { + prompti = 0; + } + if (alasql.prompthistory[prompti]) { + el.value = alasql.prompthistory[prompti]; + event.preventDefault(); + } + } else if (event.which === 40) { + prompti++; + if (prompti >= alasql.prompthistory.length) { + prompti = alasql.prompthistory.length; + el.value = ''; + } else if (alasql.prompthistory[prompti]) { + el.value = alasql.prompthistory[prompti]; + event.preventDefault(); + } } - } + }; }; -}; } diff --git a/src/console/pretty.js b/src/console/pretty.js index de819a55ce..09e9979b81 100755 --- a/src/console/pretty.js +++ b/src/console/pretty.js @@ -1,17 +1,17 @@ export default function install(alasql) { -/** + /** 12prettyflag.js - prettify @todo move this functionality to plugin */ -/** + /** Pretty flag - nice HTML output or standard text without any tags @type {boolean} */ -alasql.prettyflag = false; + alasql.prettyflag = false; -/** + /** Pretty output of SQL functions @function @param {string} sql SQL statement @@ -19,11 +19,11 @@ alasql.prettyflag = false; @return {string} HTML or text string with pretty output */ -alasql.pretty = function (sql, flag) { - var pf = alasql.prettyflag; - alasql.prettyflag = !flag; - var s = alasql.parse(sql).toString(); - alasql.prettyflag = pf; - return s; -}; + alasql.pretty = function (sql, flag) { + var pf = alasql.prettyflag; + alasql.prettyflag = !flag; + var s = alasql.parse(sql).toString(); + alasql.prettyflag = pf; + return s; + }; } diff --git a/src/data/database.js b/src/data/database.js index a8125a46c9..e9647ed578 100755 --- a/src/data/database.js +++ b/src/data/database.js @@ -1,5 +1,5 @@ export default function install(alasql) { -/* + /* // // Database class for Alasql.js // Date: 03.11.2014 @@ -7,94 +7,94 @@ export default function install(alasql) { // */ -// Main Database class + // Main Database class -/** + /** @class Database */ -var Database = (alasql.Database = function (databaseid) { - var self = this; - // self = function(a){console.log('OK',a);} - // self.prototype = this; - - if (self === alasql) { - if (databaseid) { - // if(alasql.databases[databaseid]) { - self = alasql.databases[databaseid]; - // } else { - alasql.databases[databaseid] = self; - // } - if (!self) { - throw new Error(`Database ${databaseid} not found`); + var Database = (alasql.Database = function (databaseid) { + var self = this; + // self = function(a){console.log('OK',a);} + // self.prototype = this; + + if (self === alasql) { + if (databaseid) { + // if(alasql.databases[databaseid]) { + self = alasql.databases[databaseid]; + // } else { + alasql.databases[databaseid] = self; + // } + if (!self) { + throw new Error(`Database ${databaseid} not found`); + } + } else { + // Create new database (or get alasql?) + self = alasql.databases.alasql; + // For SQL Server examples, USE tempdb + if (alasql.options.tsql) { + alasql.databases.tempdb = alasql.databases.alasql; + } + // self = new Database(databaseid); // to call without new } - } else { - // Create new database (or get alasql?) - self = alasql.databases.alasql; - // For SQL Server examples, USE tempdb - if (alasql.options.tsql) { - alasql.databases.tempdb = alasql.databases.alasql; - } - // self = new Database(databaseid); // to call without new } - } - if (!databaseid) { - databaseid = 'db' + alasql.databasenum++; // Random name - } - - // Step 1 - self.databaseid = databaseid; - alasql.databases[databaseid] = self; - self.dbversion = 0; - - //Steps 2-5 - self.tables = {}; - self.views = {}; - self.triggers = {}; - self.indices = {}; - - // Step 6: Objects storage - self.objects = {}; - self.counter = 0; - - self.resetSqlCache(); - return self; -}); - -/** + if (!databaseid) { + databaseid = 'db' + alasql.databasenum++; // Random name + } + + // Step 1 + self.databaseid = databaseid; + alasql.databases[databaseid] = self; + self.dbversion = 0; + + //Steps 2-5 + self.tables = {}; + self.views = {}; + self.triggers = {}; + self.indices = {}; + + // Step 6: Objects storage + self.objects = {}; + self.counter = 0; + + self.resetSqlCache(); + return self; + }); + + /** Reset SQL statements cache */ -Database.prototype.resetSqlCache = function () { - this.sqlCache = {}; // Cache for compiled SQL statements - this.sqlCacheSize = 0; - this.astCache = {}; // Cache for AST objects -}; + Database.prototype.resetSqlCache = function () { + this.sqlCache = {}; // Cache for compiled SQL statements + this.sqlCacheSize = 0; + this.astCache = {}; // Cache for AST objects + }; -// Main SQL function + // Main SQL function -/** + /** Run SQL statement on database @param {string} sql SQL statement @param [object] params Parameters @param {function} cb callback */ -Database.prototype.exec = function (sql, params, cb) { - return alasql.dexec(this.databaseid, sql, params, cb); -}; + Database.prototype.exec = function (sql, params, cb) { + return alasql.dexec(this.databaseid, sql, params, cb); + }; -Database.prototype.autoval = function (tablename, colname, getNext) { - return alasql.autoval(tablename, colname, getNext, this.databaseid); -}; + Database.prototype.autoval = function (tablename, colname, getNext) { + return alasql.autoval(tablename, colname, getNext, this.databaseid); + }; -Database.prototype.transaction = function (cb) { - var tx = new alasql.Transaction(this.databaseid); - var res = cb(tx); - return res; -}; + Database.prototype.transaction = function (cb) { + var tx = new alasql.Transaction(this.databaseid); + var res = cb(tx); + return res; + }; -/*/* + /*/* // // Compile // var statement = this.compile(sql); // // Run @@ -116,8 +116,8 @@ Database.prototype.transaction = function (cb) { // }; */ -// Aliases like MS SQL -/*/* + // Aliases like MS SQL + /*/* Database.prototype.query = Database.prototype.exec; Database.prototype.run = Database.prototype.exec; Database.prototype.queryArray = function(sql, params, cb) { diff --git a/src/data/query.js b/src/data/query.js index 1a59114714..fe7c746060 100755 --- a/src/data/query.js +++ b/src/data/query.js @@ -1,5 +1,5 @@ export default function install(alasql) { -/* + /* // // Query class for Alasql.js // Date: 14.11.2014 @@ -7,35 +7,35 @@ export default function install(alasql) { // */ -// Table class + // Table class -/** + /** @class Query Main query class */ -class Query { - constructor(params) { - this.alasql = alasql; - // console.log(12,alasql); - // Columns - this.columns = []; - this.xcolumns = {}; - this.selectGroup = []; - this.groupColumns = {}; - // Data array - Object.assign(this, params); + class Query { + constructor(params) { + this.alasql = alasql; + // console.log(12,alasql); + // Columns + this.columns = []; + this.xcolumns = {}; + this.selectGroup = []; + this.groupColumns = {}; + // Data array + Object.assign(this, params); + } } -} -/** + /** @class Recordset data object */ -class Recordset { - constructor(params) { - // Data array - Object.assign(this, params); + class Recordset { + constructor(params) { + // Data array + Object.assign(this, params); + } } -} -alasql.Recordset = Recordset; -alasql.Query = Query; + alasql.Recordset = Recordset; + alasql.Query = Query; } diff --git a/src/data/table.js b/src/data/table.js index 26508326a6..c32c84bc6f 100755 --- a/src/data/table.js +++ b/src/data/table.js @@ -1,5 +1,5 @@ export default function install(alasql) { -/* + /* // // Table class for Alasql.js // Date: 14.11.2014 @@ -7,49 +7,49 @@ export default function install(alasql) { // */ -// Table class -var Table = (alasql.Table = function (params) { - // Step 1: Data array - this.data = []; + // Table class + var Table = (alasql.Table = function (params) { + // Step 1: Data array + this.data = []; - // Step 2: Columns - this.columns = []; - this.xcolumns = {}; + // Step 2: Columns + this.columns = []; + this.xcolumns = {}; - // Step 3: indices - this.inddefs = {}; - this.indices = {}; - this.uniqs = {}; - this.uniqdefs = {}; + // Step 3: indices + this.inddefs = {}; + this.indices = {}; + this.uniqs = {}; + this.uniqdefs = {}; - // Step 4: identities - this.identities = {}; + // Step 4: identities + this.identities = {}; - // Step 5: checkfn... - this.checks = []; - this.checkfns = []; // For restore... to be done... + // Step 5: checkfn... + this.checks = []; + this.checkfns = []; // For restore... to be done... - // Step 6: INSERT/DELETE/UPDATE + // Step 6: INSERT/DELETE/UPDATE - // Step 7: Triggers... - // Create trigger hubs - this.beforeinsert = {}; - this.afterinsert = {}; - this.insteadofinsert = {}; + // Step 7: Triggers... + // Create trigger hubs + this.beforeinsert = {}; + this.afterinsert = {}; + this.insteadofinsert = {}; - this.beforedelete = {}; - this.afterdelete = {}; - this.insteadofdelete = {}; + this.beforedelete = {}; + this.afterdelete = {}; + this.insteadofdelete = {}; - this.beforeupdate = {}; - this.afterupdate = {}; - this.insteadofupdate = {}; + this.beforeupdate = {}; + this.afterupdate = {}; + this.insteadofupdate = {}; - // Done - Object.assign(this, params); -}); + // Done + Object.assign(this, params); + }); -/*/* + /*/* // View = function(){ // this.data = []; // this.columns = []; @@ -61,11 +61,11 @@ var Table = (alasql.Table = function (params) { // alasql.View = View; */ -Table.prototype.indexColumns = function () { - var self = this; - self.xcolumns = {}; - self.columns.forEach(function (col) { - self.xcolumns[col.columnid] = col; - }); -}; + Table.prototype.indexColumns = function () { + var self = this; + self.xcolumns = {}; + self.columns.forEach(function (col) { + self.xcolumns[col.columnid] = col; + }); + }; } diff --git a/src/data/transaction.js b/src/data/transaction.js index 5be6bbd2f3..5b8ed9fbc8 100755 --- a/src/data/transaction.js +++ b/src/data/transaction.js @@ -1,6 +1,6 @@ export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; -/* + /* // // Transaction class for Alasql.js // Date: 03.11.2014 @@ -8,54 +8,54 @@ export default function install(alasql, internal) { // */ -class Transaction { - transactionid = Date.now(); - - committed = false; + class Transaction { + transactionid = Date.now(); - /** @type {string | undefined} */ - bank; + committed = false; - constructor(databaseid) { - this.databaseid = databaseid; - this.dbversion = alasql.databases[databaseid].dbversion; - // this.bank = structuredClone(alasql.databases[databaseid]); - this.bank = JSON.stringify(alasql.databases[databaseid]); - // TODO CLone Tables with insertfns - } + /** @type {string | undefined} */ + bank; - /** Commit transaction */ - commit() { - this.committed = true; - schemaChanged(alasql.databases[this.databaseid]); - delete this.bank; - } + constructor(databaseid) { + this.databaseid = databaseid; + this.dbversion = alasql.databases[databaseid].dbversion; + // this.bank = structuredClone(alasql.databases[databaseid]); + this.bank = JSON.stringify(alasql.databases[databaseid]); + // TODO CLone Tables with insertfns + } - /** Rollback transaction */ - rollback() { - if (!this.committed) { - alasql.databases[this.databaseid] = JSON.parse(this.bank); - schemaChanged(); - // alasql.databases[this.databaseid].tables = this.bank; - // alasql.databases[this.databaseid].dbversion = this.dbversion; + /** Commit transaction */ + commit() { + this.committed = true; + schemaChanged(alasql.databases[this.databaseid]); delete this.bank; - } else { - throw new Error('Transaction already commited'); } - } - /** - * Execute SQL statement - * @param {string} sql SQL statement - * @param {object} params Parameters - * @param {function} cb Callback function - * @return result - */ - exec(sql, params, cb) { - return alasql.dexec(this.databaseid, sql, params, cb); - } + /** Rollback transaction */ + rollback() { + if (!this.committed) { + alasql.databases[this.databaseid] = JSON.parse(this.bank); + schemaChanged(); + // alasql.databases[this.databaseid].tables = this.bank; + // alasql.databases[this.databaseid].dbversion = this.dbversion; + delete this.bank; + } else { + throw new Error('Transaction already commited'); + } + } - /* + /** + * Execute SQL statement + * @param {string} sql SQL statement + * @param {object} params Parameters + * @param {function} cb Callback function + * @return result + */ + exec(sql, params, cb) { + return alasql.dexec(this.databaseid, sql, params, cb); + } + + /* queryArray (sql, params, cb) { return flatArray(this.exec(sql, params, cb)); } @@ -73,12 +73,12 @@ class Transaction { return res[Object.keys(res)[0]]; } */ -} + } -Transaction.prototype.executeSQL = Transaction.prototype.exec; + Transaction.prototype.executeSQL = Transaction.prototype.exec; -// Transaction.prototype.query = Database.prototype.exec; -// Transaction.prototype.run = Database.prototype.exec; + // Transaction.prototype.query = Database.prototype.exec; + // Transaction.prototype.run = Database.prototype.exec; -alasql.Transaction = Transaction; + alasql.Transaction = Transaction; } diff --git a/src/data/view.js b/src/data/view.js index af4f37f4a2..8df914f94d 100755 --- a/src/data/view.js +++ b/src/data/view.js @@ -1,5 +1,5 @@ export default function install(alasql) { -/* + /* // // View class for Alasql.js // Date: 14.11.2014 @@ -7,22 +7,22 @@ export default function install(alasql) { // */ -// Table class -class View { - constructor(params) { - // Columns - this.columns = []; - this.xcolumns = {}; - // Data array - this.query = []; + // Table class + class View { + constructor(params) { + // Columns + this.columns = []; + this.xcolumns = {}; + // Data array + this.query = []; - Object.assign(this, params); + Object.assign(this, params); + } } -} -alasql.View = View; + alasql.View = View; -/*/* + /*/* // View = function(){ // this.data = []; // this.columns = []; diff --git a/src/dialects/mysql.js b/src/dialects/mysql.js index 79ec4371ec..0eca596241 100755 --- a/src/dialects/mysql.js +++ b/src/dialects/mysql.js @@ -1,30 +1,30 @@ export default function install(alasql) { -if (alasql.options.mysql) { - alasql.fn.TIMESTAMPDIFF = function (unit, date1, date2) { - return alasql.stdfn.DATEDIFF(unit, date1, date2); - }; -} + if (alasql.options.mysql) { + alasql.fn.TIMESTAMPDIFF = function (unit, date1, date2) { + return alasql.stdfn.DATEDIFF(unit, date1, date2); + }; + } -if (alasql.options.mysql || alasql.options.sqlite) { - // Pseudo INFORMATION_SCHEMA function - alasql.from.INFORMATION_SCHEMA = function (filename, opts, cb, idx, query) { - if (filename == 'VIEWS' || filename == 'TABLES') { - var res = []; - for (var databaseid in alasql.databases) { - var tables = alasql.databases[databaseid].tables; - for (var tableid in tables) { - if ( - (tables[tableid].view && filename == 'VIEWS') || - (!tables[tableid].view && filename == 'TABLES') - ) { - res.push({TABLE_CATALOG: databaseid, TABLE_NAME: tableid}); + if (alasql.options.mysql || alasql.options.sqlite) { + // Pseudo INFORMATION_SCHEMA function + alasql.from.INFORMATION_SCHEMA = function (filename, opts, cb, idx, query) { + if (filename == 'VIEWS' || filename == 'TABLES') { + var res = []; + for (var databaseid in alasql.databases) { + var tables = alasql.databases[databaseid].tables; + for (var tableid in tables) { + if ( + (tables[tableid].view && filename == 'VIEWS') || + (!tables[tableid].view && filename == 'TABLES') + ) { + res.push({TABLE_CATALOG: databaseid, TABLE_NAME: tableid}); + } } } + if (cb) res = cb(res, idx, query); + return res; } - if (cb) res = cb(res, idx, query); - return res; - } - throw new Error('Unknown INFORMATION_SCHEMA table'); - }; -} + throw new Error('Unknown INFORMATION_SCHEMA table'); + }; + } } diff --git a/src/dialects/oracle.js b/src/dialects/oracle.js index 699508ac26..a71cd738cc 100755 --- a/src/dialects/oracle.js +++ b/src/dialects/oracle.js @@ -1,4 +1,4 @@ export default function install(alasql) { -if (alasql.options.oracle) { -} + if (alasql.options.oracle) { + } } diff --git a/src/dialects/postgres.js b/src/dialects/postgres.js index b81e7cb733..8960b62200 100755 --- a/src/dialects/postgres.js +++ b/src/dialects/postgres.js @@ -1,4 +1,4 @@ export default function install(alasql) { -if (alasql.options.postgres) { -} + if (alasql.options.postgres) { + } } diff --git a/src/dialects/sqlite.js b/src/dialects/sqlite.js index 79473aabd6..56aef72c9f 100755 --- a/src/dialects/sqlite.js +++ b/src/dialects/sqlite.js @@ -1,4 +1,4 @@ export default function install(alasql) { -if (alasql.options.sqlite) { -} + if (alasql.options.sqlite) { + } } diff --git a/src/dialects/tsql.js b/src/dialects/tsql.js index b575d8efb9..e928be2435 100755 --- a/src/dialects/tsql.js +++ b/src/dialects/tsql.js @@ -1,37 +1,37 @@ export default function install(alasql) { -if (alasql.options.tsql) { - // - // Check tables and views - // IF OBJECT_ID('dbo.Employees') IS NOT NULL - // DROP TABLE dbo.Employees; - // IF OBJECT_ID('dbo.VSortedOrders', 'V') IS NOT NULL - // DROP VIEW dbo.VSortedOrders; + if (alasql.options.tsql) { + // + // Check tables and views + // IF OBJECT_ID('dbo.Employees') IS NOT NULL + // DROP TABLE dbo.Employees; + // IF OBJECT_ID('dbo.VSortedOrders', 'V') IS NOT NULL + // DROP VIEW dbo.VSortedOrders; - alasql.stdfn.OBJECT_ID = function (name, type) { - if (typeof type == 'undefined') type = 'T'; - type = type.toUpperCase(); + alasql.stdfn.OBJECT_ID = function (name, type) { + if (typeof type == 'undefined') type = 'T'; + type = type.toUpperCase(); - var sname = name.split('.'); - var dbid = alasql.useid; - var objname = sname[0]; - if (sname.length == 2) { - dbid = sname[0]; - objname = sname[1]; - } + var sname = name.split('.'); + var dbid = alasql.useid; + var objname = sname[0]; + if (sname.length == 2) { + dbid = sname[0]; + objname = sname[1]; + } - var tables = alasql.databases[dbid].tables; - dbid = alasql.databases[dbid].databaseid; - for (var tableid in tables) { - if (tableid == objname) { - // TODO: What OBJECT_ID actually returns + var tables = alasql.databases[dbid].tables; + dbid = alasql.databases[dbid].databaseid; + for (var tableid in tables) { + if (tableid == objname) { + // TODO: What OBJECT_ID actually returns - if (tables[tableid].view && type == 'V') return dbid + '.' + tableid; - if (!tables[tableid].view && type == 'T') return dbid + '.' + tableid; - return undefined; + if (tables[tableid].view && type == 'V') return dbid + '.' + tableid; + if (!tables[tableid].view && type == 'T') return dbid + '.' + tableid; + return undefined; + } } - } - return undefined; - }; -} + return undefined; + }; + } } diff --git a/src/engine/core.js b/src/engine/core.js index 4a91815dcf..abf753ce34 100755 --- a/src/engine/core.js +++ b/src/engine/core.js @@ -2,23 +2,23 @@ import makeParser from '../generated/parser.js'; export default function install(alasql, internal) { var alasqlparser = makeParser(alasql); -/** + /** Database class for Alasql.js */ -// Initial parameters + // Initial parameters -/** + /** Jison parser */ -alasql.parser = alasqlparser; + alasql.parser = alasqlparser; -/*/* This is not working :-/ */ -alasql.parser.parseError = function (str, hash) { - throw new Error('Have you used a reserved keyword without `escaping` it?\n' + str); -}; + /*/* This is not working :-/ */ + alasql.parser.parseError = function (str, hash) { + throw new Error('Have you used a reserved keyword without `escaping` it?\n' + str); + }; -/** + /** Jison parser @param {string} sql SQL statement @return {object} AST (Abstract Syntax Tree) @@ -31,513 +31,513 @@ alasql.parser.parseError = function (str, hash) { // My own parser here } */ -alasql.parse = function (sql) { - return alasqlparser.parse(alasql.utils.uncomment(sql)); -}; + alasql.parse = function (sql) { + return alasqlparser.parse(alasql.utils.uncomment(sql)); + }; -/** + /** List of engines of external databases @type {object} @todo Create collection type */ -alasql.engines = {}; + alasql.engines = {}; -/** + /** List of databases @type {object} */ -alasql.databases = {}; + alasql.databases = {}; -/** + /** Number of databases @type {number} */ -alasql.databasenum = 0; + alasql.databasenum = 0; -/** + /** Schema version of all databases. Every schema change in any database increases it, so cached statements, statements from alasql.compile() and views are compiled again @type {number} */ -alasql.schemaVersion = 0; + alasql.schemaVersion = 0; -/** + /** Record a schema change. All compiled code (the statement cache of every database, statements from alasql.compile() and views) is compiled again on its next use. db.dbversion is still increased for the database that changed, as user code reads and increases it too. @param {object} [db] The database that changed, if it still exists */ -function schemaChanged(db) { - if (db) db.dbversion++; - alasql.schemaVersion++; -} + function schemaChanged(db) { + if (db) db.dbversion++; + alasql.schemaVersion++; + } -/** + /** Alasql options object */ -alasql.options = { - /** Log or throw error */ - errorlog: false, + alasql.options = { + /** Log or throw error */ + errorlog: false, - /** Use valueof in orderfn */ - valueof: true, + /** Use valueof in orderfn */ + valueof: true, - /** DROP database in any case */ - dropifnotexists: false, + /** DROP database in any case */ + dropifnotexists: false, - /** How to handle DATE and DATETIME types */ - datetimeformat: 'sql', + /** How to handle DATE and DATETIME types */ + datetimeformat: 'sql', - /** Table and column names are case sensitive and converted to lower-case */ - casesensitive: true, + /** Table and column names are case sensitive and converted to lower-case */ + casesensitive: true, - /** target for log. Values: 'console', 'output', 'id' of html tag */ - logtarget: 'output', + /** target for log. Values: 'console', 'output', 'id' of html tag */ + logtarget: 'output', - /** Print SQL at log */ - logprompt: true, + /** Print SQL at log */ + logprompt: true, - /** Callback for async queries progress */ - progress: false, + /** Callback for async queries progress */ + progress: false, - /** - * Default modifier - * values: RECORDSET, VALUE, ROW, COLUMN, MATRIX, TEXTSTRING, INDEX - * @type {'RECORDSET'|'VALUE'|'ROW'|'COLUMN'|'MATRIX'|'TEXTSTRING'|'INDEX'|undefined} - */ - modifier: undefined, + /** + * Default modifier + * values: RECORDSET, VALUE, ROW, COLUMN, MATRIX, TEXTSTRING, INDEX + * @type {'RECORDSET'|'VALUE'|'ROW'|'COLUMN'|'MATRIX'|'TEXTSTRING'|'INDEX'|undefined} + */ + modifier: undefined, - /** How many rows to lookup to define columns */ - columnlookup: 10, + /** How many rows to lookup to define columns */ + columnlookup: 10, - /** Create vertex if not found */ - autovertex: true, + /** Create vertex if not found */ + autovertex: true, - /** Use dbo as current database (for partial T-SQL comaptibility) */ - usedbo: true, + /** Use dbo as current database (for partial T-SQL comaptibility) */ + usedbo: true, - /** AUTOCOMMIT ON | OFF */ - autocommit: true, + /** AUTOCOMMIT ON | OFF */ + autocommit: true, - /** Use cache */ - cache: true, + /** Use cache */ + cache: true, - /** Compatibility flags */ - tsql: true, + /** Compatibility flags */ + tsql: true, - mysql: true, + mysql: true, - postgres: true, + postgres: true, - oracle: true, + oracle: true, - sqlite: true, + sqlite: true, - orientdb: true, + orientdb: true, - /** for SET NOCOUNT OFF */ - nocount: false, + /** for SET NOCOUNT OFF */ + nocount: false, - /** Check for NaN and convert it to undefined */ - nan: false, + /** Check for NaN and convert it to undefined */ + nan: false, - excel: {cellDates: true}, + excel: {cellDates: true}, - /** Option for SELECT * FROM a,b */ - joinstar: 'overwrite', + /** Option for SELECT * FROM a,b */ + joinstar: 'overwrite', - loopbreak: 100000, + loopbreak: 100000, - /** Maximum iterations for recursive CTEs to prevent infinite loops */ - maxCteIterations: 1000, + /** Maximum iterations for recursive CTEs to prevent infinite loops */ + maxCteIterations: 1000, - /** Whether GETDATE() and NOW() return dates as string. If false, then a Date object is returned */ - dateAsString: true, + /** Whether GETDATE() and NOW() return dates as string. If false, then a Date object is returned */ + dateAsString: true, - /** Automatically convert string values to numbers when reading from CSV files. Set to false to preserve string types */ - csvStringToNumber: true, -}; + /** Automatically convert string values to numbers when reading from CSV files. Set to false to preserve string types */ + csvStringToNumber: true, + }; -//alasql.options.worker = false; + //alasql.options.worker = false; -// Variables -alasql.vars = {}; + // Variables + alasql.vars = {}; -alasql.declares = {}; + alasql.declares = {}; -alasql.prompthistory = []; + alasql.prompthistory = []; -alasql.plugins = {}; // If plugin already loaded + alasql.plugins = {}; // If plugin already loaded -alasql.from = {}; // FROM functions + alasql.from = {}; // FROM functions -alasql.into = {}; // INTO functions + alasql.into = {}; // INTO functions -alasql.fn = {}; + alasql.fn = {}; -alasql.aggr = {}; + alasql.aggr = {}; -alasql.busy = 0; + alasql.busy = 0; -// Cache -alasql.MAXSQLCACHESIZE = 10000; -alasql.DEFAULTDATABASEID = 'alasql'; + // Cache + alasql.MAXSQLCACHESIZE = 10000; + alasql.DEFAULTDATABASEID = 'alasql'; -/* WebWorker */ -alasql.lastid = 0; + /* WebWorker */ + alasql.lastid = 0; -alasql.buffer = {}; + alasql.buffer = {}; -alasql.private = { - externalXlsxLib: null, -}; + alasql.private = { + externalXlsxLib: null, + }; -alasql.setXLSX = function (XLSX) { - alasql.private.externalXlsxLib = XLSX; -}; + alasql.setXLSX = function (XLSX) { + alasql.private.externalXlsxLib = XLSX; + }; -/** + /** Select current database @param {string} databaseid Selected database identificator */ -alasql.use = function (databaseid) { - if (!databaseid) { - databaseid = alasql.DEFAULTDATABASEID; - } + alasql.use = function (databaseid) { + if (!databaseid) { + databaseid = alasql.DEFAULTDATABASEID; + } - if (alasql.useid === databaseid) { - return; - } + if (alasql.useid === databaseid) { + return; + } - if (alasql.databases[databaseid] !== undefined) { - alasql.useid = databaseid; - let db = alasql.databases[alasql.useid]; - alasql.tables = db.tables; - db.resetSqlCache(); - if (alasql.options.usedbo) { - alasql.databases.dbo = db; + if (alasql.databases[databaseid] !== undefined) { + alasql.useid = databaseid; + let db = alasql.databases[alasql.useid]; + alasql.tables = db.tables; + db.resetSqlCache(); + if (alasql.options.usedbo) { + alasql.databases.dbo = db; + } + } else { + throw Error('Database does not exist: ' + databaseid); } - } else { - throw Error('Database does not exist: ' + databaseid); - } -}; + }; -alasql.autoval = function (tablename, colname, getNext, databaseid) { - var db = databaseid ? alasql.databases[databaseid] : alasql.databases[alasql.useid]; + alasql.autoval = function (tablename, colname, getNext, databaseid) { + var db = databaseid ? alasql.databases[databaseid] : alasql.databases[alasql.useid]; - if (!db.tables[tablename]) { - throw new Error('Tablename not found: ' + tablename); - } + if (!db.tables[tablename]) { + throw new Error('Tablename not found: ' + tablename); + } - if (!db.tables[tablename].identities[colname]) { - throw new Error('Colname not found: ' + colname); - } + if (!db.tables[tablename].identities[colname]) { + throw new Error('Colname not found: ' + colname); + } - if (getNext) { - return db.tables[tablename].identities[colname].value || null; - } + if (getNext) { + return db.tables[tablename].identities[colname].value || null; + } - return ( - db.tables[tablename].identities[colname].value - - db.tables[tablename].identities[colname].step || null - ); -}; + return ( + db.tables[tablename].identities[colname].value - + db.tables[tablename].identities[colname].step || null + ); + }; -/** + /** Run single SQL statement on current database */ -alasql.exec = function (sql, params, cb, scope) { - // Avoid setting params if not needed even with callback - if (typeof params === 'function') { - scope = cb; - cb = params; - params = {}; - } + alasql.exec = function (sql, params, cb, scope) { + // Avoid setting params if not needed even with callback + if (typeof params === 'function') { + scope = cb; + cb = params; + params = {}; + } - delete alasql.error; + delete alasql.error; - params = params || {}; + params = params || {}; - if (alasql.options.errorlog) { - try { - return alasql.dexec(alasql.useid, sql, params, cb, scope); - } catch (err) { - alasql.error = err; - if (cb) { - cb(null, alasql.error); + if (alasql.options.errorlog) { + try { + return alasql.dexec(alasql.useid, sql, params, cb, scope); + } catch (err) { + alasql.error = err; + if (cb) { + cb(null, alasql.error); + } } - } - } else { - return alasql.dexec(alasql.useid, sql, params, cb, scope); - } -}; - -/** - * Clears any unneeded properties from a given AST statement closure used for caching - * @param {Object} statement the statement to cleanup - */ -// Run a compiled statement and mark it as running until it has finished, also when it -// finishes later through the callback. The cached result data is cleaned up then. -function runStatement(statement, params, cb, scope) { - statement.running = (statement.running || 0) + 1; - var finished = false; - var finish = function () { - if (!finished) { - finished = true; - statement.running--; - cleanupCache(statement); + } else { + return alasql.dexec(alasql.useid, sql, params, cb, scope); } }; - var done = cb - ? function (res, err) { + + /** + * Clears any unneeded properties from a given AST statement closure used for caching + * @param {Object} statement the statement to cleanup + */ + // Run a compiled statement and mark it as running until it has finished, also when it + // finishes later through the callback. The cached result data is cleaned up then. + function runStatement(statement, params, cb, scope) { + statement.running = (statement.running || 0) + 1; + var finished = false; + var finish = function () { + if (!finished) { + finished = true; + statement.running--; + cleanupCache(statement); + } + }; + var done = cb + ? function (res, err) { + finish(); + return cb(res, err); + } + : cb; + try { + return statement(params, done, scope); + } finally { + if (!cb) { finish(); - return cb(res, err); } - : cb; - try { - return statement(params, done, scope); - } finally { - if (!cb) { - finish(); } } -} -function cleanupCache(statement) { - if (!statement) { - return; - } - if (!alasql.options.cache) { - return; - } - // cleanup the table data to prevent storing this information in the SQL cache - if (statement && statement.query && statement.query.data) { - statement.query.data = []; + function cleanupCache(statement) { + if (!statement) { + return; + } + if (!alasql.options.cache) { + return; + } + // cleanup the table data to prevent storing this information in the SQL cache + if (statement && statement.query && statement.query.data) { + statement.query.data = []; + } } -} -/** + /** Run SQL statement on specific database */ -alasql.dexec = function (databaseid, sql, params, cb, scope) { - var db = alasql.databases[databaseid]; - // if(db.databaseid != databaseid) console.trace('got!'); - // console.log(3,db.databaseid,databaseid); - - // Include joinstar option in cache key because it affects how SELECT * compiles - // Without this, changing joinstar would use stale cached queries compiled with old option - // The key is the text itself, a hash of it can collide and return another query's statement - var hh = sql + '|joinstar:' + alasql.options.joinstar; - - // Create hash - if (alasql.options.cache) { - let statement = db.sqlCache[hh]; - // If database structure was not changed since last time return cache - if ( - statement && - db.dbversion === statement.dbversion && - statement.schemaVersion === alasql.schemaVersion - ) { - // A compiled statement keeps the state of one run. While a run of it is still - // going (a nested call or an unfinished asynchronous run), this call compiles - // its own copy instead of sharing it (#1953) - if (!statement.running) { - return runStatement(statement, params, cb); + alasql.dexec = function (databaseid, sql, params, cb, scope) { + var db = alasql.databases[databaseid]; + // if(db.databaseid != databaseid) console.trace('got!'); + // console.log(3,db.databaseid,databaseid); + + // Include joinstar option in cache key because it affects how SELECT * compiles + // Without this, changing joinstar would use stale cached queries compiled with old option + // The key is the text itself, a hash of it can collide and return another query's statement + var hh = sql + '|joinstar:' + alasql.options.joinstar; + + // Create hash + if (alasql.options.cache) { + let statement = db.sqlCache[hh]; + // If database structure was not changed since last time return cache + if ( + statement && + db.dbversion === statement.dbversion && + statement.schemaVersion === alasql.schemaVersion + ) { + // A compiled statement keeps the state of one run. While a run of it is still + // going (a nested call or an unfinished asynchronous run), this call compiles + // its own copy instead of sharing it (#1953) + if (!statement.running) { + return runStatement(statement, params, cb); + } + var inUse = true; } - var inUse = true; } - } - let ast = inUse ? undefined : db.astCache[hh]; - if (inUse) { - ast = alasql.parse(sql); - } else if (alasql.options.cache && !ast) { - // Create AST cache - ast = alasql.parse(sql); - if (ast) { - // add to AST cache - db.astCache[hh] = ast; + let ast = inUse ? undefined : db.astCache[hh]; + if (inUse) { + ast = alasql.parse(sql); + } else if (alasql.options.cache && !ast) { + // Create AST cache + ast = alasql.parse(sql); + if (ast) { + // add to AST cache + db.astCache[hh] = ast; + } + } else { + ast = alasql.parse(sql); } - } else { - ast = alasql.parse(sql); - } - if (!ast.statements) { - return; - } + if (!ast.statements) { + return; + } - if (0 === ast.statements.length) { - return 0; - } + if (0 === ast.statements.length) { + return 0; + } - if (1 === ast.statements.length) { - if (ast.statements[0].compile) { - // Compile and Execute - var statement = ast.statements[0].compile(databaseid, params); - if (!statement) { - return; - } - statement.sql = sql; - statement.dbversion = db.dbversion; - statement.schemaVersion = alasql.schemaVersion; - - if (alasql.options.cache && !inUse) { - // Secure sqlCache size - if (db.sqlCacheSize > alasql.MAXSQLCACHESIZE) { - db.resetSqlCache(); + if (1 === ast.statements.length) { + if (ast.statements[0].compile) { + // Compile and Execute + var statement = ast.statements[0].compile(databaseid, params); + if (!statement) { + return; + } + statement.sql = sql; + statement.dbversion = db.dbversion; + statement.schemaVersion = alasql.schemaVersion; + + if (alasql.options.cache && !inUse) { + // Secure sqlCache size + if (db.sqlCacheSize > alasql.MAXSQLCACHESIZE) { + db.resetSqlCache(); + } + db.sqlCacheSize++; + db.sqlCache[hh] = statement; } - db.sqlCacheSize++; - db.sqlCache[hh] = statement; + return (alasql.res = runStatement(statement, params, cb, scope)); } - return (alasql.res = runStatement(statement, params, cb, scope)); + alasql.precompile(ast.statements[0], alasql.useid, params); + var res = (alasql.res = ast.statements[0].execute(databaseid, params, cb, scope)); + return res; } - alasql.precompile(ast.statements[0], alasql.useid, params); - var res = (alasql.res = ast.statements[0].execute(databaseid, params, cb, scope)); - return res; - } - if (cb) { - alasql.adrun(databaseid, ast, params, cb, scope); - return; - } + if (cb) { + alasql.adrun(databaseid, ast, params, cb, scope); + return; + } - return alasql.drun(databaseid, ast, params, cb, scope); -}; + return alasql.drun(databaseid, ast, params, cb, scope); + }; -/** + /** Run multiple statements and return array of results sync */ -alasql.drun = function (databaseid, ast, params, cb, scope) { - var useid = alasql.useid; + alasql.drun = function (databaseid, ast, params, cb, scope) { + var useid = alasql.useid; - if (useid !== databaseid) { - alasql.use(databaseid); - } + if (useid !== databaseid) { + alasql.use(databaseid); + } - var res = []; - for (var i = 0, ilen = ast.statements.length; i < ilen; i++) { - if (ast.statements[i]) { - if (ast.statements[i].compile) { - var statement = ast.statements[i].compile(alasql.useid); - res.push((alasql.res = statement(params, null, scope))); - } else { - alasql.precompile(ast.statements[i], alasql.useid, params); - res.push((alasql.res = ast.statements[i].execute(alasql.useid, params))); + var res = []; + for (var i = 0, ilen = ast.statements.length; i < ilen; i++) { + if (ast.statements[i]) { + if (ast.statements[i].compile) { + var statement = ast.statements[i].compile(alasql.useid); + res.push((alasql.res = statement(params, null, scope))); + } else { + alasql.precompile(ast.statements[i], alasql.useid, params); + res.push((alasql.res = ast.statements[i].execute(alasql.useid, params))); + } } } - } - if (useid !== databaseid) { - alasql.use(useid); - } + if (useid !== databaseid) { + alasql.use(useid); + } - if (cb) { - cb(res); - } + if (cb) { + cb(res); + } - alasql.res = res; + alasql.res = res; - return res; -}; + return res; + }; -/** + /** Run multiple statements and return array of results async */ -alasql.adrun = function (databaseid, ast, params, cb, scope) { - var idx = 0; - var noqueries = ast.statements.length; - if (alasql.options.progress !== false) { - alasql.options.progress(noqueries, idx++); - } - - // alasql.busy++; - var useid = alasql.useid; - if (useid !== databaseid) { - alasql.use(databaseid); - } - var res = []; + alasql.adrun = function (databaseid, ast, params, cb, scope) { + var idx = 0; + var noqueries = ast.statements.length; + if (alasql.options.progress !== false) { + alasql.options.progress(noqueries, idx++); + } - function adrunone(data) { - if (data !== undefined) { - res.push(data); + // alasql.busy++; + var useid = alasql.useid; + if (useid !== databaseid) { + alasql.use(databaseid); } + var res = []; - var astatement = ast.statements.shift(); - if (!astatement) { - if (useid !== databaseid) { - alasql.use(useid); + function adrunone(data) { + if (data !== undefined) { + res.push(data); } - cb(res); - return; - } - if (astatement.compile) { - var statement = astatement.compile(alasql.useid); - statement(params, adrunone, scope); - if (alasql.options.progress !== false) { - alasql.options.progress(noqueries, idx++); + var astatement = ast.statements.shift(); + if (!astatement) { + if (useid !== databaseid) { + alasql.use(useid); + } + cb(res); + return; } - return; - } - alasql.precompile(ast.statements[0], alasql.useid, params); - astatement.execute(alasql.useid, params, adrunone); + if (astatement.compile) { + var statement = astatement.compile(alasql.useid); + statement(params, adrunone, scope); + if (alasql.options.progress !== false) { + alasql.options.progress(noqueries, idx++); + } + return; + } - if (alasql.options.progress !== false) { - alasql.options.progress(noqueries, idx++); + alasql.precompile(ast.statements[0], alasql.useid, params); + astatement.execute(alasql.useid, params, adrunone); + + if (alasql.options.progress !== false) { + alasql.options.progress(noqueries, idx++); + } } - } - adrunone(); /** @todo Check, why data is empty here */ -}; + adrunone(); /** @todo Check, why data is empty here */ + }; -/** + /** Compile statement to JavaScript function @param {string} sql SQL statement @param {string} databaseid Database identificator @return {functions} Compiled statement functions */ -alasql.compile = function (sql, databaseid) { - databaseid = databaseid || alasql.useid; + alasql.compile = function (sql, databaseid) { + databaseid = databaseid || alasql.useid; - let ast = alasql.parse(sql); // Create AST + let ast = alasql.parse(sql); // Create AST - if (1 !== ast.statements.length) - throw new Error('Cannot compile, because number of statements in SQL is not equal to 1'); + if (1 !== ast.statements.length) + throw new Error('Cannot compile, because number of statements in SQL is not equal to 1'); - var compiled = ast.statements[0].compile(databaseid); - var schemaVersion = alasql.schemaVersion; + var compiled = ast.statements[0].compile(databaseid); + var schemaVersion = alasql.schemaVersion; - // Compile again when the schema of any database changed since the last compile - var statement = function (params, cb) { - if (schemaVersion !== alasql.schemaVersion) { - compiled = ast.statements[0].compile(databaseid); - schemaVersion = alasql.schemaVersion; - Object.assign(statement, compiled); - } - // While a run of this statement is still going (a nested call or an unfinished - // asynchronous run), this call gets its own copy, as in alasql.dexec (#1953, #2596) - if (compiled.running) { - return runStatement(alasql.parse(sql).statements[0].compile(databaseid), params, cb); - } - return runStatement(compiled, params, cb); - }; - // Keep the properties of the compiled statement on the returned function, also after a recompile - Object.assign(statement, compiled); - - statement.promise = function (params) { - return new Promise(function (resolve, reject) { - statement(params, function (data, err) { - if (err) { - reject(err); - } else { - resolve(data); - } + // Compile again when the schema of any database changed since the last compile + var statement = function (params, cb) { + if (schemaVersion !== alasql.schemaVersion) { + compiled = ast.statements[0].compile(databaseid); + schemaVersion = alasql.schemaVersion; + Object.assign(statement, compiled); + } + // While a run of this statement is still going (a nested call or an unfinished + // asynchronous run), this call gets its own copy, as in alasql.dexec (#1953, #2596) + if (compiled.running) { + return runStatement(alasql.parse(sql).statements[0].compile(databaseid), params, cb); + } + return runStatement(compiled, params, cb); + }; + // Keep the properties of the compiled statement on the returned function, also after a recompile + Object.assign(statement, compiled); + + statement.promise = function (params) { + return new Promise(function (resolve, reject) { + statement(params, function (data, err) { + if (err) { + reject(err); + } else { + resolve(data); + } + }); }); - }); - }; + }; - return statement; -}; + return statement; + }; internal.schemaChanged = schemaChanged; } diff --git a/src/engine/create.js b/src/engine/create.js index 601696449f..f5e75060fb 100644 --- a/src/engine/create.js +++ b/src/engine/create.js @@ -1,6 +1,6 @@ /* eslint-disable */ -"use strict"; +'use strict'; /** @fileoverview AlaSQL JavaScript SQL library @@ -17,9 +17,7 @@ UMD envelope for AlaSQL */ - - - /** +/** AlaSQL - Main Alasql class @function @param {string|function|object} sql - SQL-statement or data object for fluent interface @@ -49,23 +47,21 @@ */ export function create() { - let alasql = function (sql, params, cb, scope) { - params = params || []; if (typeof importScripts !== 'function' && alasql.webworker) { var id = alasql.lastid++; alasql.buffer[id] = cb; - alasql.webworker.postMessage({ id: id, sql: sql, params: params }); + alasql.webworker.postMessage({id: id, sql: sql, params: params}); return; } if (arguments.length === 0) { // Without arguments - Fluent interface return new alasql.yy.Select({ - columns: [new alasql.yy.Column({ columnid: '*' })], - from: [new alasql.yy.ParamValue({ param: 0 })] + columns: [new alasql.yy.Column({columnid: '*'})], + from: [new alasql.yy.ParamValue({param: 0})], }); } else if (arguments.length === 1) { // Access promise notation without using `.promise(...)` @@ -86,14 +82,17 @@ export function create() { // Standard interface // alasql('#sql'); - if (typeof sql === 'string' && sql[0] === '#' && typeof document === "object") { + if (typeof sql === 'string' && sql[0] === '#' && typeof document === 'object') { sql = document.querySelector(sql).textContent; } else if (typeof sql === 'object' && sql instanceof HTMLElement) { sql = sql.textContent; } else if (typeof sql === 'function') { // to run multiline functions sql = sql.toString(); - sql = (/\/\*([\S\s]+)\*\//m.exec(sql) || ['', 'Function given as SQL. Plese Provide SQL string or have a /* ... */ syle comment with SQL in the function.'])[1]; + sql = (/\/\*([\S\s]+)\*\//m.exec(sql) || [ + '', + 'Function given as SQL. Plese Provide SQL string or have a /* ... */ syle comment with SQL in the function.', + ])[1]; } // Run SQL return alasql.exec(sql, params, cb, scope); @@ -112,8 +111,7 @@ export function create() { */ alasql.debug = undefined; // Initial debug variable - -/*only-for-browser/* + /*only-for-browser/* var require = function(){return null}; // as alasqlparser.js is generated, we can not "remove" references to var __dirname = ''; //*/ diff --git a/src/engine/finish.js b/src/engine/finish.js index 3572183316..1af904049a 100755 --- a/src/engine/finish.js +++ b/src/engine/finish.js @@ -1,30 +1,27 @@ -export default function install(alasql) { - var utils = alasql.utils; - var Database = alasql.Database; -/* eslint-disable */ - -/* -// -// Last part of Alasql.js -// Date: 03.11.2014 -// (c) 2014, Andrey Gershun -// -*/ - -// This is a final part of Alasql - - -/*only-for-browser/* -if(utils.isCordova || utils.isMeteorServer || utils.isNode ){ - console.log('It looks like you are using the browser version of AlaSQL. Please use the alasql.fs.js file instead.') -} -//*/ - - -// Create default database -new Database("alasql"); - -// Set default database -alasql.use("alasql"); - -} +export default function install(alasql) { + var utils = alasql.utils; + var Database = alasql.Database; + /* eslint-disable */ + + /* +// +// Last part of Alasql.js +// Date: 03.11.2014 +// (c) 2014, Andrey Gershun +// +*/ + + // This is a final part of Alasql + + /*only-for-browser/* +if(utils.isCordova || utils.isMeteorServer || utils.isNode ){ + console.log('It looks like you are using the browser version of AlaSQL. Please use the alasql.fs.js file instead.') +} +//*/ + + // Create default database + new Database('alasql'); + + // Set default database + alasql.use('alasql'); +} diff --git a/src/engine/promise.js b/src/engine/promise.js index 30d91e7ee5..7d1fe7cd1f 100755 --- a/src/engine/promise.js +++ b/src/engine/promise.js @@ -1,90 +1,90 @@ export default function install(alasql) { var utils = alasql.utils; -// -// Promises for AlaSQL -// + // + // Promises for AlaSQL + // -if (!utils.global.Promise) { - utils.global.Promise = Promise; -} + if (!utils.global.Promise) { + utils.global.Promise = Promise; + } -var promiseExec = function (sql, params, counterStep, counterTotal) { - return new utils.global.Promise(function (resolve, reject) { - alasql(sql, params, function (data, err) { - if (err) { - reject(err); - } else { - if (counterStep && counterTotal && alasql.options.progress !== false) { - alasql.options.progress(counterStep, counterTotal); + var promiseExec = function (sql, params, counterStep, counterTotal) { + return new utils.global.Promise(function (resolve, reject) { + alasql(sql, params, function (data, err) { + if (err) { + reject(err); + } else { + if (counterStep && counterTotal && alasql.options.progress !== false) { + alasql.options.progress(counterStep, counterTotal); + } + resolve(data); } - resolve(data); - } + }); }); - }); -}; + }; -const sequentialPromiseResolver = (promiseData, Promise) => { - var startingPoint = Promise.resolve([]); + const sequentialPromiseResolver = (promiseData, Promise) => { + var startingPoint = Promise.resolve([]); - promiseData.forEach(p => { - startingPoint = startingPoint.then(previousResult => - promiseExec(p.sql, p.params, p.i, p.length).then(result => [...previousResult, result]) - ); - }); + promiseData.forEach(p => { + startingPoint = startingPoint.then(previousResult => + promiseExec(p.sql, p.params, p.i, p.length).then(result => [...previousResult, result]) + ); + }); - return startingPoint; -}; + return startingPoint; + }; -var promiseAll = function (sqlParamsArray) { - if (sqlParamsArray.length < 1) { - return; - } + var promiseAll = function (sqlParamsArray) { + if (sqlParamsArray.length < 1) { + return; + } - var active, sql, params; + var active, sql, params; - var execArray = []; + var execArray = []; - for (var i = 0; i < sqlParamsArray.length; i++) { - active = sqlParamsArray[i]; + for (var i = 0; i < sqlParamsArray.length; i++) { + active = sqlParamsArray[i]; - if (typeof active === 'string') { - active = [active]; - } + if (typeof active === 'string') { + active = [active]; + } - if (!utils.isArray(active) || active.length < 1 || 2 < active.length) { - throw new Error('Error in .promise parameter'); - } + if (!utils.isArray(active) || active.length < 1 || 2 < active.length) { + throw new Error('Error in .promise parameter'); + } - sql = active[0]; - params = active[1] || undefined; + sql = active[0]; + params = active[1] || undefined; - execArray.push({ - sql, - params, - i, - length: sqlParamsArray.length, - }); - } + execArray.push({ + sql, + params, + i, + length: sqlParamsArray.length, + }); + } - // in case of indexdb the version does not update - // if create table queries are run in parallel - // this causes certain DML queries to not execute - // running them sequentially fixes this issue - return sequentialPromiseResolver(execArray, utils.global.Promise); -}; + // in case of indexdb the version does not update + // if create table queries are run in parallel + // this causes certain DML queries to not execute + // running them sequentially fixes this issue + return sequentialPromiseResolver(execArray, utils.global.Promise); + }; -alasql.promise = function (sql, params) { - if (typeof Promise === 'undefined') { - throw new Error('Please include a Promise/A+ library'); - } + alasql.promise = function (sql, params) { + if (typeof Promise === 'undefined') { + throw new Error('Please include a Promise/A+ library'); + } - if (typeof sql === 'string') { - return promiseExec(sql, params); - } + if (typeof sql === 'string') { + return promiseExec(sql, params); + } - if (!utils.isArray(sql) || sql.length < 1 || typeof params !== 'undefined') { - throw new Error('Error in .promise parameters'); - } - return promiseAll(sql); -}; + if (!utils.isArray(sql) || sql.length < 1 || typeof params !== 'undefined') { + throw new Error('Error in .promise parameters'); + } + return promiseAll(sql); + }; } diff --git a/src/engine/statements.js b/src/engine/statements.js index c46a300781..32e2ee3552 100755 --- a/src/engine/statements.js +++ b/src/engine/statements.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // Statements class for Alasql.js // Date: 03.11.2014 @@ -8,25 +8,25 @@ export default function install(alasql) { // */ -yy.Statements = class Statements { - constructor(params) { - Object.assign(this, params); - } + yy.Statements = class Statements { + constructor(params) { + Object.assign(this, params); + } - toString() { - return this.statements.map(st => st.toString()).join('; '); - } + toString() { + return this.statements.map(st => st.toString()).join('; '); + } - // Compile array of statements into single statement - compile(db) { - const statements = this.statements.map(st => st.compile(db)); - return statements.length === 1 - ? statements[0] - : (params, cb) => { - const res = statements.map(st => st(params)); - if (cb) cb(res); - return res; - }; - } -}; + // Compile array of statements into single statement + compile(db) { + const statements = this.statements.map(st => st.compile(db)); + return statements.length === 1 + ? statements[0] + : (params, cb) => { + const res = statements.map(st => st(params)); + if (cb) cb(res); + return res; + }; + } + }; } diff --git a/src/engine/yy.js b/src/engine/yy.js index b442beea03..175e3f3cfc 100755 --- a/src/engine/yy.js +++ b/src/engine/yy.js @@ -1,6 +1,6 @@ export default function install(alasql) { var alasqlparser = alasql.parser; -/* + /* // // Parser helper for Alasql.js // Date: 03.11.2014 @@ -8,68 +8,68 @@ export default function install(alasql) { // */ -// Base class for all yy classes -class Base { - constructor(params) { - Object.assign(this, params); + // Base class for all yy classes + class Base { + constructor(params) { + Object.assign(this, params); + } + toString() {} + toType() {} + toJS() {} + exec() {} + compile() {} } - toString() {} - toType() {} - toJS() {} - exec() {} - compile() {} -} -var yy = { - // Utility - /** @deprecated use `Object.assign` instead */ - extend: Object.assign, + var yy = { + // Utility + /** @deprecated use `Object.assign` instead */ + extend: Object.assign, + + // Option for case sensitive + casesensitive: alasql.options.casesensitive, + Base, - // Option for case sensitive - casesensitive: alasql.options.casesensitive, - Base, + // Helper for ParamValue handling in UPDATE/DELETE/INSERT + compileParamValue: function (paramIndex, operation, needsSync, databaseid, self, refProp) { + return function (params, cb) { + var data = params[paramIndex]; + if (!Array.isArray(data)) { + var err = new Error(operation + ' requires an array for parameter ' + paramIndex); + if (cb) return cb(null, err); + throw err; + } - // Helper for ParamValue handling in UPDATE/DELETE/INSERT - compileParamValue: function (paramIndex, operation, needsSync, databaseid, self, refProp) { - return function (params, cb) { - var data = params[paramIndex]; - if (!Array.isArray(data)) { - var err = new Error(operation + ' requires an array for parameter ' + paramIndex); - if (cb) return cb(null, err); - throw err; - } + // Create temp table, execute, sync, cleanup + var tmpid = '__p' + paramIndex + '_' + Date.now(); + var db = alasql.databases[databaseid || 'alasql']; + db.tables[tmpid] = new alasql.Table({tableid: tmpid}); + db.tables[tmpid].data = data; - // Create temp table, execute, sync, cleanup - var tmpid = '__p' + paramIndex + '_' + Date.now(); - var db = alasql.databases[databaseid || 'alasql']; - db.tables[tmpid] = new alasql.Table({tableid: tmpid}); - db.tables[tmpid].data = data; + try { + var origRef = self[refProp]; + self[refProp] = new yy.Table({tableid: tmpid, databaseid: db.databaseid}); + var stmt = self.compile(databaseid); + self[refProp] = origRef; - try { - var origRef = self[refProp]; - self[refProp] = new yy.Table({tableid: tmpid, databaseid: db.databaseid}); - var stmt = self.compile(databaseid); - self[refProp] = origRef; + var res = stmt(params, cb); - var res = stmt(params, cb); + // Sync back changes for operations that replace the array + if (needsSync) { + var newData = db.tables[tmpid].data; + data.length = 0; + Array.prototype.push.apply(data, newData); + } - // Sync back changes for operations that replace the array - if (needsSync) { - var newData = db.tables[tmpid].data; - data.length = 0; - Array.prototype.push.apply(data, newData); + return res; + } catch (err) { + if (cb) return cb(null, err); + throw err; + } finally { + delete db.tables[tmpid]; } + }; + }, + }; - return res; - } catch (err) { - if (cb) return cb(null, err); - throw err; - } finally { - delete db.tables[tmpid]; - } - }; - }, -}; - -alasqlparser.yy = alasql.yy = yy; + alasqlparser.yy = alasql.yy = yy; } diff --git a/src/expression/case.js b/src/expression/case.js index 2cd1caccba..8b4cb4b9e7 100755 --- a/src/expression/case.js +++ b/src/expression/case.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // CASE for Alasql.js // Date: 03.11.2014 @@ -8,63 +8,63 @@ export default function install(alasql) { // */ -yy.CaseValue = function (params) { - return Object.assign(this, params); -}; -yy.CaseValue.prototype.toString = function () { - var s = 'CASE '; - if (this.expression) s += this.expression.toString(); - if (this.whens) { - s += this.whens - .map(function (w) { - return ' WHEN ' + w.when.toString() + ' THEN ' + w.then.toString(); - }) - .join(); - } - s += ' END'; - return s; -}; + yy.CaseValue = function (params) { + return Object.assign(this, params); + }; + yy.CaseValue.prototype.toString = function () { + var s = 'CASE '; + if (this.expression) s += this.expression.toString(); + if (this.whens) { + s += this.whens + .map(function (w) { + return ' WHEN ' + w.when.toString() + ' THEN ' + w.then.toString(); + }) + .join(); + } + s += ' END'; + return s; + }; -yy.CaseValue.prototype.findAggregator = function (query) { - // console.log(this.toString()); - if (this.expression && this.expression.findAggregator) this.expression.findAggregator(query); - if (this.whens && this.whens.length > 0) { - this.whens.forEach(function (w) { - if (w.when.findAggregator) w.when.findAggregator(query); - if (w.then.findAggregator) w.then.findAggregator(query); - }); - } - if (this.elses && this.elses.findAggregator) this.elses.findAggregator(query); -}; + yy.CaseValue.prototype.findAggregator = function (query) { + // console.log(this.toString()); + if (this.expression && this.expression.findAggregator) this.expression.findAggregator(query); + if (this.whens && this.whens.length > 0) { + this.whens.forEach(function (w) { + if (w.when.findAggregator) w.when.findAggregator(query); + if (w.then.findAggregator) w.then.findAggregator(query); + }); + } + if (this.elses && this.elses.findAggregator) this.elses.findAggregator(query); + }; -yy.CaseValue.prototype.toJS = function (context, tableid, defcols) { - let s = `(((${context}, params, alasql) => { + yy.CaseValue.prototype.toJS = function (context, tableid, defcols) { + let s = `(((${context}, params, alasql) => { let y, r;`; - if (this.expression) { - // If there's an expression, evaluate it and store in `v`, then compare in `when` clauses - s += `let v = ${this.expression.toJS(context, tableid, defcols)};`; - this.whens.forEach((w, index) => { - const condition = `v === ${w.when.toJS(context, tableid, defcols)}`; - const assignment = `r = ${w.then.toJS(context, tableid, defcols)}`; - s += `${index === 0 ? 'if' : ' else if'} (${condition}) { ${assignment}; }`; - }); - } else { - // Directly evaluate `when` conditions without an initial expression - this.whens.forEach((w, index) => { - const condition = w.when.toJS(context, tableid, defcols); - const assignment = `r = ${w.then.toJS(context, tableid, defcols)}`; - s += `${index === 0 ? 'if' : ' else if'} (${condition}) { ${assignment}; }`; - }); - } + if (this.expression) { + // If there's an expression, evaluate it and store in `v`, then compare in `when` clauses + s += `let v = ${this.expression.toJS(context, tableid, defcols)};`; + this.whens.forEach((w, index) => { + const condition = `v === ${w.when.toJS(context, tableid, defcols)}`; + const assignment = `r = ${w.then.toJS(context, tableid, defcols)}`; + s += `${index === 0 ? 'if' : ' else if'} (${condition}) { ${assignment}; }`; + }); + } else { + // Directly evaluate `when` conditions without an initial expression + this.whens.forEach((w, index) => { + const condition = w.when.toJS(context, tableid, defcols); + const assignment = `r = ${w.then.toJS(context, tableid, defcols)}`; + s += `${index === 0 ? 'if' : ' else if'} (${condition}) { ${assignment}; }`; + }); + } - // Handle the `else` case - if (this.elses) { - s += ` else { r = ${this.elses.toJS(context, tableid, defcols)}; }`; - } + // Handle the `else` case + if (this.elses) { + s += ` else { r = ${this.elses.toJS(context, tableid, defcols)}; }`; + } - s += '; return r; }))(' + context + ', params, alasql)'; + s += '; return r; }))(' + context + ', params, alasql)'; - return s; -}; + return s; + }; } diff --git a/src/expression/convert.js b/src/expression/convert.js index 87d52cac31..9a8c0b84f5 100755 --- a/src/expression/convert.js +++ b/src/expression/convert.js @@ -2,7 +2,7 @@ import {newDate} from '../functions/date.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // CAST and CONVERT functions // Date: 03.11.2014 @@ -10,208 +10,208 @@ export default function install(alasql) { // */ -yy.Convert = function (params) { - return Object.assign(this, params); -}; -yy.Convert.prototype.toString = function () { - var s = 'CONVERT('; - s += this.dbtypeid; - if (typeof this.dbsize != 'undefined') { - s += '(' + this.dbsize; - if (this.dbprecision) s += ',' + this.dbprecision; + yy.Convert = function (params) { + return Object.assign(this, params); + }; + yy.Convert.prototype.toString = function () { + var s = 'CONVERT('; + s += this.dbtypeid; + if (typeof this.dbsize != 'undefined') { + s += '(' + this.dbsize; + if (this.dbprecision) s += ',' + this.dbprecision; + s += ')'; + } + s += ',' + this.expression.toString(); + if (this.style) s += ',' + this.style; s += ')'; - } - s += ',' + this.expression.toString(); - if (this.style) s += ',' + this.style; - s += ')'; - return s; -}; -yy.Convert.prototype.toJS = function (context, tableid, defcols) { - return `alasql.stdfn.CONVERT(${this.expression.toJS(context, tableid, defcols)}, { + return s; + }; + yy.Convert.prototype.toJS = function (context, tableid, defcols) { + return `alasql.stdfn.CONVERT(${this.expression.toJS(context, tableid, defcols)}, { dbtypeid: "${this.dbtypeid}", dbsize: ${this.dbsize}, dbprecision: ${this.dbprecision}, style: ${this.style} })`; -}; - -function structuredDate(unFormattedDate) { - var month = unFormattedDate.getMonth() + 1; - var year = unFormattedDate.getYear(); - var fullYear = unFormattedDate.getFullYear(); - var date = unFormattedDate.getDate(); - var day = unFormattedDate.toString().substr(4, 3); - var formattedDate = ('0' + date).substr(-2); - var formattedMonth = ('0' + month).substr(-2); - var formattedYear = ('0' + year).substr(-2); - var formattedHour = ('0' + unFormattedDate.getHours()).substr(-2); - var formattedMinutes = ('0' + unFormattedDate.getMinutes()).substr(-2); - var formattedSeconds = ('0' + unFormattedDate.getSeconds()).substr(-2); - var formattedMilliseconds = ('00' + unFormattedDate.getMilliseconds()).substr(-3); - return { - month, - year, - fullYear, - date, - day, - formattedDate, - formattedMonth, - formattedYear, - formattedHour, - formattedMinutes, - formattedSeconds, - formattedMilliseconds, }; -} -/** + function structuredDate(unFormattedDate) { + var month = unFormattedDate.getMonth() + 1; + var year = unFormattedDate.getYear(); + var fullYear = unFormattedDate.getFullYear(); + var date = unFormattedDate.getDate(); + var day = unFormattedDate.toString().substr(4, 3); + var formattedDate = ('0' + date).substr(-2); + var formattedMonth = ('0' + month).substr(-2); + var formattedYear = ('0' + year).substr(-2); + var formattedHour = ('0' + unFormattedDate.getHours()).substr(-2); + var formattedMinutes = ('0' + unFormattedDate.getMinutes()).substr(-2); + var formattedSeconds = ('0' + unFormattedDate.getSeconds()).substr(-2); + var formattedMilliseconds = ('00' + unFormattedDate.getMilliseconds()).substr(-3); + return { + month, + year, + fullYear, + date, + day, + formattedDate, + formattedMonth, + formattedYear, + formattedHour, + formattedMinutes, + formattedSeconds, + formattedMilliseconds, + }; + } + + /** Convert one type to another */ -alasql.stdfn.CONVERT = function (value, args) { - var val = value; - var udbtypeid = args.dbtypeid?.toUpperCase(); + alasql.stdfn.CONVERT = function (value, args) { + var val = value; + var udbtypeid = args.dbtypeid?.toUpperCase(); - var t; - var s; - if ( - args.style || - args.dbtypeid == 'Date' || - ['DATE', 'DATETIME', 'DATETIME2'].indexOf(udbtypeid) > -1 - ) { - if (/\d{8}/.test(val)) { - t = new Date(+val.substr(0, 4), +val.substr(4, 2) - 1, +val.substr(6, 2)); - } else { - t = newDate(val); + var t; + var s; + if ( + args.style || + args.dbtypeid == 'Date' || + ['DATE', 'DATETIME', 'DATETIME2'].indexOf(udbtypeid) > -1 + ) { + if (/\d{8}/.test(val)) { + t = new Date(+val.substr(0, 4), +val.substr(4, 2) - 1, +val.substr(6, 2)); + } else { + t = newDate(val); + } + s = structuredDate(t); } - s = structuredDate(t); - } - if (args.style) { - // TODO 9,109, 20,120,21,121,126,130,131 conversions - switch (args.style) { - case 1: // mm/dd/yy - val = s.formattedMonth + '/' + s.formattedDate + '/' + s.formattedYear; - break; - case 2: // yy.mm.dd - val = s.formattedYear + '.' + s.formattedMonth + '.' + s.formattedDate; - break; - case 3: // dd/mm/yy - val = s.formattedDate + '/' + s.formattedMonth + '/' + s.formattedYear; - break; - case 4: // dd.mm.yy - val = s.formattedDate + '.' + s.formattedMonth + '.' + s.formattedYear; - break; - case 5: // dd-mm-yy - val = s.formattedDate + '-' + s.formattedMonth + '-' + s.formattedYear; - break; - case 6: // dd mon yy - val = s.formattedDate + ' ' + s.day.toLowerCase() + ' ' + s.formattedYear; - break; - case 7: // Mon dd,yy - val = s.day + ' ' + s.formattedDate + ',' + s.formattedYear; - break; - case 8: // hh:mm:ss - case 108: // hh:mm:ss - val = s.formattedHour + ':' + s.formattedMinutes + ':' + s.formattedSeconds; - break; - case 10: // mm-dd-yy - val = s.formattedMonth + '-' + s.formattedDate + '-' + s.formattedYear; - break; - case 11: // yy/mm/dd - val = s.formattedYear + '/' + s.formattedMonth + '/' + s.formattedDate; - break; - case 12: // yymmdd - val = s.formattedYear + s.formattedMonth + s.formattedDate; - break; - case 101: // mm/dd/yyyy - val = s.formattedMonth + '/' + s.formattedDate + '/' + s.fullYear; - break; - case 102: // yyyy.mm.dd - val = s.fullYear + '.' + s.formattedMonth + '.' + s.formattedDate; - break; - case 103: // dd/mm/yyyy - val = s.formattedDate + '/' + s.formattedMonth + '/' + s.fullYear; - break; - case 104: // dd.mm.yyyy - val = s.formattedDate + '.' + s.formattedMonth + '.' + s.fullYear; - break; - case 105: // dd-mm-yyyy - val = s.formattedDate + '-' + s.formattedMonth + '-' + s.fullYear; - break; - case 106: // dd mon yyyy - val = s.formattedDate + ' ' + s.day.toLowerCase() + ' ' + s.fullYear; - break; - case 107: // Mon dd,yyyy - val = s.day + ' ' + s.formattedDate + ',' + s.fullYear; - break; - case 110: // mm-dd-yyyy - val = s.formattedMonth + '-' + s.formattedDate + '-' + s.fullYear; - break; - case 111: // yyyy/mm/dd - val = s.fullYear + '/' + s.formattedMonth + '/' + s.formattedDate; - break; + if (args.style) { + // TODO 9,109, 20,120,21,121,126,130,131 conversions + switch (args.style) { + case 1: // mm/dd/yy + val = s.formattedMonth + '/' + s.formattedDate + '/' + s.formattedYear; + break; + case 2: // yy.mm.dd + val = s.formattedYear + '.' + s.formattedMonth + '.' + s.formattedDate; + break; + case 3: // dd/mm/yy + val = s.formattedDate + '/' + s.formattedMonth + '/' + s.formattedYear; + break; + case 4: // dd.mm.yy + val = s.formattedDate + '.' + s.formattedMonth + '.' + s.formattedYear; + break; + case 5: // dd-mm-yy + val = s.formattedDate + '-' + s.formattedMonth + '-' + s.formattedYear; + break; + case 6: // dd mon yy + val = s.formattedDate + ' ' + s.day.toLowerCase() + ' ' + s.formattedYear; + break; + case 7: // Mon dd,yy + val = s.day + ' ' + s.formattedDate + ',' + s.formattedYear; + break; + case 8: // hh:mm:ss + case 108: // hh:mm:ss + val = s.formattedHour + ':' + s.formattedMinutes + ':' + s.formattedSeconds; + break; + case 10: // mm-dd-yy + val = s.formattedMonth + '-' + s.formattedDate + '-' + s.formattedYear; + break; + case 11: // yy/mm/dd + val = s.formattedYear + '/' + s.formattedMonth + '/' + s.formattedDate; + break; + case 12: // yymmdd + val = s.formattedYear + s.formattedMonth + s.formattedDate; + break; + case 101: // mm/dd/yyyy + val = s.formattedMonth + '/' + s.formattedDate + '/' + s.fullYear; + break; + case 102: // yyyy.mm.dd + val = s.fullYear + '.' + s.formattedMonth + '.' + s.formattedDate; + break; + case 103: // dd/mm/yyyy + val = s.formattedDate + '/' + s.formattedMonth + '/' + s.fullYear; + break; + case 104: // dd.mm.yyyy + val = s.formattedDate + '.' + s.formattedMonth + '.' + s.fullYear; + break; + case 105: // dd-mm-yyyy + val = s.formattedDate + '-' + s.formattedMonth + '-' + s.fullYear; + break; + case 106: // dd mon yyyy + val = s.formattedDate + ' ' + s.day.toLowerCase() + ' ' + s.fullYear; + break; + case 107: // Mon dd,yyyy + val = s.day + ' ' + s.formattedDate + ',' + s.fullYear; + break; + case 110: // mm-dd-yyyy + val = s.formattedMonth + '-' + s.formattedDate + '-' + s.fullYear; + break; + case 111: // yyyy/mm/dd + val = s.fullYear + '/' + s.formattedMonth + '/' + s.formattedDate; + break; - case 112: // yyyymmdd - val = s.fullYear + s.formattedMonth + s.formattedDate; - break; - default: - throw new Error('The CONVERT style ' + args.style + ' is not realized yet.'); + case 112: // yyyymmdd + val = s.fullYear + s.formattedMonth + s.formattedDate; + break; + default: + throw new Error('The CONVERT style ' + args.style + ' is not realized yet.'); + } } - } - switch (udbtypeid) { - case 'DATE': - return `${s.formattedYear}.${s.formattedMonth}.${s.formattedDate}`; - case 'DATETIME': - case 'DATETIME2': - return `${s.fullYear}.${s.formattedMonth}.${s.formattedDate} ${s.formattedHour}:${s.formattedMinutes}:${s.formattedSeconds}.${s.formattedMilliseconds}`; - case 'MONEY': - var m = +val; - return (m | 0) + ((m * 100) % 100) / 100; - case 'BOOLEAN': - return !!val; - case 'INT': - case 'INTEGER': - case 'SMALLINT': - case 'BIGINT': - case 'SERIAL': - case 'SMALLSERIAL': - case 'BIGSERIAL': - return val | 0; - case 'STRING': - case 'VARCHAR': - case 'NVARCHAR': - case 'CHARACTER VARIABLE': - return args.dbsize ? String(val).substr(0, args.dbsize) : String(val); - case 'CHAR': - case 'CHARACTER': - case 'NCHAR': - return (val + ' '.repeat(args.dbsize)).substr(0, args.dbsize); - case 'NUMBER': - case 'FLOAT': - case 'DECIMAL': - case 'NUMERIC': - var m = +val; - if (args.dbsize !== undefined) { - m = parseFloat(m.toPrecision(args.dbsize)); - } - if (args.dbprecision !== undefined) { - m = parseFloat(m.toFixed(args.dbprecision)); - } - return m; - case 'JSON': - if (typeof val === 'object') { + switch (udbtypeid) { + case 'DATE': + return `${s.formattedYear}.${s.formattedMonth}.${s.formattedDate}`; + case 'DATETIME': + case 'DATETIME2': + return `${s.fullYear}.${s.formattedMonth}.${s.formattedDate} ${s.formattedHour}:${s.formattedMinutes}:${s.formattedSeconds}.${s.formattedMilliseconds}`; + case 'MONEY': + var m = +val; + return (m | 0) + ((m * 100) % 100) / 100; + case 'BOOLEAN': + return !!val; + case 'INT': + case 'INTEGER': + case 'SMALLINT': + case 'BIGINT': + case 'SERIAL': + case 'SMALLSERIAL': + case 'BIGSERIAL': + return val | 0; + case 'STRING': + case 'VARCHAR': + case 'NVARCHAR': + case 'CHARACTER VARIABLE': + return args.dbsize ? String(val).substr(0, args.dbsize) : String(val); + case 'CHAR': + case 'CHARACTER': + case 'NCHAR': + return (val + ' '.repeat(args.dbsize)).substr(0, args.dbsize); + case 'NUMBER': + case 'FLOAT': + case 'DECIMAL': + case 'NUMERIC': + var m = +val; + if (args.dbsize !== undefined) { + m = parseFloat(m.toPrecision(args.dbsize)); + } + if (args.dbprecision !== undefined) { + m = parseFloat(m.toFixed(args.dbprecision)); + } + return m; + case 'JSON': + if (typeof val === 'object') { + return val; + } + try { + return JSON.parse(val); + } catch (err) { + throw new Error('Cannot convert string to JSON'); + } + case 'Date': return val; - } - try { - return JSON.parse(val); - } catch (err) { - throw new Error('Cannot convert string to JSON'); - } - case 'Date': - return val; - default: - return val; - } -}; + default: + return val; + } + }; } diff --git a/src/expression/expression.js b/src/expression/expression.js index 4c1f23b5fa..cef3f6a7e6 100755 --- a/src/expression/expression.js +++ b/src/expression/expression.js @@ -3,944 +3,948 @@ import {re_invalidFnNameChars} from '../functions/functions.js'; export default function install(alasql) { var yy = alasql.yy; -{ - const assign = Object.assign; + { + const assign = Object.assign; - class ExpressionStatement { - /** @param {object} params Initial parameters */ - constructor(params) { - assign(this, params); - } + class ExpressionStatement { + /** @param {object} params Initial parameters */ + constructor(params) { + assign(this, params); + } - /** + /** Convert AST to string @return {string} */ - toString() { - return this.expression.toString(); - } + toString() { + return this.expression.toString(); + } - /** + /** Execute statement @param {string} databaseid Database identificatro @param {object} params Statement parameters @param {statement-callback} cb Callback @return {object} Result value */ - execute(databaseid, params, cb) { - if (this.expression) { - alasql.precompile(this, databaseid, params); // Precompile queries - var exprfn = new Function( - 'params,alasql,p', - 'var y;return ' + this.expression.toJS('({})', '', null) - ).bind(this); - var res = exprfn(params, alasql); - if (cb) { - res = cb(res); + execute(databaseid, params, cb) { + if (this.expression) { + alasql.precompile(this, databaseid, params); // Precompile queries + var exprfn = new Function( + 'params,alasql,p', + 'var y;return ' + this.expression.toJS('({})', '', null) + ).bind(this); + var res = exprfn(params, alasql); + if (cb) { + res = cb(res); + } + return res; } - return res; } } - } - class Expression { - constructor(params) { - assign(this, params); - } + class Expression { + constructor(params) { + assign(this, params); + } - /** + /** Convert AST to string @return {string} */ - toString() { - var s = this.expression.toString(); - if (this.order) { - s += ' ' + this.order.toString(); - } - if (this.nocase) { - s += ' COLLATE NOCASE'; - } - if (this.direction) { - s += ' ' + this.direction; + toString() { + var s = this.expression.toString(); + if (this.order) { + s += ' ' + this.order.toString(); + } + if (this.nocase) { + s += ' COLLATE NOCASE'; + } + if (this.direction) { + s += ' ' + this.direction; + } + return s; } - return s; - } - /** + /** Find aggregator in AST subtree @param {object} query Query object */ - findAggregator(query) { - if (this.expression.findAggregator) { - this.expression.findAggregator(query); + findAggregator(query) { + if (this.expression.findAggregator) { + this.expression.findAggregator(query); + } } - } - /** + /** Convert AST to JavaScript expression @param {string} context Context string, e.g. 'p','g', or 'x' @param {string} tableid Default table name @param {object} defcols Default columns dictionary @return {string} JavaScript expression */ - toJS(context, tableid, defcols) { - // console.log('Expression',this); - if (this.expression.reduced) { - return 'true'; + toJS(context, tableid, defcols) { + // console.log('Expression',this); + if (this.expression.reduced) { + return 'true'; + } + return this.expression.toJS(context, tableid, defcols); } - return this.expression.toJS(context, tableid, defcols); - } - /** + /** Compile AST to JavaScript expression @param {string} context Context string, e.g. 'p','g', or 'x' @param {string} tableid Default table name @param {object} defcols Default columns dictionary @return {string} JavaScript expression */ - compile(context, tableid, defcols) { - // console.log('Expression',this); - if (this.reduced) { - return returnTrue(); + compile(context, tableid, defcols) { + // console.log('Expression',this); + if (this.reduced) { + return returnTrue(); + } + return new Function('p', 'var y;return ' + this.toJS(context, tableid, defcols)); } - return new Function('p', 'var y;return ' + this.toJS(context, tableid, defcols)); } - } - class JavaScript { - constructor(params) { - assign(this, params); - } - - toString() { - var s = '``' + this.value + '``'; - return s; - } - - toJS() { - return '(' + this.value + ')'; - } - - execute(databaseid, params, cb) { - var res = 1; - var expr = new Function('params,alasql,p', this.value); - expr(params, alasql); - if (cb) { - res = cb(res); + class JavaScript { + constructor(params) { + assign(this, params); } - return res; - } - } - class Literal { - constructor(params) { - assign(this, params); - } + toString() { + var s = '``' + this.value + '``'; + return s; + } - toString() { - var s = this.value; - if (this.value1) { - s = this.value1 + '.' + s; + toJS() { + return '(' + this.value + ')'; } - // else s = tableid+'.'+s; - return s; - } - } - class Join { - constructor(params) { - assign(this, params); + execute(databaseid, params, cb) { + var res = 1; + var expr = new Function('params,alasql,p', this.value); + expr(params, alasql); + if (cb) { + res = cb(res); + } + return res; + } } - toString() { - var s = ' '; - if (this.joinmode) { - s += this.joinmode + ' '; + class Literal { + constructor(params) { + assign(this, params); } - s += 'JOIN ' + this.table.toString(); - return s; - } - } - class Table { - constructor(params) { - assign(this, params); + toString() { + var s = this.value; + if (this.value1) { + s = this.value1 + '.' + s; + } + // else s = tableid+'.'+s; + return s; + } } - toString() { - var s = this.tableid; - // if(this.joinmode) - if (this.databaseid) { - s = this.databaseid + '.' + s; + class Join { + constructor(params) { + assign(this, params); } - return s; - } - } - class View { - constructor(params) { - assign(this, params); + toString() { + var s = ' '; + if (this.joinmode) { + s += this.joinmode + ' '; + } + s += 'JOIN ' + this.table.toString(); + return s; + } } - toString() { - var s = this.viewid; - // if(this.joinmode) - if (this.databaseid) { - s = this.databaseid + '.' + s; + class Table { + constructor(params) { + assign(this, params); } - return s; - } - } - const toTypeNumberOps = new Set(['-', '*', '/', '%', '^', '<<', '>>', '&', '|']); - const toTypeStringOps = new Set(['||']); - // Regex to detect identifiers that need bracket wrapping (spaces, dots, hyphens, square brackets) - const re_needsBrackets = /[\s.\-\[\]]/; - const toTypeBoolOps = new Set([ - 'AND', - 'OR', - 'NOT', - '=', - '==', - '===', - '!=', - '!==', - '!===', - '>', - '>=', - '<', - '<=', - 'IN', - 'NOT IN', - 'LIKE', - 'NOT LIKE', - 'REGEXP', - 'GLOB', - 'BETWEEN', - 'NOT BETWEEN', - 'IS NULL', - 'IS NOT NULL', - ]); - class Op { - constructor(params) { - assign(this, params); + toString() { + var s = this.tableid; + // if(this.joinmode) + if (this.databaseid) { + s = this.databaseid + '.' + s; + } + return s; + } } - toString() { - const leftStr = this.left.toString(); - let s; - - if (this.op === 'IN' || this.op === 'NOT IN') { - return `${leftStr} ${this.op} (${this.right.toString()})`; + class View { + constructor(params) { + assign(this, params); } - if (this.allsome) { - return `${leftStr} ${this.op} ${this.allsome} (${this.right.toString()})`; - } + toString() { + var s = this.viewid; + // if(this.joinmode) + if (this.databaseid) { + s = this.databaseid + '.' + s; + } + return s; + } + } + + const toTypeNumberOps = new Set(['-', '*', '/', '%', '^', '<<', '>>', '&', '|']); + const toTypeStringOps = new Set(['||']); + // Regex to detect identifiers that need bracket wrapping (spaces, dots, hyphens, square brackets) + const re_needsBrackets = /[\s.\-\[\]]/; + const toTypeBoolOps = new Set([ + 'AND', + 'OR', + 'NOT', + '=', + '==', + '===', + '!=', + '!==', + '!===', + '>', + '>=', + '<', + '<=', + 'IN', + 'NOT IN', + 'LIKE', + 'NOT LIKE', + 'REGEXP', + 'GLOB', + 'BETWEEN', + 'NOT BETWEEN', + 'IS NULL', + 'IS NOT NULL', + ]); + class Op { + constructor(params) { + assign(this, params); + } + + toString() { + const leftStr = this.left.toString(); + let s; + + if (this.op === 'IN' || this.op === 'NOT IN') { + return `${leftStr} ${this.op} (${this.right.toString()})`; + } - if (this.op === '->' || this.op === '!') { - s = `${leftStr}${this.op}`; - if (typeof this.right !== 'string' && typeof this.right !== 'number') - return s + `(${this.right.toString()})`; - return s + this.right.toString(); - } + if (this.allsome) { + return `${leftStr} ${this.op} ${this.allsome} (${this.right.toString()})`; + } - if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { - return `${leftStr} ${this.op} ${this.right1.toString()} AND ${this.right2.toString()}`; - } + if (this.op === '->' || this.op === '!') { + s = `${leftStr}${this.op}`; + if (typeof this.right !== 'string' && typeof this.right !== 'number') + return s + `(${this.right.toString()})`; + return s + this.right.toString(); + } - return `${leftStr} ${this.op} ${this.allsome ? this.allsome + ' ' : ''}${this.right.toString()}`; - } + if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { + return `${leftStr} ${this.op} ${this.right1.toString()} AND ${this.right2.toString()}`; + } - findAggregator(query) { - if (this.left && this.left.findAggregator) { - this.left.findAggregator(query); + return `${leftStr} ${this.op} ${this.allsome ? this.allsome + ' ' : ''}${this.right.toString()}`; } - // Handle BETWEEN operators which have right1 and right2 - if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { - if (this.right1 && this.right1.findAggregator) { - this.right1.findAggregator(query); + + findAggregator(query) { + if (this.left && this.left.findAggregator) { + this.left.findAggregator(query); + } + // Handle BETWEEN operators which have right1 and right2 + if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { + if (this.right1 && this.right1.findAggregator) { + this.right1.findAggregator(query); + } + if (this.right2 && this.right2.findAggregator) { + this.right2.findAggregator(query); + } } - if (this.right2 && this.right2.findAggregator) { - this.right2.findAggregator(query); + // Do not go in > ALL + if (this.right && this.right.findAggregator && !this.allsome) { + this.right.findAggregator(query); } } - // Do not go in > ALL - if (this.right && this.right.findAggregator && !this.allsome) { - this.right.findAggregator(query); - } - } - toType(tableid) { - if (toTypeNumberOps.has(this.op)) return 'number'; + toType(tableid) { + if (toTypeNumberOps.has(this.op)) return 'number'; - if (toTypeStringOps.has(this.op)) return 'string'; + if (toTypeStringOps.has(this.op)) return 'string'; - if (this.op === '+') { - const leftType = this.left.toType(tableid); - const rightType = this.right.toType(tableid); + if (this.op === '+') { + const leftType = this.left.toType(tableid); + const rightType = this.right.toType(tableid); - if (leftType === 'string' || rightType === 'string') { - return 'string'; - } - if (leftType === 'number' || rightType === 'number') { - return 'number'; + if (leftType === 'string' || rightType === 'string') { + return 'string'; + } + if (leftType === 'number' || rightType === 'number') { + return 'number'; + } } - } - if (toTypeBoolOps.has(this.op) || this.allsome) return 'boolean'; + if (toTypeBoolOps.has(this.op) || this.allsome) return 'boolean'; - if (!this.op) return this.left.toType(tableid); + if (!this.op) return this.left.toType(tableid); - return 'unknown'; - } + return 'unknown'; + } - toJS(context, tableid, defcols) { - var s; - let refs = []; - let op = this.op; - let skipNullCheck = false; // Flag to skip null checking for deterministic empty set operations - let _this = this; - let ref = function (expr) { - if (expr.toJS) { - expr = expr.toJS(context, tableid, defcols); - } - let i = refs.push(expr) - 1; - return 'y[' + i + ']'; - }; - var leftJS = function () { - return ref(_this.left); - }; - var rightJS = function () { - return ref(_this.right); - }; - - if (this.op === '=') { - op = '==='; - } else if (this.op === '<>') { - op = '!='; - } else if (this.op === 'OR') { - op = '||'; - } else if (this.op === '->') { - // Expression to prevent error if object is empty (#344) - const ljs = `(${leftJS()} || {})`; - - if (typeof this.right === 'string') { - s = `${ljs}["${escapeq(this.right)}"]`; - } else if (typeof this.right === 'number') { - s = `${ljs}[${this.right}]`; - } else if (this.right instanceof yy.FuncValue) { - let ss = []; - if (this.right.args && this.right.args.length > 0) { - ss = this.right.args.map(ref); + toJS(context, tableid, defcols) { + var s; + let refs = []; + let op = this.op; + let skipNullCheck = false; // Flag to skip null checking for deterministic empty set operations + let _this = this; + let ref = function (expr) { + if (expr.toJS) { + expr = expr.toJS(context, tableid, defcols); } - s = `${ljs}[${JSON.stringify(this.right.funcid)}](${ss.join(',')})`; - } else { - s = `${ljs}[${rightJS()}]`; - } - } else if (this.op === '!') { - if (typeof this.right === 'string') { - s = `alasql.databases[alasql.useid].objects[${leftJS()}]["${this.right}"]`; - } - // TODO - add other cases - } else if (this.op === 'IS') { - const leftOperand = leftJS(); - const rightOperand = rightJS(); - if ( - this.right instanceof yy.NullValue || - (this.right.op === 'NOT' && this.right.right instanceof yy.NullValue) - ) { - s = `((${leftOperand} == null) === (${rightOperand} == null))`; // == null can't be === - } else { - s = `((${leftOperand} == ${rightOperand}) || (${leftOperand} < 0 && true == ${rightOperand}))`; - } - } else if (this.op === '==') { - s = `alasql.utils.deepEqual(${leftJS()}, ${rightJS()})`; - } else if (this.op === '===' || this.op === '!===') { - s = `(${this.op === '!===' ? '!' : ''}((${leftJS()}).valueOf() === (${rightJS()}).valueOf()))`; - } else if (this.op === '!==') { - s = `(!alasql.utils.deepEqual(${leftJS()}, ${rightJS()}))`; - } else if (this.op === '||') { - s = `(''+(${leftJS()} || '') + (${rightJS()} || ''))`; - } else if (this.op === 'LIKE' || this.op === 'NOT LIKE') { - s = `(${this.op === 'NOT LIKE' ? '!' : ''}alasql.utils.like(${rightJS()}, ${leftJS()}${this.escape ? `, ${ref(this.escape)}` : ''}))`; - } else if (this.op === 'REGEXP') { - s = `alasql.stdfn.REGEXP_LIKE(${leftJS()}, ${rightJS()})`; - } else if (this.op === 'GLOB') { - s = `alasql.utils.glob(${leftJS()}, ${rightJS()})`; - } else if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { - const left = leftJS(); - s = `(${this.op === 'NOT BETWEEN' ? '!' : ''}((${ref(this.right1)} <= ${left}) && (${left} <= ${ref(this.right2)})))`; - } else if (this.op === 'IN') { - if (this.right instanceof yy.Select) { - // Check if this is a correlated subquery (references outer tables) - // If correlated, we cannot cache the results as they depend on the current row - const cacheKey = `in${this.queriesidx}`; - const checkCorrelated = `(this.queriesfn[${this.queriesidx}].query && this.queriesfn[${this.queriesidx}].query.isCorrelated)`; - const cachedLookup = `((this.subqueryCache = this.subqueryCache || {}, this.subqueryCache.${cacheKey} || (this.subqueryCache.${cacheKey} = new Set(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).map(alasql.utils.getValueOf)))).has(alasql.utils.getValueOf(${leftJS()})))`; - const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) > -1)`; - s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`; - } else if (Array.isArray(this.right)) { - // Empty array: nothing is IN an empty set, always false - if (this.right.length === 0) { - // Must call leftJS() to populate the refs array for the declareRefs statement, - // even though the result is not used in the final expression - leftJS(); - s = 'false'; - skipNullCheck = true; // Result is deterministic even with null operands - } else if ( - !alasql.options.cache || - this.right.some(value => value instanceof yy.ParamValue) - ) { - // Leverage JS Set for faster lookups than arrays - s = `(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()})))`; + let i = refs.push(expr) - 1; + return 'y[' + i + ']'; + }; + var leftJS = function () { + return ref(_this.left); + }; + var rightJS = function () { + return ref(_this.right); + }; + + if (this.op === '=') { + op = '==='; + } else if (this.op === '<>') { + op = '!='; + } else if (this.op === 'OR') { + op = '||'; + } else if (this.op === '->') { + // Expression to prevent error if object is empty (#344) + const ljs = `(${leftJS()} || {})`; + + if (typeof this.right === 'string') { + s = `${ljs}["${escapeq(this.right)}"]`; + } else if (typeof this.right === 'number') { + s = `${ljs}[${this.right}]`; + } else if (this.right instanceof yy.FuncValue) { + let ss = []; + if (this.right.args && this.right.args.length > 0) { + ss = this.right.args.map(ref); + } + s = `${ljs}[${JSON.stringify(this.right.funcid)}](${ss.join(',')})`; } else { - // Use a cache to avoid re-creating the Set on every identical query - alasql.sets = alasql.sets || {}; - const allValues = this.right.map(value => value.value); - const allValuesStr = allValues.join(','); - alasql.sets[allValuesStr] = alasql.sets[allValuesStr] || new Set(allValues); - s = `alasql.sets["${allValuesStr}"].has(alasql.utils.getValueOf(${leftJS()}))`; + s = `${ljs}[${rightJS()}]`; } - } else { - s = `(${rightJS()}.indexOf(${leftJS()}) > -1)`; - } - } else if (this.op === 'NOT IN') { - if (this.right instanceof yy.Select) { - // Check if this is a correlated subquery (references outer tables) - // If correlated, we cannot cache the results as they depend on the current row - const cacheKey = `notIn${this.queriesidx}`; - const checkCorrelated = `(this.queriesfn[${this.queriesidx}].query && this.queriesfn[${this.queriesidx}].query.isCorrelated)`; - const cachedLookup = `(!(this.subqueryCache = this.subqueryCache || {}, this.subqueryCache.${cacheKey} || (this.subqueryCache.${cacheKey} = new Set(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).map(alasql.utils.getValueOf)))).has(alasql.utils.getValueOf(${leftJS()})))`; - const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) < 0)`; - s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`; - } else if (Array.isArray(this.right)) { - // Empty array: everything is NOT IN an empty set, always true - if (this.right.length === 0) { - // Must call leftJS() to populate the refs array for the declareRefs statement, - // even though the result is not used in the final expression - leftJS(); - s = 'true'; - skipNullCheck = true; // Result is deterministic even with null operands - } else if ( - !alasql.options.cache || - this.right.some(value => value instanceof yy.ParamValue) + } else if (this.op === '!') { + if (typeof this.right === 'string') { + s = `alasql.databases[alasql.useid].objects[${leftJS()}]["${this.right}"]`; + } + // TODO - add other cases + } else if (this.op === 'IS') { + const leftOperand = leftJS(); + const rightOperand = rightJS(); + if ( + this.right instanceof yy.NullValue || + (this.right.op === 'NOT' && this.right.right instanceof yy.NullValue) ) { - // Leverage JS Set for faster lookups than arrays - s = `(!(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()}))))`; + s = `((${leftOperand} == null) === (${rightOperand} == null))`; // == null can't be === } else { - // Use a cache to avoid re-creating the Set on every identical query - alasql.sets = alasql.sets || {}; - const allValues = this.right.map(value => value.value); - const allValuesStr = allValues.join(','); - alasql.sets[allValuesStr] = alasql.sets[allValuesStr] || new Set(allValues); - s = `!alasql.sets["${allValuesStr}"].has(alasql.utils.getValueOf(${leftJS()}))`; + s = `((${leftOperand} == ${rightOperand}) || (${leftOperand} < 0 && true == ${rightOperand}))`; + } + } else if (this.op === '==') { + s = `alasql.utils.deepEqual(${leftJS()}, ${rightJS()})`; + } else if (this.op === '===' || this.op === '!===') { + s = `(${this.op === '!===' ? '!' : ''}((${leftJS()}).valueOf() === (${rightJS()}).valueOf()))`; + } else if (this.op === '!==') { + s = `(!alasql.utils.deepEqual(${leftJS()}, ${rightJS()}))`; + } else if (this.op === '||') { + s = `(''+(${leftJS()} || '') + (${rightJS()} || ''))`; + } else if (this.op === 'LIKE' || this.op === 'NOT LIKE') { + s = `(${this.op === 'NOT LIKE' ? '!' : ''}alasql.utils.like(${rightJS()}, ${leftJS()}${this.escape ? `, ${ref(this.escape)}` : ''}))`; + } else if (this.op === 'REGEXP') { + s = `alasql.stdfn.REGEXP_LIKE(${leftJS()}, ${rightJS()})`; + } else if (this.op === 'GLOB') { + s = `alasql.utils.glob(${leftJS()}, ${rightJS()})`; + } else if (this.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { + const left = leftJS(); + s = `(${this.op === 'NOT BETWEEN' ? '!' : ''}((${ref(this.right1)} <= ${left}) && (${left} <= ${ref(this.right2)})))`; + } else if (this.op === 'IN') { + if (this.right instanceof yy.Select) { + // Check if this is a correlated subquery (references outer tables) + // If correlated, we cannot cache the results as they depend on the current row + const cacheKey = `in${this.queriesidx}`; + const checkCorrelated = `(this.queriesfn[${this.queriesidx}].query && this.queriesfn[${this.queriesidx}].query.isCorrelated)`; + const cachedLookup = `((this.subqueryCache = this.subqueryCache || {}, this.subqueryCache.${cacheKey} || (this.subqueryCache.${cacheKey} = new Set(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).map(alasql.utils.getValueOf)))).has(alasql.utils.getValueOf(${leftJS()})))`; + const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) > -1)`; + s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`; + } else if (Array.isArray(this.right)) { + // Empty array: nothing is IN an empty set, always false + if (this.right.length === 0) { + // Must call leftJS() to populate the refs array for the declareRefs statement, + // even though the result is not used in the final expression + leftJS(); + s = 'false'; + skipNullCheck = true; // Result is deterministic even with null operands + } else if ( + !alasql.options.cache || + this.right.some(value => value instanceof yy.ParamValue) + ) { + // Leverage JS Set for faster lookups than arrays + s = `(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()})))`; + } else { + // Use a cache to avoid re-creating the Set on every identical query + alasql.sets = alasql.sets || {}; + const allValues = this.right.map(value => value.value); + const allValuesStr = allValues.join(','); + alasql.sets[allValuesStr] = alasql.sets[allValuesStr] || new Set(allValues); + s = `alasql.sets["${allValuesStr}"].has(alasql.utils.getValueOf(${leftJS()}))`; + } + } else { + s = `(${rightJS()}.indexOf(${leftJS()}) > -1)`; + } + } else if (this.op === 'NOT IN') { + if (this.right instanceof yy.Select) { + // Check if this is a correlated subquery (references outer tables) + // If correlated, we cannot cache the results as they depend on the current row + const cacheKey = `notIn${this.queriesidx}`; + const checkCorrelated = `(this.queriesfn[${this.queriesidx}].query && this.queriesfn[${this.queriesidx}].query.isCorrelated)`; + const cachedLookup = `(!(this.subqueryCache = this.subqueryCache || {}, this.subqueryCache.${cacheKey} || (this.subqueryCache.${cacheKey} = new Set(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).map(alasql.utils.getValueOf)))).has(alasql.utils.getValueOf(${leftJS()})))`; + const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) < 0)`; + s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`; + } else if (Array.isArray(this.right)) { + // Empty array: everything is NOT IN an empty set, always true + if (this.right.length === 0) { + // Must call leftJS() to populate the refs array for the declareRefs statement, + // even though the result is not used in the final expression + leftJS(); + s = 'true'; + skipNullCheck = true; // Result is deterministic even with null operands + } else if ( + !alasql.options.cache || + this.right.some(value => value instanceof yy.ParamValue) + ) { + // Leverage JS Set for faster lookups than arrays + s = `(!(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()}))))`; + } else { + // Use a cache to avoid re-creating the Set on every identical query + alasql.sets = alasql.sets || {}; + const allValues = this.right.map(value => value.value); + const allValuesStr = allValues.join(','); + alasql.sets[allValuesStr] = alasql.sets[allValuesStr] || new Set(allValues); + s = `!alasql.sets["${allValuesStr}"].has(alasql.utils.getValueOf(${leftJS()}))`; + } + } else { + s = `(${rightJS()}.indexOf(${leftJS()}) === -1)`; } - } else { - s = `(${rightJS()}.indexOf(${leftJS()}) === -1)`; } - } - if (this.allsome === 'ALL') { - var s; - if (this.right instanceof yy.Select) { - s = - 'alasql.utils.flatArray(this.query.queriesfn[' + this.queriesidx + '](params,null,p))'; - - s += '.every(function(b){return ('; - s += leftJS() + ')' + op + 'b})'; - } else if (Array.isArray(this.right)) { - s = - '' + - (this.right.length == 1 - ? ref(this.right[0]) - : '[' + this.right.map(ref).join(',') + ']'); - s += '.every(function(b){return ('; - s += leftJS() + ')' + op + 'b})'; - } else { - throw new Error('NOT IN operator without SELECT'); + if (this.allsome === 'ALL') { + var s; + if (this.right instanceof yy.Select) { + s = + 'alasql.utils.flatArray(this.query.queriesfn[' + + this.queriesidx + + '](params,null,p))'; + + s += '.every(function(b){return ('; + s += leftJS() + ')' + op + 'b})'; + } else if (Array.isArray(this.right)) { + s = + '' + + (this.right.length == 1 + ? ref(this.right[0]) + : '[' + this.right.map(ref).join(',') + ']'); + s += '.every(function(b){return ('; + s += leftJS() + ')' + op + 'b})'; + } else { + throw new Error('NOT IN operator without SELECT'); + } } - } - if (this.allsome === 'SOME' || this.allsome === 'ANY') { - var s; - if (this.right instanceof yy.Select) { - s = - 'alasql.utils.flatArray(this.query.queriesfn[' + this.queriesidx + '](params,null,p))'; - s += '.some(function(b){return ('; - s += leftJS() + ')' + op + 'b})'; - } else if (Array.isArray(this.right)) { - s = - '' + - (this.right.length == 1 - ? ref(this.right[0]) - : '[' + this.right.map(ref).join(',') + ']'); - s += '.some(function(b){return ('; - s += leftJS() + ')' + op + 'b})'; - } else { - throw new Error('SOME/ANY operator without SELECT'); + if (this.allsome === 'SOME' || this.allsome === 'ANY') { + var s; + if (this.right instanceof yy.Select) { + s = + 'alasql.utils.flatArray(this.query.queriesfn[' + + this.queriesidx + + '](params,null,p))'; + s += '.some(function(b){return ('; + s += leftJS() + ')' + op + 'b})'; + } else if (Array.isArray(this.right)) { + s = + '' + + (this.right.length == 1 + ? ref(this.right[0]) + : '[' + this.right.map(ref).join(',') + ']'); + s += '.some(function(b){return ('; + s += leftJS() + ')' + op + 'b})'; + } else { + throw new Error('SOME/ANY operator without SELECT'); + } } - } - // Special case for AND optimization (if reduced) - if (this.op === 'AND') { - if (this.left.reduced) { - if (this.right.reduced) { - return 'true'; - } else { - s = rightJS(); + // Special case for AND optimization (if reduced) + if (this.op === 'AND') { + if (this.left.reduced) { + if (this.right.reduced) { + return 'true'; + } else { + s = rightJS(); + } + } else if (this.right.reduced) { + s = leftJS(); } - } else if (this.right.reduced) { - s = leftJS(); + + // Otherwise process as regular operation (see below) + op = '&&'; + } + + var expr = s || '(' + leftJS() + op + rightJS() + ')'; + + var declareRefs = 'y=[(' + refs.join('), (') + ')]'; + if ( + skipNullCheck || + op === '&&' || + op === '||' || + op === 'IS' || + op === 'IS NULL' || + op === 'IS NOT NULL' + ) { + return '(' + declareRefs + ', ' + expr + ')'; } - // Otherwise process as regular operation (see below) - op = '&&'; + return `(${declareRefs}, y.some(e => e == null || (typeof e === 'number' && isNaN(e))) ? void 0 : ${expr})`; } + } - var expr = s || '(' + leftJS() + op + rightJS() + ')'; + class VarValue { + constructor(params) { + assign(this, params); + } - var declareRefs = 'y=[(' + refs.join('), (') + ')]'; - if ( - skipNullCheck || - op === '&&' || - op === '||' || - op === 'IS' || - op === 'IS NULL' || - op === 'IS NOT NULL' - ) { - return '(' + declareRefs + ', ' + expr + ')'; + toString() { + return '@' + this.variable; } - return `(${declareRefs}, y.some(e => e == null || (typeof e === 'number' && isNaN(e))) ? void 0 : ${expr})`; - } - } + toType() { + return 'unknown'; + } - class VarValue { - constructor(params) { - assign(this, params); + toJS() { + return "alasql.vars['" + escapeq(this.variable) + "']"; + } } - toString() { - return '@' + this.variable; - } + class NumValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'unknown'; - } + toString() { + return this.value.toString(); + } - toJS() { - return "alasql.vars['" + escapeq(this.variable) + "']"; - } - } + toType() { + return 'number'; + } - class NumValue { - constructor(params) { - assign(this, params); + toJS() { + return '' + this.value; + } } - toString() { - return this.value.toString(); - } + class StringValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'number'; - } + toString() { + return "'" + this.value.toString() + "'"; + } - toJS() { - return '' + this.value; - } - } + toType() { + return 'string'; + } - class StringValue { - constructor(params) { - assign(this, params); + toJS() { + return "'" + escapeq(this.value) + "'"; + } } - toString() { - return "'" + this.value.toString() + "'"; - } + class DomainValueValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'string'; - } + toString() { + return 'VALUE'; + } - toJS() { - return "'" + escapeq(this.value) + "'"; - } - } + toType() { + return 'object'; + } - class DomainValueValue { - constructor(params) { - assign(this, params); + toJS(context, tableid, defcols) { + return context; + } } - toString() { - return 'VALUE'; - } + class ArrayValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'object'; - } + toString() { + return 'ARRAY[]'; + } - toJS(context, tableid, defcols) { - return context; - } - } + toType() { + return 'object'; + } - class ArrayValue { - constructor(params) { - assign(this, params); + toJS(context, tableid, defcols) { + return ( + '[(' + + this.value + .map(function (el) { + return el.toJS(context, tableid, defcols); + }) + .join('), (') + + ')]' + ); + } } - toString() { - return 'ARRAY[]'; - } + class LogicValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'object'; - } + toString() { + return this.value ? 'TRUE' : 'FALSE'; + } - toJS(context, tableid, defcols) { - return ( - '[(' + - this.value - .map(function (el) { - return el.toJS(context, tableid, defcols); - }) - .join('), (') + - ')]' - ); - } - } + toType() { + return 'boolean'; + } - class LogicValue { - constructor(params) { - assign(this, params); + toJS() { + return this.value ? 'true' : 'false'; + } } - toString() { - return this.value ? 'TRUE' : 'FALSE'; - } + class NullValue { + constructor(params) { + assign(this, params); + } - toType() { - return 'boolean'; - } + toString() { + return 'NULL'; + } - toJS() { - return this.value ? 'true' : 'false'; + toJS() { + return 'undefined'; + } } - } - class NullValue { - constructor(params) { - assign(this, params); - } + class ParamValue { + constructor(params) { + assign(this, params); + } - toString() { - return 'NULL'; - } + toString() { + return '$' + this.param; + } - toJS() { - return 'undefined'; - } - } + toJS() { + if (typeof this.param === 'string') { + return "params['" + this.param + "']"; + } - class ParamValue { - constructor(params) { - assign(this, params); + return 'params[' + this.param + ']'; + } } - toString() { - return '$' + this.param; - } + const toJsOpMapping = { + '~': '~', + '-': '-', + '+': '+', + NOT: '!', + }; - toJS() { - if (typeof this.param === 'string') { - return "params['" + this.param + "']"; + class UniOp { + constructor(params) { + assign(this, params); } - return 'params[' + this.param + ']'; - } - } - - const toJsOpMapping = { - '~': '~', - '-': '-', - '+': '+', - NOT: '!', - }; + toString() { + const {op, right} = this; + const res = right.toString(); - class UniOp { - constructor(params) { - assign(this, params); - } + switch (op) { + case '~': + case '-': + case '+': + case '#': + return op + res; + case 'NOT': + return op + '(' + res + ')'; + default: + return '(' + res + ')'; + } + } - toString() { - const {op, right} = this; - const res = right.toString(); - - switch (op) { - case '~': - case '-': - case '+': - case '#': - return op + res; - case 'NOT': - return op + '(' + res + ')'; - default: - return '(' + res + ')'; + findAggregator(query) { + if (this.right.findAggregator) { + this.right.findAggregator(query); + } } - } - findAggregator(query) { - if (this.right.findAggregator) { - this.right.findAggregator(query); + toType() { + switch (this.op) { + case '-': + case '+': + return 'number'; + case 'NOT': + return 'boolean'; + default: + return 'string'; + } } - } - toType() { - switch (this.op) { - case '-': - case '+': - return 'number'; - case 'NOT': - return 'boolean'; - default: - return 'string'; + toJS(context, tableid, defcols) { + if (this.right instanceof Column && this.op === '#') { + return `(alasql.databases[alasql.useid].objects['${this.right.columnid}'])`; + } + + const rightJS = this.right.toJS(context, tableid, defcols); + + if (toJsOpMapping.hasOwnProperty(this.op)) { + return `(${toJsOpMapping[this.op]}(${rightJS}))`; + } + + if (this.op == null) { + return `(${rightJS})`; + } + + throw new Error(`Unsupported operator: ${this.op}`); } } - toJS(context, tableid, defcols) { - if (this.right instanceof Column && this.op === '#') { - return `(alasql.databases[alasql.useid].objects['${this.right.columnid}'])`; + class Column { + constructor(params) { + assign(this, params); } - const rightJS = this.right.toJS(context, tableid, defcols); + // Check if identifier needs to be wrapped in brackets + // (numeric values, or contains spaces, dots, or other special characters) + static needsBrackets(id) { + if (id == null) return false; + // Numeric indices need brackets + if (id == +id) return true; + // Check for special characters that require brackets + return re_needsBrackets.test(id); + } - if (toJsOpMapping.hasOwnProperty(this.op)) { - return `(${toJsOpMapping[this.op]}(${rightJS}))`; + static wrapId(id) { + if (Column.needsBrackets(id)) { + return '[' + id + ']'; + } + return id; } - if (this.op == null) { - return `(${rightJS})`; + // Value of an unqualified column from the first source where it is not null + static fromAnySource(context, aliases, columnid) { + return ( + '(' + aliases.map(alias => `${context}['${alias}']?.['${columnid}']`).join(' ?? ') + ')' + ); } - throw new Error(`Unsupported operator: ${this.op}`); - } - } + toString() { + const colNeedsBrackets = Column.needsBrackets(this.columnid); + let s = colNeedsBrackets ? '[' + this.columnid + ']' : this.columnid; - class Column { - constructor(params) { - assign(this, params); - } + if (this.tableid) { + // Omit dot separator when columnid is wrapped in brackets (e.g., table[1] not table.[1]) + const separator = colNeedsBrackets ? '' : '.'; + s = Column.wrapId(this.tableid) + separator + s; - // Check if identifier needs to be wrapped in brackets - // (numeric values, or contains spaces, dots, or other special characters) - static needsBrackets(id) { - if (id == null) return false; - // Numeric indices need brackets - if (id == +id) return true; - // Check for special characters that require brackets - return re_needsBrackets.test(id); - } + if (this.databaseid) { + s = Column.wrapId(this.databaseid) + '.' + s; + } + } - static wrapId(id) { - if (Column.needsBrackets(id)) { - return '[' + id + ']'; + return s; } - return id; - } - // Value of an unqualified column from the first source where it is not null - static fromAnySource(context, aliases, columnid) { - return ( - '(' + aliases.map(alias => `${context}['${alias}']?.['${columnid}']`).join(' ?? ') + ')' - ); - } + toJS(context, tableid, defcols) { + if (!this.tableid && tableid === '' && !defcols) { + return this.columnid !== '_' + ? `${context}['${this.columnid}']` + : context === 'g' + ? "g['_']" + : context; + } - toString() { - const colNeedsBrackets = Column.needsBrackets(this.columnid); - let s = colNeedsBrackets ? '[' + this.columnid + ']' : this.columnid; + if (context === 'g') { + // When accessing grouped columns, use columnid (without table prefix) if nick is not set + // This handles cases like: SELECT a.id + 1 FROM ... GROUP BY a.id + // where the column in the expression doesn't have nick set, but the group stores it by columnid + const nickToUse = this.nick || this.columnid; + return `g['${nickToUse}']`; + } - if (this.tableid) { - // Omit dot separator when columnid is wrapped in brackets (e.g., table[1] not table.[1]) - const separator = colNeedsBrackets ? '' : '.'; - s = Column.wrapId(this.tableid) + separator + s; + if (this.tableid) { + return this.columnid !== '_' + ? `${context}['${this.tableid}']['${this.columnid}']` + : context === 'g' + ? "g['_']" + : `${context}['${this.tableid}']`; + } - if (this.databaseid) { - s = Column.wrapId(this.databaseid) + '.' + s; + if (defcols) { + const tbid = defcols[this.columnid]; + if (tbid === '-') { + throw new Error( + `Cannot resolve column "${this.columnid}" because it exists in two source tables` + ); + } else if (tbid) { + return this.columnid !== '_' + ? `${context}['${tbid}']['${this.columnid}']` + : `${context}['${tbid}']`; + } else if (this.columnid !== '_' && Object.keys(defcols['.']).length > 1) { + // Not a column of any table here, so it comes from a subquery, parameter + // or table without column definitions (#832) + return Column.fromAnySource(context, Object.keys(defcols['.']), this.columnid); + } else { + return this.columnid !== '_' + ? `${context}['${this.tableid || tableid}']['${this.columnid}']` + : `${context}['${this.tableid || tableid}']`; + } } - } - return s; - } + if (tableid === -1) { + return `${context}['${this.columnid}']`; + } - toJS(context, tableid, defcols) { - if (!this.tableid && tableid === '' && !defcols) { return this.columnid !== '_' - ? `${context}['${this.columnid}']` - : context === 'g' - ? "g['_']" - : context; + ? `${context}['${this.tableid || tableid}']['${this.columnid}']` + : `${context}['${this.tableid || tableid}']`; } + } - if (context === 'g') { - // When accessing grouped columns, use columnid (without table prefix) if nick is not set - // This handles cases like: SELECT a.id + 1 FROM ... GROUP BY a.id - // where the column in the expression doesn't have nick set, but the group stores it by columnid - const nickToUse = this.nick || this.columnid; - return `g['${nickToUse}']`; + class AggrValue { + constructor(params) { + assign(this, params); } - if (this.tableid) { - return this.columnid !== '_' - ? `${context}['${this.tableid}']['${this.columnid}']` - : context === 'g' - ? "g['_']" - : `${context}['${this.tableid}']`; - } - - if (defcols) { - const tbid = defcols[this.columnid]; - if (tbid === '-') { - throw new Error( - `Cannot resolve column "${this.columnid}" because it exists in two source tables` - ); - } else if (tbid) { - return this.columnid !== '_' - ? `${context}['${tbid}']['${this.columnid}']` - : `${context}['${tbid}']`; - } else if (this.columnid !== '_' && Object.keys(defcols['.']).length > 1) { - // Not a column of any table here, so it comes from a subquery, parameter - // or table without column definitions (#832) - return Column.fromAnySource(context, Object.keys(defcols['.']), this.columnid); - } else { - return this.columnid !== '_' - ? `${context}['${this.tableid || tableid}']['${this.columnid}']` - : `${context}['${this.tableid || tableid}']`; - } - } + toString() { + const funcName = + this.aggregatorid === 'REDUCE' + ? this.funcid.replace(re_invalidFnNameChars, '') + : this.aggregatorid; + const distinctPart = this.distinct ? 'DISTINCT ' : ''; + const expressionPart = this.expression ? this.expression.toString() : ''; + const overPart = this.over ? ` ${this.over.toString()}` : ''; - if (tableid === -1) { - return `${context}['${this.columnid}']`; + return `${funcName}(${distinctPart}${expressionPart})${overPart}`; } - return this.columnid !== '_' - ? `${context}['${this.tableid || tableid}']['${this.columnid}']` - : `${context}['${this.tableid || tableid}']`; - } - } - - class AggrValue { - constructor(params) { - assign(this, params); - } - - toString() { - const funcName = - this.aggregatorid === 'REDUCE' - ? this.funcid.replace(re_invalidFnNameChars, '') - : this.aggregatorid; - const distinctPart = this.distinct ? 'DISTINCT ' : ''; - const expressionPart = this.expression ? this.expression.toString() : ''; - const overPart = this.over ? ` ${this.over.toString()}` : ''; + findAggregator(query) { + // Skip adding window aggregates (aggregates with OVER clause) to selectGroup + // They will be handled separately as window functions + if (this.over) { + return; + } - return `${funcName}(${distinctPart}${expressionPart})${overPart}`; - } + // Check if an identical aggregate already exists in selectGroup + let existingAggr = query.selectGroup.find(agg => agg.toString() === this.toString()); - findAggregator(query) { - // Skip adding window aggregates (aggregates with OVER clause) to selectGroup - // They will be handled separately as window functions - if (this.over) { - return; - } - - // Check if an identical aggregate already exists in selectGroup - let existingAggr = query.selectGroup.find(agg => agg.toString() === this.toString()); - - if (existingAggr) { - // Reuse the existing aggregate's nick to share the same accumulator - this.aggrNick = existingAggr.nick; - } else { - // New aggregate - assign nick and add to selectGroup - if (!this.nick) { - this.nick = escapeq(this.toString()) + ':' + query.selectGroup.length; - if (!query.removeKeys.includes(this.nick)) { - query.removeKeys.push(this.nick); + if (existingAggr) { + // Reuse the existing aggregate's nick to share the same accumulator + this.aggrNick = existingAggr.nick; + } else { + // New aggregate - assign nick and add to selectGroup + if (!this.nick) { + this.nick = escapeq(this.toString()) + ':' + query.selectGroup.length; + if (!query.removeKeys.includes(this.nick)) { + query.removeKeys.push(this.nick); + } } + this.aggrNick = this.nick; + query.selectGroup.push(this); } - this.aggrNick = this.nick; - query.selectGroup.push(this); } - } - toType() { - if ( - ['SUM', 'COUNT', 'AVG', 'MIN', 'MAX', 'AGGR', 'VAR', 'STDDEV', 'TOTAL'].includes( - this.aggregatorid - ) - ) { - return 'number'; - } + toType() { + if ( + ['SUM', 'COUNT', 'AVG', 'MIN', 'MAX', 'AGGR', 'VAR', 'STDDEV', 'TOTAL'].includes( + this.aggregatorid + ) + ) { + return 'number'; + } + + if (this.aggregatorid === 'ARRAY') { + return 'array'; + } - if (this.aggregatorid === 'ARRAY') { - return 'array'; + return this.expression.toType(); } - return this.expression.toType(); + toJS() { + // Use aggrNick for duplicate aggregates to share the same accumulator, otherwise use the unique nick + var colas = this.aggrNick || this.nick; + if (colas === undefined) { + colas = escapeq(this.toString()); + } + return "g['" + colas + "']"; + } } - toJS() { - // Use aggrNick for duplicate aggregates to share the same accumulator, otherwise use the unique nick - var colas = this.aggrNick || this.nick; - if (colas === undefined) { - colas = escapeq(this.toString()); + class OrderExpression { + constructor(params) { + assign(this, params); } - return "g['" + colas + "']"; } - } - class OrderExpression { - constructor(params) { - assign(this, params); - } - } + OrderExpression.prototype.toString = Expression.prototype.toString; - OrderExpression.prototype.toString = Expression.prototype.toString; + class GroupExpression { + constructor(params) { + assign(this, params); + } - class GroupExpression { - constructor(params) { - assign(this, params); + toString() { + return this.type + '(' + this.group.toString() + ')'; + } } - toString() { - return this.type + '(' + this.group.toString() + ')'; - } + assign(yy, { + AggrValue, + ArrayValue, + Column, + DomainValueValue, + Expression, + ExpressionStatement, + GroupExpression, + JavaScript, + Join, + Literal, + LogicValue, + NullValue, + NumValue, + Op, + OrderExpression, + ParamValue, + StringValue, + Table, + UniOp, + VarValue, + View, + }); } - - assign(yy, { - AggrValue, - ArrayValue, - Column, - DomainValueValue, - Expression, - ExpressionStatement, - GroupExpression, - JavaScript, - Join, - Literal, - LogicValue, - NullValue, - NumValue, - Op, - OrderExpression, - ParamValue, - StringValue, - Table, - UniOp, - VarValue, - View, - }); -} } diff --git a/src/expression/json.js b/src/expression/json.js index eaed7e951b..9d7d3b2ac7 100755 --- a/src/expression/json.js +++ b/src/expression/json.js @@ -1,6 +1,6 @@ export default function install(alasql, internal) { var yy = alasql.yy; -/* + /* // // JSON for Alasql.js // Date: 19.11.2014 @@ -8,77 +8,77 @@ export default function install(alasql, internal) { // */ -yy.Json = function (params) { - return Object.assign(this, params); -}; -yy.Json.prototype.toString = function () { - var s = ''; // '@' - s += JSONtoString(this.value); - s += ''; - return s; -}; + yy.Json = function (params) { + return Object.assign(this, params); + }; + yy.Json.prototype.toString = function () { + var s = ''; // '@' + s += JSONtoString(this.value); + s += ''; + return s; + }; -const JSONtoString = (alasql.utils.JSONtoString = function (obj) { - if (typeof obj === 'string') return `"${obj}"`; - if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj); - if (typeof obj === 'bigint') return `${obj.toString()}n`; + const JSONtoString = (alasql.utils.JSONtoString = function (obj) { + if (typeof obj === 'string') return `"${obj}"`; + if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj); + if (typeof obj === 'bigint') return `${obj.toString()}n`; - if (Array.isArray(obj)) { - return `[${obj.map(b => JSONtoString(b)).join(',')}]`; - } + if (Array.isArray(obj)) { + return `[${obj.map(b => JSONtoString(b)).join(',')}]`; + } - if (typeof obj === 'object') { - if (!obj.toJS || obj instanceof yy.Json) { - const ss = []; - for (const k in obj) { - const keyStr = typeof k === 'string' ? `"${k}"` : String(k); - const valueStr = JSONtoString(obj[k]); - ss.push(`${keyStr}:${valueStr}`); + if (typeof obj === 'object') { + if (!obj.toJS || obj instanceof yy.Json) { + const ss = []; + for (const k in obj) { + const keyStr = typeof k === 'string' ? `"${k}"` : String(k); + const valueStr = JSONtoString(obj[k]); + ss.push(`${keyStr}:${valueStr}`); + } + return `{${ss.join(',')}}`; + } else if (obj.toString) { + return obj.toString(); + } else { + throw new Error(`1: Cannot show JSON object ${JSON.stringify(obj)}`); } - return `{${ss.join(',')}}`; - } else if (obj.toString) { - return obj.toString(); } else { - throw new Error(`1: Cannot show JSON object ${JSON.stringify(obj)}`); + throw new Error(`2: Cannot show JSON object ${JSON.stringify(obj)}`); } - } else { - throw new Error(`2: Cannot show JSON object ${JSON.stringify(obj)}`); - } -}); + }); -function JSONtoJS(obj, context, tableid, defcols) { - var s = ''; - if (typeof obj == 'string') s = '"' + obj + '"'; - else if (typeof obj == 'number') s = '(' + obj + ')'; - else if (typeof obj == 'boolean') s = obj; - else if (typeof obj === 'bigint') s = obj.toString() + 'n'; - else if (typeof obj === 'object') { - if (Array.isArray(obj)) { - s += `[${obj.map(b => JSONtoJS(b, context, tableid, defcols)).join(',')}]`; - } else if (!obj.toJS || obj instanceof yy.Json) { - let ss = []; - for (const k in obj) { - let keyStr = typeof k === 'string' ? `"${k}"` : k.toString(); - let valueStr = JSONtoJS(obj[k], context, tableid, defcols); - ss.push(`${keyStr}:${valueStr}`); + function JSONtoJS(obj, context, tableid, defcols) { + var s = ''; + if (typeof obj == 'string') s = '"' + obj + '"'; + else if (typeof obj == 'number') s = '(' + obj + ')'; + else if (typeof obj == 'boolean') s = obj; + else if (typeof obj === 'bigint') s = obj.toString() + 'n'; + else if (typeof obj === 'object') { + if (Array.isArray(obj)) { + s += `[${obj.map(b => JSONtoJS(b, context, tableid, defcols)).join(',')}]`; + } else if (!obj.toJS || obj instanceof yy.Json) { + let ss = []; + for (const k in obj) { + let keyStr = typeof k === 'string' ? `"${k}"` : k.toString(); + let valueStr = JSONtoJS(obj[k], context, tableid, defcols); + ss.push(`${keyStr}:${valueStr}`); + } + s = `{${ss.join(',')}}`; + } else if (obj.toJS) { + s = obj.toJS(context, tableid, defcols); + } else { + throw new Error(`Cannot parse JSON object ${JSON.stringify(obj)}`); } - s = `{${ss.join(',')}}`; - } else if (obj.toJS) { - s = obj.toJS(context, tableid, defcols); } else { - throw new Error(`Cannot parse JSON object ${JSON.stringify(obj)}`); + throw new Error('2Can not parse JSON object ' + JSON.stringify(obj)); } - } else { - throw new Error('2Can not parse JSON object ' + JSON.stringify(obj)); - } - return s; -} + return s; + } -yy.Json.prototype.toJS = function (context, tableid, defcols) { - // TODO redo - return JSONtoJS(this.value, context, tableid, defcols); -}; + yy.Json.prototype.toJS = function (context, tableid, defcols) { + // TODO redo + return JSONtoJS(this.value, context, tableid, defcols); + }; internal.JSONtoJS = JSONtoJS; diff --git a/src/formats/from.js b/src/formats/from.js index dff886d219..665a2ecb46 100755 --- a/src/formats/from.js +++ b/src/formats/from.js @@ -1,6 +1,6 @@ export default function install(alasql, internal) { var getXLSX = internal.getXLSX; -/* + /* // // FROM functions Alasql.js // Date: 11.12.2014 @@ -8,385 +8,385 @@ export default function install(alasql, internal) { // */ -/** + /** Meteor */ -/* global alasql Tabletop document Event */ + /* global alasql Tabletop document Event */ -alasql.from.METEOR = function (filename, opts, cb, idx, query) { - var res = filename.find(opts).fetch(); - if (cb) res = cb(res, idx, query); + alasql.from.METEOR = function (filename, opts, cb, idx, query) { + var res = filename.find(opts).fetch(); + if (cb) res = cb(res, idx, query); - return res; -}; + return res; + }; -/** + /** Google Spreadsheet reader */ -alasql.from.TABLETOP = function (key, opts, cb, idx, query) { - var res = []; - - var opt = {headers: true, simpleSheet: true, key: key}; - alasql.utils.extend(opt, opts); - opt.callback = function (data) { - res = data; - if (cb) res = cb(res, idx, query); + alasql.from.TABLETOP = function (key, opts, cb, idx, query) { + var res = []; + + var opt = {headers: true, simpleSheet: true, key: key}; + alasql.utils.extend(opt, opts); + opt.callback = function (data) { + res = data; + if (cb) res = cb(res, idx, query); + }; + + Tabletop.init(opt); + return null; }; - Tabletop.init(opt); - return null; -}; - -alasql.from.HTML = function (selector, opts, cb, idx, query) { - var opt = {}; - alasql.utils.extend(opt, opts); + alasql.from.HTML = function (selector, opts, cb, idx, query) { + var opt = {}; + alasql.utils.extend(opt, opts); - var sel = document.querySelector(selector); - if (!sel || sel.tagName !== 'TABLE') { - throw new Error('Selected HTML element is not a TABLE'); - } + var sel = document.querySelector(selector); + if (!sel || sel.tagName !== 'TABLE') { + throw new Error('Selected HTML element is not a TABLE'); + } - var res = []; - var headers = opt.headers; + var res = []; + var headers = opt.headers; - if (headers && !Array.isArray(headers)) { - headers = []; - var ths = sel.querySelector('thead tr').children; - for (var i = 0; i < ths.length; i++) { - if (!(ths.item(i).style && ths.item(i).style.display === 'none' && opt.skipdisplaynone)) { - headers.push(ths.item(i).textContent); - } else { - headers.push(undefined); + if (headers && !Array.isArray(headers)) { + headers = []; + var ths = sel.querySelector('thead tr').children; + for (var i = 0; i < ths.length; i++) { + if (!(ths.item(i).style && ths.item(i).style.display === 'none' && opt.skipdisplaynone)) { + headers.push(ths.item(i).textContent); + } else { + headers.push(undefined); + } } } - } - // console.log(headers); + // console.log(headers); - var trs = sel.querySelectorAll('tbody tr'); + var trs = sel.querySelectorAll('tbody tr'); - for (var j = 0; j < trs.length; j++) { - var tds = trs.item(j).children; - var r = {}; - for (i = 0; i < tds.length; i++) { - if (!(tds.item(i).style && tds.item(i).style.display === 'none' && opt.skipdisplaynone)) { - if (headers) { - r[headers[i]] = tds.item(i).textContent; - } else { - r[i] = tds.item(i).textContent; - // console.log(r); + for (var j = 0; j < trs.length; j++) { + var tds = trs.item(j).children; + var r = {}; + for (i = 0; i < tds.length; i++) { + if (!(tds.item(i).style && tds.item(i).style.display === 'none' && opt.skipdisplaynone)) { + if (headers) { + r[headers[i]] = tds.item(i).textContent; + } else { + r[i] = tds.item(i).textContent; + // console.log(r); + } } } + res.push(r); } - res.push(r); - } - //console.log(res); - if (cb) { - res = cb(res, idx, query); - } - return res; -}; - -alasql.from.RANGE = function (start, finish, cb, idx, query) { - var res = []; - for (var i = start; i <= finish; i++) { - res.push(i); - } - // res = new alasql.Recordset({data:res,columns:{columnid:'_'}}); - if (cb) { - res = cb(res, idx, query); - } - return res; -}; + //console.log(res); + if (cb) { + res = cb(res, idx, query); + } + return res; + }; -/** - * UNNEST function - converts an array into a table for use in FROM clauses - * - * This function enables flattening of nested arrays when used with CROSS APPLY or OUTER APPLY. - * - * @param {Array} arr - The array to unnest - * @param {Object} opts - Options (reserved for future use) - * @param {Function} cb - Callback function - * @param {number} idx - Index - * @param {Object} query - Query object - * @returns {Array} The input array, or empty array if input is not an array - * - * @example - * // Flatten nested arrays - * SELECT b.name, e.id, e.value - * FROM data AS b - * CROSS APPLY (SELECT * FROM UNNEST(b.entries)) AS e - */ -alasql.from.UNNEST = function (arr, opts, cb, idx, query) { - var res = arr; - if (!Array.isArray(res)) { - res = []; - } - if (cb) { - res = cb(res, idx, query); - } - return res; -}; + alasql.from.RANGE = function (start, finish, cb, idx, query) { + var res = []; + for (var i = start; i <= finish; i++) { + res.push(i); + } + // res = new alasql.Recordset({data:res,columns:{columnid:'_'}}); + if (cb) { + res = cb(res, idx, query); + } + return res; + }; -// Read data from any file -alasql.from.FILE = function (filename, opts, cb, idx, query) { - var fname; - if (typeof filename === 'string') { - fname = filename; - } else if (filename instanceof Event) { - fname = filename.target.files[0].name; - } else { - throw new Error('Wrong usage of FILE() function'); - } + /** + * UNNEST function - converts an array into a table for use in FROM clauses + * + * This function enables flattening of nested arrays when used with CROSS APPLY or OUTER APPLY. + * + * @param {Array} arr - The array to unnest + * @param {Object} opts - Options (reserved for future use) + * @param {Function} cb - Callback function + * @param {number} idx - Index + * @param {Object} query - Query object + * @returns {Array} The input array, or empty array if input is not an array + * + * @example + * // Flatten nested arrays + * SELECT b.name, e.id, e.value + * FROM data AS b + * CROSS APPLY (SELECT * FROM UNNEST(b.entries)) AS e + */ + alasql.from.UNNEST = function (arr, opts, cb, idx, query) { + var res = arr; + if (!Array.isArray(res)) { + res = []; + } + if (cb) { + res = cb(res, idx, query); + } + return res; + }; - var parts = fname.split('.'); - var ext = parts[parts.length - 1].toUpperCase(); - if (alasql.from[ext]) { - return alasql.from[ext](filename, opts, cb, idx, query); - } else { - throw new Error('Cannot recognize file type for loading'); - } -}; + // Read data from any file + alasql.from.FILE = function (filename, opts, cb, idx, query) { + var fname; + if (typeof filename === 'string') { + fname = filename; + } else if (filename instanceof Event) { + fname = filename.target.files[0].name; + } else { + throw new Error('Wrong usage of FILE() function'); + } -// Read JSON file - -alasql.from.JSON = function (filename, opts, cb, idx, query) { - var res; - //console.log('cb',cb); - //console.log('JSON'); - filename = alasql.utils.autoExtFilename(filename, 'json', opts); - const errorHandler = err => { - const error = err instanceof Error ? err : new Error(err); - if (query && query.cb) { - query.cb(null, error); - return; + var parts = fname.split('.'); + var ext = parts[parts.length - 1].toUpperCase(); + if (alasql.from[ext]) { + return alasql.from[ext](filename, opts, cb, idx, query); + } else { + throw new Error('Cannot recognize file type for loading'); } - throw error; }; - alasql.utils.loadFile( - filename, - !!cb, - function (data) { - // console.log('DATA:'+data); - // res = [{a:1}]; - try { - res = JSON.parse(data); - } catch (error) { - errorHandler(error); + + // Read JSON file + + alasql.from.JSON = function (filename, opts, cb, idx, query) { + var res; + //console.log('cb',cb); + //console.log('JSON'); + filename = alasql.utils.autoExtFilename(filename, 'json', opts); + const errorHandler = err => { + const error = err instanceof Error ? err : new Error(err); + if (query && query.cb) { + query.cb(null, error); return; } - if (cb) { - res = cb(res, idx, query); - } - }, - errorHandler - ); - return res; -}; - -const jsonl = ext => { - return function (filename, opts, cb, idx, query) { - let out = []; - filename = alasql.utils.autoExtFilename(filename, ext, opts); + throw error; + }; alasql.utils.loadFile( filename, !!cb, function (data) { - data.split(/\r?\n/).forEach((line, ix) => { - const trimmed = line.trim(); - if (trimmed !== '') { - // skip empty lines, we do not use filter on an input, as we want to preserve line numbers - try { - out.push(JSON.parse(trimmed)); - } catch (e) { - throw new Error(`Could not parse JSON at line ${ix}: ${e.toString()}`); - } - } - }); + // console.log('DATA:'+data); + // res = [{a:1}]; + try { + res = JSON.parse(data); + } catch (error) { + errorHandler(error); + return; + } if (cb) { - out = cb(out, idx, query); + res = cb(res, idx, query); } }, - err => { - const error = err instanceof Error ? err : new Error(err); - if (query && query.cb) { - query.cb(null, error); - return; - } - throw error; - } + errorHandler ); - return out; + return res; + }; + + const jsonl = ext => { + return function (filename, opts, cb, idx, query) { + let out = []; + filename = alasql.utils.autoExtFilename(filename, ext, opts); + alasql.utils.loadFile( + filename, + !!cb, + function (data) { + data.split(/\r?\n/).forEach((line, ix) => { + const trimmed = line.trim(); + if (trimmed !== '') { + // skip empty lines, we do not use filter on an input, as we want to preserve line numbers + try { + out.push(JSON.parse(trimmed)); + } catch (e) { + throw new Error(`Could not parse JSON at line ${ix}: ${e.toString()}`); + } + } + }); + if (cb) { + out = cb(out, idx, query); + } + }, + err => { + const error = err instanceof Error ? err : new Error(err); + if (query && query.cb) { + query.cb(null, error); + return; + } + throw error; + } + ); + return out; + }; }; -}; -alasql.from.JSONL = jsonl('jsonl'); -alasql.from.NDJSON = jsonl('ndjson'); + alasql.from.JSONL = jsonl('jsonl'); + alasql.from.NDJSON = jsonl('ndjson'); -alasql.from.TXT = function (filename, opts, cb, idx, query) { - var res; - filename = alasql.utils.autoExtFilename(filename, 'txt', opts); - alasql.utils.loadFile(filename, !!cb, function (data) { - res = data.split(/\r?\n/); + alasql.from.TXT = function (filename, opts, cb, idx, query) { + var res; + filename = alasql.utils.autoExtFilename(filename, 'txt', opts); + alasql.utils.loadFile(filename, !!cb, function (data) { + res = data.split(/\r?\n/); - // Remove last line if empty - if (res[res.length - 1] === '') { - res.pop(); - } - for (var i = 0, ilen = res.length; i < ilen; i++) { - // Please avoid '===' here - if (res[i] == +res[i]) { - // eslint:ignore - // jshint ignore:line - res[i] = +res[i]; + // Remove last line if empty + if (res[res.length - 1] === '') { + res.pop(); } - res[i] = [res[i]]; - } - if (cb) { - res = cb(res, idx, query); - } - }); - return res; -}; - -alasql.from.TAB = alasql.from.TSV = function (filename, opts, cb, idx, query) { - opts = opts || {}; - opts.separator = '\t'; - filename = alasql.utils.autoExtFilename(filename, 'tab', opts); - opts.autoext = false; - return alasql.from.CSV(filename, opts, cb, idx, query); -}; + for (var i = 0, ilen = res.length; i < ilen; i++) { + // Please avoid '===' here + if (res[i] == +res[i]) { + // eslint:ignore + // jshint ignore:line + res[i] = +res[i]; + } + res[i] = [res[i]]; + } + if (cb) { + res = cb(res, idx, query); + } + }); + return res; + }; -alasql.from.CSV = function (contents, opts, cb, idx, query) { - contents = '' + contents; - var opt = { - separator: ',', - quote: '"', - headers: true, - raw: false, + alasql.from.TAB = alasql.from.TSV = function (filename, opts, cb, idx, query) { + opts = opts || {}; + opts.separator = '\t'; + filename = alasql.utils.autoExtFilename(filename, 'tab', opts); + opts.autoext = false; + return alasql.from.CSV(filename, opts, cb, idx, query); }; - alasql.utils.extend(opt, opts); - var res; - var hs = []; - // Determine once whether to auto-convert: not raw mode, not SELECT INTO, and csvStringToNumber option is set - const shouldAutoConvert = !opt.raw && !query?.intofns && alasql.options.csvStringToNumber; - - function potentialAutoConvert(val) { - if (shouldAutoConvert && val !== undefined && val.length !== 0 && val == +val) { - return +val; - } - return val; - } - function parseText(text) { - var delimiterCode = opt.separator.charCodeAt(0); - var quoteCode = opt.quote.charCodeAt(0); - - var EOL = {}, - EOF = {}, - rows = [], - N = text.length, - I = 0, - n = 0, - t, - eol; - function token() { - if (I >= N) { - return EOF; - } - if (eol) { - return ((eol = false), EOL); + alasql.from.CSV = function (contents, opts, cb, idx, query) { + contents = '' + contents; + var opt = { + separator: ',', + quote: '"', + headers: true, + raw: false, + }; + alasql.utils.extend(opt, opts); + var res; + var hs = []; + // Determine once whether to auto-convert: not raw mode, not SELECT INTO, and csvStringToNumber option is set + const shouldAutoConvert = !opt.raw && !query?.intofns && alasql.options.csvStringToNumber; + + function potentialAutoConvert(val) { + if (shouldAutoConvert && val !== undefined && val.length !== 0 && val == +val) { + return +val; } - var j = I; - if (text.charCodeAt(j) === quoteCode) { - var i = j; - while (i++ < N) { - if (text.charCodeAt(i) === quoteCode) { - if (text.charCodeAt(i + 1) !== quoteCode) { - break; + return val; + } + + function parseText(text) { + var delimiterCode = opt.separator.charCodeAt(0); + var quoteCode = opt.quote.charCodeAt(0); + + var EOL = {}, + EOF = {}, + rows = [], + N = text.length, + I = 0, + n = 0, + t, + eol; + function token() { + if (I >= N) { + return EOF; + } + if (eol) { + return ((eol = false), EOL); + } + var j = I; + if (text.charCodeAt(j) === quoteCode) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === quoteCode) { + if (text.charCodeAt(i + 1) !== quoteCode) { + break; + } + ++i; } - ++i; } - } - I = i + 2; - var c = text.charCodeAt(i + 1); - if (c === 13) { - eol = true; - if (text.charCodeAt(i + 2) === 10) { - ++I; + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) { + ++I; + } + } else if (c === 10) { + eol = true; } - } else if (c === 10) { - eol = true; + return text.substring(j + 1, i).replace(/""/g, '"'); } - return text.substring(j + 1, i).replace(/""/g, '"'); - } - while (I < N) { - var c = text.charCodeAt(I++), - k = 1; - if (c === 10) { - eol = true; - } else if (c === 13) { - eol = true; - if (text.charCodeAt(I) === 10) { - ++I; - ++k; + while (I < N) { + var c = text.charCodeAt(I++), + k = 1; + if (c === 10) { + eol = true; + } else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) { + ++I; + ++k; + } + } else if (c !== delimiterCode) { + continue; } - } else if (c !== delimiterCode) { - continue; + return text.substring(j, I - k); } - return text.substring(j, I - k); + return text.substring(j); } - return text.substring(j); - } - while ((t = token()) !== EOF) { - var a = []; - while (t !== EOL && t !== EOF) { - a.push(t.trim()); - t = token(); - } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t.trim()); + t = token(); + } - if (opt.headers) { - if (n === 0) { - if (typeof opt.headers === 'boolean') { - hs = a; - } else if (Array.isArray(opt.headers)) { - hs = opt.headers; + if (opt.headers) { + if (n === 0) { + if (typeof opt.headers === 'boolean') { + hs = a; + } else if (Array.isArray(opt.headers)) { + hs = opt.headers; + var r = {}; + hs.forEach(function (h, idx) { + r[h] = potentialAutoConvert(a[idx]); + }); + rows.push(r); + } + } else { var r = {}; hs.forEach(function (h, idx) { r[h] = potentialAutoConvert(a[idx]); }); rows.push(r); } + n++; } else { var r = {}; - hs.forEach(function (h, idx) { - r[h] = potentialAutoConvert(a[idx]); + a.forEach(function (v, idx) { + r[idx] = potentialAutoConvert(a[idx]); }); rows.push(r); } - n++; - } else { - var r = {}; - a.forEach(function (v, idx) { - r[idx] = potentialAutoConvert(a[idx]); - }); - rows.push(r); } - } - res = rows; + res = rows; - if (opt.headers) { - if (query && query.sources && query.sources[idx]) { - var columns = (query.sources[idx].columns = []); - hs.forEach(function (h) { - columns.push({columnid: h}); - }); + if (opt.headers) { + if (query && query.sources && query.sources[idx]) { + var columns = (query.sources[idx].columns = []); + hs.forEach(function (h) { + columns.push({columnid: h}); + }); + } } - } - /*/* + /*/* if(false) { res = data.split(/\r?\n/); if(opt.headers) { @@ -419,208 +419,208 @@ if(false) { }; */ - if (cb) { - res = cb(res, idx, query); + if (cb) { + res = cb(res, idx, query); + } } - } - if (new RegExp('\n').test(contents)) { - parseText(contents); - } else { - contents = alasql.utils.autoExtFilename(contents, 'csv', opts); - alasql.utils.loadFile(contents, !!cb, parseText, e => query.cb(null, e)); - } - return res; -}; - -function XLSXLSX(X, filename, opts, cb, idx, query) { - var opt = {}; - opts = opts || {}; - alasql.utils.extend(opt, opts); - if (typeof opt.headers === 'undefined') { - opt.headers = true; - } - var res; - - /** - * see https://github.com/SheetJS/js-xlsx/blob/5ae6b1965bfe3764656a96f536b356cd1586fec7/README.md - * for example of using readAsArrayBuffer under `Parsing Workbooks` - */ - function fixdata(data) { - var o = '', - l = 0, - w = 10240; - for (; l < data.byteLength / w; ++l) - o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))); - o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))); - return o; - } - function getHeaderText(text) { - // if casesensitive option is set to false and there is a text value return lowercase value of text - if (text && alasql.options.casesensitive === false) { - return text.toLowerCase(); + if (new RegExp('\n').test(contents)) { + parseText(contents); } else { - return text; + contents = alasql.utils.autoExtFilename(contents, 'csv', opts); + alasql.utils.loadFile(contents, !!cb, parseText, e => query.cb(null, e)); } - } + return res; + }; - function processSheet(workbook, sheetid, sheetOpt) { - var range; - var sheetRes = []; - if (typeof sheetOpt.range === 'undefined') { - range = workbook.Sheets[sheetid]['!ref']; - } else { - range = sheetOpt.range; - if (workbook.Sheets[sheetid][range]) { - range = workbook.Sheets[sheetid][range]; + function XLSXLSX(X, filename, opts, cb, idx, query) { + var opt = {}; + opts = opts || {}; + alasql.utils.extend(opt, opts); + if (typeof opt.headers === 'undefined') { + opt.headers = true; + } + var res; + + /** + * see https://github.com/SheetJS/js-xlsx/blob/5ae6b1965bfe3764656a96f536b356cd1586fec7/README.md + * for example of using readAsArrayBuffer under `Parsing Workbooks` + */ + function fixdata(data) { + var o = '', + l = 0, + w = 10240; + for (; l < data.byteLength / w; ++l) + o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))); + o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))); + return o; + } + function getHeaderText(text) { + // if casesensitive option is set to false and there is a text value return lowercase value of text + if (text && alasql.options.casesensitive === false) { + return text.toLowerCase(); + } else { + return text; } } - // if range has some value then data is present in the current sheet - // else current sheet is empty - if (range) { - var rg = range.split(':'); - var col0 = rg[0].match(/[A-Z]+/)[0]; - var row0 = +rg[0].match(/[0-9]+/)[0]; - var col1 = rg[1].match(/[A-Z]+/)[0]; - var row1 = +rg[1].match(/[0-9]+/)[0]; - - var hh = {}; - var xlscnCol0 = alasql.utils.xlscn(col0); - var xlscnCol1 = alasql.utils.xlscn(col1); - for (var j = xlscnCol0; j <= xlscnCol1; j++) { - var col = alasql.utils.xlsnc(j); - if (sheetOpt.headers) { - if (workbook.Sheets[sheetid][col + '' + row0]) { - hh[col] = getHeaderText(workbook.Sheets[sheetid][col + '' + row0].v); - } else { - hh[col] = getHeaderText(col); - } - } else { - hh[col] = col; + + function processSheet(workbook, sheetid, sheetOpt) { + var range; + var sheetRes = []; + if (typeof sheetOpt.range === 'undefined') { + range = workbook.Sheets[sheetid]['!ref']; + } else { + range = sheetOpt.range; + if (workbook.Sheets[sheetid][range]) { + range = workbook.Sheets[sheetid][range]; } } - if (sheetOpt.headers) { - row0++; - } - for (var i = row0; i <= row1; i++) { - var row = {}; + // if range has some value then data is present in the current sheet + // else current sheet is empty + if (range) { + var rg = range.split(':'); + var col0 = rg[0].match(/[A-Z]+/)[0]; + var row0 = +rg[0].match(/[0-9]+/)[0]; + var col1 = rg[1].match(/[A-Z]+/)[0]; + var row1 = +rg[1].match(/[0-9]+/)[0]; + + var hh = {}; + var xlscnCol0 = alasql.utils.xlscn(col0); + var xlscnCol1 = alasql.utils.xlscn(col1); for (var j = xlscnCol0; j <= xlscnCol1; j++) { var col = alasql.utils.xlsnc(j); - if (workbook.Sheets[sheetid][col + '' + i]) { - row[hh[col]] = workbook.Sheets[sheetid][col + '' + i].v; + if (sheetOpt.headers) { + if (workbook.Sheets[sheetid][col + '' + row0]) { + hh[col] = getHeaderText(workbook.Sheets[sheetid][col + '' + row0].v); + } else { + hh[col] = getHeaderText(col); + } + } else { + hh[col] = col; + } + } + if (sheetOpt.headers) { + row0++; + } + for (var i = row0; i <= row1; i++) { + var row = {}; + for (var j = xlscnCol0; j <= xlscnCol1; j++) { + var col = alasql.utils.xlsnc(j); + if (workbook.Sheets[sheetid][col + '' + i]) { + row[hh[col]] = workbook.Sheets[sheetid][col + '' + i].v; + } } + sheetRes.push(row); } - sheetRes.push(row); + } else { + sheetRes.push([]); } - } else { - sheetRes.push([]); - } - // Remove last empty line (issue #548) - if ( - sheetRes.length > 0 && - sheetRes[sheetRes.length - 1] && - Object.keys(sheetRes[sheetRes.length - 1]).length == 0 - ) { - sheetRes.pop(); - } + // Remove last empty line (issue #548) + if ( + sheetRes.length > 0 && + sheetRes[sheetRes.length - 1] && + Object.keys(sheetRes[sheetRes.length - 1]).length == 0 + ) { + sheetRes.pop(); + } - return sheetRes; - } + return sheetRes; + } - filename = alasql.utils.autoExtFilename(filename, 'xls', opts); - alasql.utils.loadBinaryFile( - filename, - !!cb, - function (data) { - // function processData(data) { - if (data instanceof ArrayBuffer) { - var arr = fixdata(data); - var workbook = X.read(btoa(arr), { - type: 'base64', - ...alasql.options.excel, - ...opts, - }); - } else { - var workbook = X.read(data, { - type: 'binary', - ...alasql.options.excel, - ...opts, - }); - } + filename = alasql.utils.autoExtFilename(filename, 'xls', opts); + alasql.utils.loadBinaryFile( + filename, + !!cb, + function (data) { + // function processData(data) { + if (data instanceof ArrayBuffer) { + var arr = fixdata(data); + var workbook = X.read(btoa(arr), { + type: 'base64', + ...alasql.options.excel, + ...opts, + }); + } else { + var workbook = X.read(data, { + type: 'binary', + ...alasql.options.excel, + ...opts, + }); + } - // Check if we should process multiple sheets - var shouldProcessMultipleSheets = - opt.sheetid === '*' || (Array.isArray(opt.sheetid) && opt.sheetid.length > 0); - - if (shouldProcessMultipleSheets) { - // Process multiple sheets and combine into a single array - res = []; - var sheetsToProcess = opt.sheetid === '*' ? workbook.SheetNames : opt.sheetid; - - for (var s = 0; s < sheetsToProcess.length; s++) { - var currentSheetId = - opt.sheetid === '*' - ? sheetsToProcess[s] - : typeof sheetsToProcess[s] === 'number' - ? workbook.SheetNames[sheetsToProcess[s]] - : sheetsToProcess[s]; - - if (workbook.Sheets[currentSheetId]) { - var sheetData = processSheet(workbook, currentSheetId, opt); - // Add sheet name to each row - for (var r = 0; r < sheetData.length; r++) { - sheetData[r]._sheet = currentSheetId; + // Check if we should process multiple sheets + var shouldProcessMultipleSheets = + opt.sheetid === '*' || (Array.isArray(opt.sheetid) && opt.sheetid.length > 0); + + if (shouldProcessMultipleSheets) { + // Process multiple sheets and combine into a single array + res = []; + var sheetsToProcess = opt.sheetid === '*' ? workbook.SheetNames : opt.sheetid; + + for (var s = 0; s < sheetsToProcess.length; s++) { + var currentSheetId = + opt.sheetid === '*' + ? sheetsToProcess[s] + : typeof sheetsToProcess[s] === 'number' + ? workbook.SheetNames[sheetsToProcess[s]] + : sheetsToProcess[s]; + + if (workbook.Sheets[currentSheetId]) { + var sheetData = processSheet(workbook, currentSheetId, opt); + // Add sheet name to each row + for (var r = 0; r < sheetData.length; r++) { + sheetData[r]._sheet = currentSheetId; + } + res = res.concat(sheetData); } - res = res.concat(sheetData); } - } - } else { - // Process single sheet (original behavior) - var sheetid; - if (typeof opt.sheetid === 'undefined') { - sheetid = workbook.SheetNames[0]; - } else if (typeof opt.sheetid === 'number') { - sheetid = workbook.SheetNames[opt.sheetid]; } else { - sheetid = opt.sheetid; + // Process single sheet (original behavior) + var sheetid; + if (typeof opt.sheetid === 'undefined') { + sheetid = workbook.SheetNames[0]; + } else if (typeof opt.sheetid === 'number') { + sheetid = workbook.SheetNames[opt.sheetid]; + } else { + sheetid = opt.sheetid; + } + res = processSheet(workbook, sheetid, opt); } - res = processSheet(workbook, sheetid, opt); - } - if (cb) { - res = cb(res, idx, query); - } - }, - function (err) { - if (query && query.cb) { - query.cb(null, err); - return; + if (cb) { + res = cb(res, idx, query); + } + }, + function (err) { + if (query && query.cb) { + query.cb(null, err); + return; + } + throw err; } - throw err; - } - ); + ); - return res; -} + return res; + } -alasql.from.XLS = function (filename, opts, cb, idx, query) { - opts = opts || {}; - filename = alasql.utils.autoExtFilename(filename, 'xls', opts); - opts.autoExt = false; - return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); -}; + alasql.from.XLS = function (filename, opts, cb, idx, query) { + opts = opts || {}; + filename = alasql.utils.autoExtFilename(filename, 'xls', opts); + opts.autoExt = false; + return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); + }; -alasql.from.XLSX = function (filename, opts, cb, idx, query) { - opts = opts || {}; - filename = alasql.utils.autoExtFilename(filename, 'xlsx', opts); - opts.autoExt = false; - return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); -}; + alasql.from.XLSX = function (filename, opts, cb, idx, query) { + opts = opts || {}; + filename = alasql.utils.autoExtFilename(filename, 'xlsx', opts); + opts.autoExt = false; + return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); + }; -alasql.from.ODS = function (filename, opts, cb, idx, query) { - opts = opts || {}; - filename = alasql.utils.autoExtFilename(filename, 'ods', opts); - opts.autoExt = false; - return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); -}; + alasql.from.ODS = function (filename, opts, cb, idx, query) { + opts = opts || {}; + filename = alasql.utils.autoExtFilename(filename, 'ods', opts); + opts.autoExt = false; + return XLSXLSX(getXLSX(), filename, opts, cb, idx, query); + }; } diff --git a/src/formats/gexf.js b/src/formats/gexf.js index 47ff91505d..88faf655cd 100755 --- a/src/formats/gexf.js +++ b/src/formats/gexf.js @@ -1,11 +1,11 @@ export default function install(alasql) { -alasql.from.GEXF = function (filename, opts, cb, idx, query) { - var res; - alasql('SEARCH FROM XML(' + filename + ')', [], function (data) { - res = data; - // console.log(res); - if (cb) res = cb(res); - }); - return res; -}; + alasql.from.GEXF = function (filename, opts, cb, idx, query) { + var res; + alasql('SEARCH FROM XML(' + filename + ')', [], function (data) { + res = data; + // console.log(res); + if (cb) res = cb(res); + }); + return res; + }; } diff --git a/src/formats/into.js b/src/formats/into.js index 0c0ad5466a..cb6fe1cff3 100755 --- a/src/formats/into.js +++ b/src/formats/into.js @@ -1,91 +1,25 @@ import {escapeqq} from '../utils/helpers.js'; export default function install(alasql) { -// -// into functions -// -// (c) 2014 Andrey Gershun -// + // + // into functions + // + // (c) 2014 Andrey Gershun + // -alasql.into.SQL = function (filename, opts, data, columns, cb) { - var res; - if (typeof filename === 'object') { - opts = filename; - filename = undefined; - } - var opt = {}; - alasql.utils.extend(opt, opts); - if (typeof opt.tableid === 'undefined') { - throw new Error('Table for INSERT TO is not defined.'); - } - - var s = ''; - if (columns.length === 0) { - if (typeof data[0] === 'object') { - columns = Object.keys(data[0]).map(function (columnid) { - return {columnid: columnid}; - }); - } else { - // What should I do? - // columns = [{columnid:"_"}]; + alasql.into.SQL = function (filename, opts, data, columns, cb) { + var res; + if (typeof filename === 'object') { + opts = filename; + filename = undefined; } - } - - for (var i = 0, ilen = data.length; i < ilen; i++) { - s += 'INSERT INTO ' + opts.tableid + '('; - s += columns - .map(function (col) { - return col.columnid; - }) - .join(','); - s += ') VALUES ('; - s += columns - .map(function (col) { - var val = data[i][col.columnid]; - // Handle null and undefined values - if (val === null || val === undefined) { - return 'NULL'; - } - // Check if value should be escaped as a string - var shouldEscape = - (col.typeid && - (col.typeid === 'STRING' || - col.typeid === 'VARCHAR' || - col.typeid === 'NVARCHAR' || - col.typeid === 'CHAR' || - col.typeid === 'NCHAR')) || - typeof val == 'string'; - if (shouldEscape) { - val = "'" + escapeqq(val) + "'"; - } - return val; - }) - .join(','); - s += ');\n'; - } - // if(filename === '') { - // res = s; - // } else { - // res = data.length; - filename = alasql.utils.autoExtFilename(filename, 'sql', opts); - res = alasql.utils.saveFile(filename, s); - if (cb) { - res = cb(res); - } - return res; -}; - -alasql.into.HTML = function (selector, opts, data, columns, cb) { - var res = 1; - if (typeof document !== 'object') { - var opt = {headers: true}; + var opt = {}; alasql.utils.extend(opt, opts); - - var sel = document.querySelector(selector); - if (!sel) { - throw new Error('Selected HTML element is not found'); + if (typeof opt.tableid === 'undefined') { + throw new Error('Table for INSERT TO is not defined.'); } + var s = ''; if (columns.length === 0) { if (typeof data[0] === 'object') { columns = Object.keys(data[0]).map(function (columnid) { @@ -97,191 +31,257 @@ alasql.into.HTML = function (selector, opts, data, columns, cb) { } } - var tbe = document.createElement('table'); - var thead = document.createElement('thead'); - tbe.appendChild(thead); - if (opt.headers) { - var tre = document.createElement('tr'); - for (var i = 0; i < columns.length; i++) { - var the = document.createElement('th'); - the.textContent = columns[i].columnid; - tre.appendChild(the); - } - thead.appendChild(tre); + for (var i = 0, ilen = data.length; i < ilen; i++) { + s += 'INSERT INTO ' + opts.tableid + '('; + s += columns + .map(function (col) { + return col.columnid; + }) + .join(','); + s += ') VALUES ('; + s += columns + .map(function (col) { + var val = data[i][col.columnid]; + // Handle null and undefined values + if (val === null || val === undefined) { + return 'NULL'; + } + // Check if value should be escaped as a string + var shouldEscape = + (col.typeid && + (col.typeid === 'STRING' || + col.typeid === 'VARCHAR' || + col.typeid === 'NVARCHAR' || + col.typeid === 'CHAR' || + col.typeid === 'NCHAR')) || + typeof val == 'string'; + if (shouldEscape) { + val = "'" + escapeqq(val) + "'"; + } + return val; + }) + .join(','); + s += ');\n'; + } + // if(filename === '') { + // res = s; + // } else { + // res = data.length; + filename = alasql.utils.autoExtFilename(filename, 'sql', opts); + res = alasql.utils.saveFile(filename, s); + if (cb) { + res = cb(res); } + return res; + }; - var tbody = document.createElement('tbody'); - tbe.appendChild(tbody); - for (var j = 0; j < data.length; j++) { - var tre = document.createElement('tr'); - for (var i = 0; i < columns.length; i++) { - var the = document.createElement('td'); - the.textContent = data[j][columns[i].columnid]; - tre.appendChild(the); + alasql.into.HTML = function (selector, opts, data, columns, cb) { + var res = 1; + if (typeof document !== 'object') { + var opt = {headers: true}; + alasql.utils.extend(opt, opts); + + var sel = document.querySelector(selector); + if (!sel) { + throw new Error('Selected HTML element is not found'); } - tbody.appendChild(tre); - } - alasql.utils.domEmptyChildren(sel); - // console.log(tbe,columns); - sel.appendChild(tbe); - } - if (cb) { - res = cb(res); - } - return res; -}; -alasql.into.JSON = function (filename, opts, data, columns, cb) { - var res = 1; - if (typeof filename === 'object') { - opts = filename; - filename = undefined; - } - var s = JSON.stringify(data); + if (columns.length === 0) { + if (typeof data[0] === 'object') { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } else { + // What should I do? + // columns = [{columnid:"_"}]; + } + } - filename = alasql.utils.autoExtFilename(filename, 'json', opts); - res = alasql.utils.saveFile(filename, s); - if (cb) { - res = cb(res); - } - return res; -}; + var tbe = document.createElement('table'); + var thead = document.createElement('thead'); + tbe.appendChild(thead); + if (opt.headers) { + var tre = document.createElement('tr'); + for (var i = 0; i < columns.length; i++) { + var the = document.createElement('th'); + the.textContent = columns[i].columnid; + tre.appendChild(the); + } + thead.appendChild(tre); + } -alasql.into.OBJECT = function (filename, opts, data, columns, cb) { - var res; - if (typeof filename === 'object') { - opts = filename; - filename = undefined; - } + var tbody = document.createElement('tbody'); + tbe.appendChild(tbody); + for (var j = 0; j < data.length; j++) { + var tre = document.createElement('tr'); + for (var i = 0; i < columns.length; i++) { + var the = document.createElement('td'); + the.textContent = data[j][columns[i].columnid]; + tre.appendChild(the); + } + tbody.appendChild(tre); + } + alasql.utils.domEmptyChildren(sel); + // console.log(tbe,columns); + sel.appendChild(tbe); + } + if (cb) { + res = cb(res); + } + return res; + }; - // Data is already in nested object format from core compilation - // If filename is provided, save to file like JSON - if (filename) { + alasql.into.JSON = function (filename, opts, data, columns, cb) { + var res = 1; + if (typeof filename === 'object') { + opts = filename; + filename = undefined; + } var s = JSON.stringify(data); + filename = alasql.utils.autoExtFilename(filename, 'json', opts); res = alasql.utils.saveFile(filename, s); - } else { - // Return the data directly - res = data; - } + if (cb) { + res = cb(res); + } + return res; + }; + + alasql.into.OBJECT = function (filename, opts, data, columns, cb) { + var res; + if (typeof filename === 'object') { + opts = filename; + filename = undefined; + } - if (cb) { - res = cb(res); - } - return res; -}; + // Data is already in nested object format from core compilation + // If filename is provided, save to file like JSON + if (filename) { + var s = JSON.stringify(data); + filename = alasql.utils.autoExtFilename(filename, 'json', opts); + res = alasql.utils.saveFile(filename, s); + } else { + // Return the data directly + res = data; + } -alasql.into.TXT = function (filename, opts, data, columns, cb) { - // If columns is empty - if (columns.length === 0 && data.length > 0) { - columns = Object.keys(data[0]).map(function (columnid) { - return {columnid: columnid}; - }); - } - // If one parameter - if (typeof filename === 'object') { - opts = filename; - filename = undefined; - } + if (cb) { + res = cb(res); + } + return res; + }; - var res = data.length; - var s = ''; - if (data.length > 0) { - var key = columns[0].columnid; - s += data - .map(function (d) { - return d[key]; - }) - .join('\n'); - } + alasql.into.TXT = function (filename, opts, data, columns, cb) { + // If columns is empty + if (columns.length === 0 && data.length > 0) { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } + // If one parameter + if (typeof filename === 'object') { + opts = filename; + filename = undefined; + } - // } else { - // if(utils.isNode) { - // process.stdout.write(s); - // } else { - // console.log(s); - // }; - // } - filename = alasql.utils.autoExtFilename(filename, 'txt', opts); - res = alasql.utils.saveFile(filename, s); - if (cb) { - res = cb(res); - } - return res; -}; + var res = data.length; + var s = ''; + if (data.length > 0) { + var key = columns[0].columnid; + s += data + .map(function (d) { + return d[key]; + }) + .join('\n'); + } -alasql.into.TAB = alasql.into.TSV = function (filename, opts, data, columns, cb) { - var opt = {}; - alasql.utils.extend(opt, opts); - opt.separator = '\t'; - filename = alasql.utils.autoExtFilename(filename, 'tab', opts); - opt.autoExt = false; - return alasql.into.CSV(filename, opt, data, columns, cb); -}; + // } else { + // if(utils.isNode) { + // process.stdout.write(s); + // } else { + // console.log(s); + // }; + // } + filename = alasql.utils.autoExtFilename(filename, 'txt', opts); + res = alasql.utils.saveFile(filename, s); + if (cb) { + res = cb(res); + } + return res; + }; -alasql.into.CSV = function (filename, opts, data, columns, cb) { - if (columns.length === 0 && data.length > 0) { - columns = Object.keys(data[0]).map(function (columnid) { - return {columnid: columnid}; - }); - } - if (typeof filename === 'object') { - opts = filename; - filename = undefined; - } + alasql.into.TAB = alasql.into.TSV = function (filename, opts, data, columns, cb) { + var opt = {}; + alasql.utils.extend(opt, opts); + opt.separator = '\t'; + filename = alasql.utils.autoExtFilename(filename, 'tab', opts); + opt.autoExt = false; + return alasql.into.CSV(filename, opt, data, columns, cb); + }; - var opt = {headers: true}; - //opt.separator = ','; - opt.separator = ';'; - opt.quote = '"'; + alasql.into.CSV = function (filename, opts, data, columns, cb) { + if (columns.length === 0 && data.length > 0) { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } + if (typeof filename === 'object') { + opts = filename; + filename = undefined; + } - opt.utf8Bom = true; - if (opts && !opts.headers && typeof opts.headers !== 'undefined') { - opt.utf8Bom = false; - } + var opt = {headers: true}; + //opt.separator = ','; + opt.separator = ';'; + opt.quote = '"'; - alasql.utils.extend(opt, opts); - var res = data.length; - var s = opt.utf8Bom ? '\ufeff' : ''; - if (opt.headers) { - s += - opt.quote + - columns - .map(function (col) { - return col.columnid.trim(); - }) - .join(opt.quote + opt.separator + opt.quote) + - opt.quote + - '\r\n'; - } + opt.utf8Bom = true; + if (opts && !opts.headers && typeof opts.headers !== 'undefined') { + opt.utf8Bom = false; + } - data.forEach(function (d) { - s += - columns - .map(function (col) { - var s = d[col.columnid]; - // escape the character wherever it appears in the field - if (opt.quote !== '') { - s = (s + '').replace(new RegExp('\\' + opt.quote, 'g'), opt.quote + opt.quote); - } - // if((s+"").indexOf(opt.separator) > -1 || (s+"").indexOf(opt.quote) > -1) s = opt.quote + s + opt.quote; + alasql.utils.extend(opt, opts); + var res = data.length; + var s = opt.utf8Bom ? '\ufeff' : ''; + if (opt.headers) { + s += + opt.quote + + columns + .map(function (col) { + return col.columnid.trim(); + }) + .join(opt.quote + opt.separator + opt.quote) + + opt.quote + + '\r\n'; + } - //Excel 2013 needs quotes around strings - thanks for _not_ complying with RFC for CSV - if (+s != s) { - // jshint ignore:line - s = opt.quote + s + opt.quote; - } + data.forEach(function (d) { + s += + columns + .map(function (col) { + var s = d[col.columnid]; + // escape the character wherever it appears in the field + if (opt.quote !== '') { + s = (s + '').replace(new RegExp('\\' + opt.quote, 'g'), opt.quote + opt.quote); + } + // if((s+"").indexOf(opt.separator) > -1 || (s+"").indexOf(opt.quote) > -1) s = opt.quote + s + opt.quote; - return s; - }) - .join(opt.separator) + '\r\n'; - }); + //Excel 2013 needs quotes around strings - thanks for _not_ complying with RFC for CSV + if (+s != s) { + // jshint ignore:line + s = opt.quote + s + opt.quote; + } + + return s; + }) + .join(opt.separator) + '\r\n'; + }); - filename = alasql.utils.autoExtFilename(filename, 'csv', opts); - res = alasql.utils.saveFile(filename, s, null, {disableAutoBom: true}); - if (cb) { - res = cb(res); - } - return res; -}; + filename = alasql.utils.autoExtFilename(filename, 'csv', opts); + res = alasql.utils.saveFile(filename, s, null, {disableAutoBom: true}); + if (cb) { + res = cb(res); + } + return res; + }; } diff --git a/src/formats/xls.js b/src/formats/xls.js index 6d32bc252a..9d6623c696 100755 --- a/src/formats/xls.js +++ b/src/formats/xls.js @@ -1,328 +1,328 @@ import {extend} from '../utils/helpers.js'; export default function install(alasql) { -// -// 831xl.js - Coloring Excel -// 18.04.2015 -// Generate XLS file with colors and styles -// with Excel - -alasql.into.XLS = function (filename, opts, data, columns, cb) { - // If filename is not defined then output to the result - if (typeof filename == 'object') { - opts = filename; - filename = undefined; - } - - // Set sheets - var sheets = {}; - if (opts && opts.sheets) { - sheets = opts.sheets; - } - - // Default sheet - var sheet = {headers: true}; - if (typeof sheets['Sheet1'] != 'undefined') { - sheet = sheets[0]; - } else { - if (typeof opts != 'undefined') { - sheet = opts; + // + // 831xl.js - Coloring Excel + // 18.04.2015 + // Generate XLS file with colors and styles + // with Excel + + alasql.into.XLS = function (filename, opts, data, columns, cb) { + // If filename is not defined then output to the result + if (typeof filename == 'object') { + opts = filename; + filename = undefined; } - } - // Set sheet name and default is 'Sheet1' - if (typeof sheet.sheetid == 'undefined') { - sheet.sheetid = 'Sheet1'; - } + // Set sheets + var sheets = {}; + if (opts && opts.sheets) { + sheets = opts.sheets; + } + + // Default sheet + var sheet = {headers: true}; + if (typeof sheets['Sheet1'] != 'undefined') { + sheet = sheets[0]; + } else { + if (typeof opts != 'undefined') { + sheet = opts; + } + } - var s = toHTML(); + // Set sheet name and default is 'Sheet1' + if (typeof sheet.sheetid == 'undefined') { + sheet.sheetid = 'Sheet1'; + } - // File is ready to save - filename = alasql.utils.autoExtFilename(filename, 'xls', opts); - var res = alasql.utils.saveFile(filename, s); - if (cb) res = cb(res); - return res; + var s = toHTML(); - function toHTML() { - // Generate prologue - var s = - ' \ \ '; + s += ''; - // Generate body - s += ' 0) { - if (typeof data[0] == 'object') { - if (Array.isArray(data[0])) { - columns = data[0].map(function (d, columnidx) { - return {columnid: columnidx}; - }); + s += ''; + if (typeof sheet.caption != 'undefined') { + var caption = sheet.caption; + if (typeof caption == 'string') { + caption = {title: caption}; + } + s += ' 0) { + if (typeof data[0] == 'object') { + if (Array.isArray(data[0])) { + columns = data[0].map(function (d, columnidx) { + return {columnid: columnidx}; + }); + } else { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } + } } } - if (typeof column.width == 'number') column.width = column.width + 'px'; - if (typeof column.columnid == 'undefined') column.columnid = columnidx; - if (typeof column.title == 'undefined') column.title = '' + column.columnid.trim(); - if (sheet.headers && Array.isArray(sheet.headers)) column.title = sheet.headers[columnidx]; - }); - - // Set columns widths - s += ''; - columns.forEach(function (column) { - s += ''; - }); - s += ''; - - // Headers - if (sheet.headers) { - s += ''; - s += ''; - - // TODO: Skip columns to body - // Headers + // Prepare columns columns.forEach(function (column, columnidx) { - s += ''; + s += ''; - // Loop over data rows - data.forEach(function (row, rowidx) { - // Limit number of rows on the sheet - if (rowidx > sheet.limit) return; - // Create row - s += ' 0) { + // TODO: Skip columns to body - if (typeof typeid == 'undefined') { - if (typeof value == 'number') typeid = 'number'; - else if (typeof value == 'string') typeid = 'string'; - else if (typeof value == 'boolean') typeid = 'boolean'; - else if (typeof value == 'object') { - if (value instanceof Date) typeid = 'date'; + // Loop over data rows + data.forEach(function (row, rowidx) { + // Limit number of rows on the sheet + if (rowidx > sheet.limit) return; + // Create row + s += ' 0) { - data.forEach(function (dat, idx) { - prepareSheet(opts[idx], dat, undefined, idx + 1); - }); + if (typeof opts == 'object' && Array.isArray(opts)) { + if (data && data.length > 0) { + data.forEach(function (dat, idx) { + prepareSheet(opts[idx], dat, undefined, idx + 1); + }); + } + } else { + prepareSheet(opts, data, columns, 1); } - } else { - prepareSheet(opts, data, columns, 1); - } - saveWorkbook(cb); - } + saveWorkbook(cb); + } - /** + /** Prepare sheet @params {object} opts @params {array|object} data @params {array} columns Columns */ - function prepareSheet(opts, data, columns, idx) { - /** Default options for sheet */ - var opt = {sheetid: 'Sheet ' + idx, headers: true}; - alasql.utils.extend(opt, opts); - - var dataLength = Object.keys(data).length; - - // Generate columns if they are not defined - if (!columns || columns.length == 0) { - if (dataLength > 0) { - columns = Object.keys(data[0]).map(function (columnid) { - return {columnid: columnid}; - }); - } else { - columns = []; + function prepareSheet(opts, data, columns, idx) { + /** Default options for sheet */ + var opt = {sheetid: 'Sheet ' + idx, headers: true}; + alasql.utils.extend(opt, opts); + + var dataLength = Object.keys(data).length; + + // Generate columns if they are not defined + if (!columns || columns.length == 0) { + if (dataLength > 0) { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } else { + columns = []; + } } - } - var cells = {}; + var cells = {}; - if (wb.SheetNames.indexOf(opt.sheetid) > -1) { - cells = wb.Sheets[opt.sheetid]; - } else { - wb.SheetNames.push(opt.sheetid); - wb.Sheets[opt.sheetid] = {}; - cells = wb.Sheets[opt.sheetid]; - } + if (wb.SheetNames.indexOf(opt.sheetid) > -1) { + cells = wb.Sheets[opt.sheetid]; + } else { + wb.SheetNames.push(opt.sheetid); + wb.Sheets[opt.sheetid] = {}; + cells = wb.Sheets[opt.sheetid]; + } - var range = 'A1'; - if (opt.range) range = opt.range; + var range = 'A1'; + if (opt.range) range = opt.range; - var col0 = alasql.utils.xlscn(range.match(/[A-Z]+/)[0]); - var row0 = +range.match(/[0-9]+/)[0] - 1; + var col0 = alasql.utils.xlscn(range.match(/[A-Z]+/)[0]); + var row0 = +range.match(/[0-9]+/)[0] - 1; - if (wb.Sheets[opt.sheetid]['!ref']) { - var rangem = wb.Sheets[opt.sheetid]['!ref']; - var colm = alasql.utils.xlscn(rangem.match(/[A-Z]+/)[0]); - var rowm = +rangem.match(/[0-9]+/)[0] - 1; - } else { - var colm = 1, - rowm = 1; - } - var zeroColumnFix = columns.length ? 0 : 1; - var colmax = Math.max(col0 + columns.length - 1 + zeroColumnFix, colm); - var rowmax = Math.max(row0 + dataLength + 2, rowm); + if (wb.Sheets[opt.sheetid]['!ref']) { + var rangem = wb.Sheets[opt.sheetid]['!ref']; + var colm = alasql.utils.xlscn(rangem.match(/[A-Z]+/)[0]); + var rowm = +rangem.match(/[0-9]+/)[0] - 1; + } else { + var colm = 1, + rowm = 1; + } + var zeroColumnFix = columns.length ? 0 : 1; + var colmax = Math.max(col0 + columns.length - 1 + zeroColumnFix, colm); + var rowmax = Math.max(row0 + dataLength + 2, rowm); - // console.log(col0,row0); - var i = row0 + 1; + // console.log(col0,row0); + var i = row0 + 1; - wb.Sheets[opt.sheetid]['!ref'] = 'A1:' + alasql.utils.xlsnc(colmax) + rowmax; - // var i = 1; + wb.Sheets[opt.sheetid]['!ref'] = 'A1:' + alasql.utils.xlsnc(colmax) + rowmax; + // var i = 1; - if (opt.headers) { - columns.forEach(function (col, idx) { - cells[alasql.utils.xlsnc(col0 + idx) + '' + i] = { - v: col.columnid.trim(), - }; - }); - i++; - } + if (opt.headers) { + columns.forEach(function (col, idx) { + cells[alasql.utils.xlsnc(col0 + idx) + '' + i] = { + v: col.columnid.trim(), + }; + }); + i++; + } - for (var j = 0; j < dataLength; j++) { - columns.forEach(function (col, idx) { - var cell = {v: data[j][col.columnid]}; - if (typeof data[j][col.columnid] == 'number') { - cell.t = 'n'; - } else if (typeof data[j][col.columnid] == 'string') { - cell.t = 's'; - } else if (typeof data[j][col.columnid] == 'boolean') { - cell.t = 'b'; - } else if (typeof data[j][col.columnid] == 'object') { - if (data[j][col.columnid] instanceof Date) { - cell.t = 'd'; + for (var j = 0; j < dataLength; j++) { + columns.forEach(function (col, idx) { + var cell = {v: data[j][col.columnid]}; + if (typeof data[j][col.columnid] == 'number') { + cell.t = 'n'; + } else if (typeof data[j][col.columnid] == 'string') { + cell.t = 's'; + } else if (typeof data[j][col.columnid] == 'boolean') { + cell.t = 'b'; + } else if (typeof data[j][col.columnid] == 'object') { + if (data[j][col.columnid] instanceof Date) { + cell.t = 'd'; + } } - } - cells[alasql.utils.xlsnc(col0 + idx) + '' + i] = cell; - }); - i++; + cells[alasql.utils.xlsnc(col0 + idx) + '' + i] = cell; + }); + i++; + } } - } - /** + /** Save Workbook @params {array} wb Workbook @params {callback} cb Callback */ - function saveWorkbook(cb) { - //console.log(wb); - var XLSX; - - if (typeof filename == 'undefined') { - res = wb; - } else { - XLSX = getXLSX(); + function saveWorkbook(cb) { + //console.log(wb); + var XLSX; - if (utils.isNode || utils.isMeteorServer) { - XLSX.writeFile(wb, filename); + if (typeof filename == 'undefined') { + res = wb; } else { - var wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'}; - var wbout = XLSX.write(wb, wopts); - - var s2ab = function (s) { - var buf = new ArrayBuffer(s.length); - var view = new Uint8Array(buf); - for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff; - return buf; - }; - - saveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}), filename); + XLSX = getXLSX(); + + if (utils.isNode || utils.isMeteorServer) { + XLSX.writeFile(wb, filename); + } else { + var wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'}; + var wbout = XLSX.write(wb, wopts); + + var s2ab = function (s) { + var buf = new ArrayBuffer(s.length); + var view = new Uint8Array(buf); + for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff; + return buf; + }; + + saveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}), filename); + } } - } - /*/* + /*/* // data.forEach(function(d){ // s += columns.map(function(col){ // return d[col.columnid]; @@ -208,6 +208,6 @@ alasql.into.XLSX = function (filename, opts, data, columns, cb) { // }); // alasql.utils.saveFile(filename,s); */ - } -}; + } + }; } diff --git a/src/formats/xlsxml.js b/src/formats/xlsxml.js index 43b46f2bba..d461310a83 100755 --- a/src/formats/xlsxml.js +++ b/src/formats/xlsxml.js @@ -1,51 +1,51 @@ import {extend, hash} from '../utils/helpers.js'; export default function install(alasql) { -alasql.into.XLSXML = function (filename, opts, data, columns, cb) { - opts = opts || {}; - - // If filename is not defined then output to the result - if (typeof filename == 'object') { - opts = filename; - filename = undefined; - } - - // Set sheets - var sheets = {}; - var sheetsdata; - var sheetscolumns; - if (opts && opts.sheets) { - sheets = opts.sheets; - // data and columns are already an array for the sheets - sheetsdata = data; - sheetscolumns = columns; - } else { - sheets.Sheet1 = opts; - // wrapd ata and columns array for single sheet - sheetsdata = [data]; - sheetscolumns = [columns]; - } - - // File is ready to save - filename = alasql.utils.autoExtFilename(filename, 'xls', opts); - var res = alasql.utils.saveFile(filename, toXML()); - if (cb) res = cb(res); - return res; - - function toXML() { - // Helper function to escape XML special characters - function escapeXML(str) { - if (str === null || str === undefined) return ''; - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + alasql.into.XLSXML = function (filename, opts, data, columns, cb) { + opts = opts || {}; + + // If filename is not defined then output to the result + if (typeof filename == 'object') { + opts = filename; + filename = undefined; + } + + // Set sheets + var sheets = {}; + var sheetsdata; + var sheetscolumns; + if (opts && opts.sheets) { + sheets = opts.sheets; + // data and columns are already an array for the sheets + sheetsdata = data; + sheetscolumns = columns; + } else { + sheets.Sheet1 = opts; + // wrapd ata and columns array for single sheet + sheetsdata = [data]; + sheetscolumns = [columns]; } - var s1 = - ' \ + // File is ready to save + filename = alasql.utils.autoExtFilename(filename, 'xls', opts); + var res = alasql.utils.saveFile(filename, toXML()); + if (cb) res = cb(res); + return res; + + function toXML() { + // Helper function to escape XML special characters + function escapeXML(str) { + if (str === null || str === undefined) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + var s1 = + ' \ \ '; - var s2 = ''; // for styles - - var s3 = ' '; - - var styles = {}; // hash based storage for styles - var stylesn = 62; // First style - - // Generate style - function hstyle(st) { - // Prepare string - var s = ''; - for (var key in st) { - s += '<' + key; - for (var attr in st[key]) { - s += ' '; - if (attr.substr(0, 2) == 'x:') { - s += attr; - } else { - s += 'ss:'; - } - s += attr + '=' + JSON.stringify(st[key][attr]); - } - s += '/>'; - } + var s2 = ''; // for styles - var hh = hash(s); - // Store in hash - if (styles[hh]) { - } else { - styles[hh] = {styleid: stylesn}; - s2 += `'; - stylesn++; - } - return 's' + styles[hh].styleid; - } + var s3 = ' '; - function values(obj) { - try { - return Object.values(obj); - } catch (e) { - // support for older runtimes - return Object.keys(obj).map(function (e) { - return obj[e]; - }); - } - } + var styles = {}; // hash based storage for styles + var stylesn = 62; // First style - var sheetidx = 0; - for (var sheetid in sheets) { - var sheet = sheets[sheetid]; - var idx = typeof sheet.dataidx != 'undefined' ? sheet.dataidx : sheetidx++; - var data = values(sheetsdata[idx]); - // If columns defined in sheet, then take them - var columns = undefined; - if (typeof sheet.columns != 'undefined') { - columns = sheet.columns; - } else { - // Autogenerate columns if they are passed as parameters - columns = sheetscolumns[idx]; - if (columns === undefined || (columns.length == 0 && data.length > 0)) { - if (typeof data[0] == 'object') { - if (Array.isArray(data[0])) { - columns = data[0].map(function (d, columnidx) { - return {columnid: columnidx}; - }); + // Generate style + function hstyle(st) { + // Prepare string + var s = ''; + for (var key in st) { + s += '<' + key; + for (var attr in st[key]) { + s += ' '; + if (attr.substr(0, 2) == 'x:') { + s += attr; } else { - columns = Object.keys(data[0]).map(function (columnid) { - return {columnid: columnid}; - }); + s += 'ss:'; } + s += attr + '=' + JSON.stringify(st[key][attr]); } + s += '/>'; } + + var hh = hash(s); + // Store in hash + if (styles[hh]) { + } else { + styles[hh] = {styleid: stylesn}; + s2 += `'; + stylesn++; + } + return 's' + styles[hh].styleid; } - // Prepare columns - columns.forEach(function (column, columnidx) { - if (typeof sheet.column != 'undefined') { - extend(column, sheet.column); + function values(obj) { + try { + return Object.values(obj); + } catch (e) { + // support for older runtimes + return Object.keys(obj).map(function (e) { + return obj[e]; + }); } + } - if (typeof column.width == 'undefined') { - if (sheet.column && typeof sheet.column.width != 'undefined') { - column.width = sheet.column.width; - } else { - column.width = 120; + var sheetidx = 0; + for (var sheetid in sheets) { + var sheet = sheets[sheetid]; + var idx = typeof sheet.dataidx != 'undefined' ? sheet.dataidx : sheetidx++; + var data = values(sheetsdata[idx]); + // If columns defined in sheet, then take them + var columns = undefined; + if (typeof sheet.columns != 'undefined') { + columns = sheet.columns; + } else { + // Autogenerate columns if they are passed as parameters + columns = sheetscolumns[idx]; + if (columns === undefined || (columns.length == 0 && data.length > 0)) { + if (typeof data[0] == 'object') { + if (Array.isArray(data[0])) { + columns = data[0].map(function (d, columnidx) { + return {columnid: columnidx}; + }); + } else { + columns = Object.keys(data[0]).map(function (columnid) { + return {columnid: columnid}; + }); + } + } } } - if (typeof column.width == 'number') column.width = column.width; - if (typeof column.columnid == 'undefined') column.columnid = columnidx; - if (typeof column.title == 'undefined') column.title = '' + column.columnid.trim(); - if (sheet.headers && Array.isArray(sheet.headers)) column.title = sheet.headers[columnidx]; - }); - - // Header - s3 += - ' \ -
'; + }); + s += ''; - if (data && data.length > 0) { - // TODO: Skip columns to body + // Headers + if (sheet.headers) { + s += '
'; - columns.forEach(function (column, columnidx) { - s3 += ` - `; - }); - - // Headers - if (sheet.headers) { - s3 += ''; - - // TODO: Skip columns to body - - // Headers + // Prepare columns columns.forEach(function (column, columnidx) { - s3 += ' \ +
'; - // Column title - if (typeof column.title != 'undefined') { - if (typeof column.title == 'function') { - s3 += escapeXML(column.title(sheet, column, columnidx)); - } else { - s3 += escapeXML(column.title); - } - } - s3 += ''; + columns.forEach(function (column, columnidx) { + s3 += ` + `; }); - s3 += ''; - } + // Headers + if (sheet.headers) { + s3 += ''; - // Data - if (data && data.length > 0) { - // Loop over data rows - data.forEach(function (row, rowidx) { - // Limit number of rows on the sheet - if (rowidx > sheet.limit) return; - - // Extend row properties - var srow = {}; - extend(srow, sheet.row); - if (sheet.rows && sheet.rows[rowidx]) { - extend(srow, sheet.rows[rowidx]); - } + // TODO: Skip columns to body - s3 += '' + s3 += '>'; - // Data - columns.forEach(function (column, columnidx) { - // Parameters - var cell = {}; - extend(cell, sheet.cell); - extend(cell, srow.cell); - if (typeof sheet.column != 'undefined') { - extend(cell, sheet.column.cell); - } - extend(cell, column.cell); - if (sheet.cells && sheet.cells[rowidx] && sheet.cells[rowidx][columnidx]) { - extend(cell, sheet.cells[rowidx][columnidx]); + // Column title + if (typeof column.title != 'undefined') { + if (typeof column.title == 'function') { + s3 += escapeXML(column.title(sheet, column, columnidx)); + } else { + s3 += escapeXML(column.title); + } } + s3 += ''; + }); - // Create value - var value = row[column.columnid]; - if (typeof cell.value == 'function') { - value = cell.value(value, sheet, row, column, cell, rowidx, columnidx); - } + s3 += ''; + } - // Define cell type - var typeid = cell.typeid; - if (typeof typeid == 'function') { - typeid = typeid(value, sheet, row, column, cell, rowidx, columnidx); + // Data + if (data && data.length > 0) { + // Loop over data rows + data.forEach(function (row, rowidx) { + // Limit number of rows on the sheet + if (rowidx > sheet.limit) return; + + // Extend row properties + var srow = {}; + extend(srow, sheet.row); + if (sheet.rows && sheet.rows[rowidx]) { + extend(srow, sheet.rows[rowidx]); } - if (typeof typeid == 'undefined') { - if (typeof value == 'number') typeid = 'number'; - else if (typeof value == 'string') typeid = 'string'; - else if (typeof value == 'boolean') typeid = 'boolean'; - else if (typeof value == 'object') { - if (value instanceof Date) typeid = 'date'; + s3 += '' - // Prepare Data types styles - var typestyle = ''; + // Data + columns.forEach(function (column, columnidx) { + // Parameters + var cell = {}; + extend(cell, sheet.cell); + extend(cell, srow.cell); + if (typeof sheet.column != 'undefined') { + extend(cell, sheet.column.cell); + } + extend(cell, column.cell); + if (sheet.cells && sheet.cells[rowidx] && sheet.cells[rowidx][columnidx]) { + extend(cell, sheet.cells[rowidx][columnidx]); + } - if (typeid == 'money') { - typestyle = 'mso-number-format:"\\#\\,\\#\\#0\\\\ _Ñ€_\\.";white-space:normal;'; - } else if (typeid == 'number') { - typestyle = ' '; - } else if (typeid == 'date') { - typestyle = 'mso-number-format:"Short Date";'; - } else { - // For other types is saved - if (opts.types && opts.types[typeid] && opts.types[typeid].typestyle) { - typestyle = opts.types[typeid].typestyle; + // Create value + var value = row[column.columnid]; + if (typeof cell.value == 'function') { + value = cell.value(value, sheet, row, column, cell, rowidx, columnidx); } - } - // TODO Replace with extend... - typestyle = typestyle || 'mso-number-format:"\\@";'; // Default type style + // Define cell type + var typeid = cell.typeid; + if (typeof typeid == 'function') { + typeid = typeid(value, sheet, row, column, cell, rowidx, columnidx); + } - s3 += ''; + // Prepare Data types styles + var typestyle = ''; - // TODO Replace with extend... - var format = cell.format; - if (typeof value == 'undefined') { - s3 += ''; - } else if (typeof format != 'undefined') { - if (typeof format == 'function') { - s3 += escapeXML(format(value)); - } else if (typeof format == 'string') { - s3 += escapeXML(value); // TODO - add string format + if (typeid == 'money') { + typestyle = 'mso-number-format:"\\#\\,\\#\\#0\\\\ _Ñ€_\\.";white-space:normal;'; + } else if (typeid == 'number') { + typestyle = ' '; + } else if (typeid == 'date') { + typestyle = 'mso-number-format:"Short Date";'; } else { - throw new Error('Unknown format type. Should be function or string'); + // For other types is saved + if (opts.types && opts.types[typeid] && opts.types[typeid].typestyle) { + typestyle = opts.types[typeid].typestyle; + } } - } else { - if (typeid == 'number' || typeid == 'date') { - s3 += escapeXML(value.toString()); - } else if (typeid == 'money') { - s3 += escapeXML((+value).toFixed(2)); + + // TODO Replace with extend... + typestyle = typestyle || 'mso-number-format:"\\@";'; // Default type style + + s3 += ''; + + // TODO Replace with extend... + var format = cell.format; + if (typeof value == 'undefined') { + s3 += ''; + } else if (typeof format != 'undefined') { + if (typeof format == 'function') { + s3 += escapeXML(format(value)); + } else if (typeof format == 'string') { + s3 += escapeXML(value); // TODO - add string format + } else { + throw new Error('Unknown format type. Should be function or string'); + } } else { - s3 += escapeXML(value); + if (typeid == 'number' || typeid == 'date') { + s3 += escapeXML(value.toString()); + } else if (typeid == 'money') { + s3 += escapeXML((+value).toFixed(2)); + } else { + s3 += escapeXML(value); + } } - } - // s3 += row[column.columnid]; - s3 += ''; - }); + // s3 += row[column.columnid]; + s3 += ''; + }); - s3 += ''; - }); + s3 += ''; + }); + } + // Finish + s3 += '
'; } - // Finish - s3 += ''; - } - s3 += ''; + s3 += ''; - return s1 + s2 + s3; - } -}; + return s1 + s2 + s3; + } + }; } diff --git a/src/formats/xml.js b/src/formats/xml.js index 670de1a9ee..95dfcc90eb 100755 --- a/src/formats/xml.js +++ b/src/formats/xml.js @@ -1,168 +1,168 @@ export default function install(alasql) { -alasql.from.XML = function (filename, opts, cb, idx, query) { - var res; - //console.log('cb',cb); - //console.log('JSON'); - alasql.utils.loadFile(filename, !!cb, function (data) { - // console.log('DATA:'+data); - // res = [{a:1}]; - - res = xmlparse(data).root; - // console.log(res); - if (cb) res = cb(res, idx, query); - }); - return res; -}; - -/** - * Parse the given string of `xml`. - * - * @param {String} xml - * @return {Object} - * @api public - */ - -function xmlparse(xml) { - xml = xml.trim(); - - // strip comments - xml = xml.replace(//g, ''); - - return document(); + alasql.from.XML = function (filename, opts, cb, idx, query) { + var res; + //console.log('cb',cb); + //console.log('JSON'); + alasql.utils.loadFile(filename, !!cb, function (data) { + // console.log('DATA:'+data); + // res = [{a:1}]; + + res = xmlparse(data).root; + // console.log(res); + if (cb) res = cb(res, idx, query); + }); + return res; + }; /** - * XML document. + * Parse the given string of `xml`. + * + * @param {String} xml + * @return {Object} + * @api public */ - function document() { - return { - declaration: declaration(), - root: tag(), - }; - } + function xmlparse(xml) { + xml = xml.trim(); - /** - * Declaration. - */ + // strip comments + xml = xml.replace(//g, ''); - function declaration() { - var m = match(/^<\?xml\s*/); - if (!m) return; + return document(); - // tag - var node = { - attributes: {}, - }; + /** + * XML document. + */ - // attributes - while (!(eos() || is('?>'))) { - var attr = attribute(); - if (!attr) return node; - node.attributes[attr.name] = attr.value; + function document() { + return { + declaration: declaration(), + root: tag(), + }; } - match(/\?>\s*/); + /** + * Declaration. + */ - return node; - } + function declaration() { + var m = match(/^<\?xml\s*/); + if (!m) return; - /** - * Tag. - */ + // tag + var node = { + attributes: {}, + }; - function tag() { - var m = match(/^<([\w-:.]+)\s*/); - if (!m) return; - - // name - var node = { - name: m[1], - attributes: {}, - children: [], - }; - - // attributes - while (!(eos() || is('>') || is('?>') || is('/>'))) { - var attr = attribute(); - if (!attr) return node; - node.attributes[attr.name] = attr.value; - } + // attributes + while (!(eos() || is('?>'))) { + var attr = attribute(); + if (!attr) return node; + node.attributes[attr.name] = attr.value; + } + + match(/\?>\s*/); - // self closing tag - if (match(/^\s*\/>\s*/)) { return node; } - match(/\??>\s*/); + /** + * Tag. + */ - // content - node.content = content(); + function tag() { + var m = match(/^<([\w-:.]+)\s*/); + if (!m) return; - // children - var child; - while ((child = tag())) { - node.children.push(child); - } + // name + var node = { + name: m[1], + attributes: {}, + children: [], + }; - // closing - match(/^<\/[\w-:.]+>\s*/); + // attributes + while (!(eos() || is('>') || is('?>') || is('/>'))) { + var attr = attribute(); + if (!attr) return node; + node.attributes[attr.name] = attr.value; + } - return node; - } + // self closing tag + if (match(/^\s*\/>\s*/)) { + return node; + } - /** - * Text content. - */ + match(/\??>\s*/); - function content() { - var m = match(/^([^<]*)/); - if (m) return m[1]; - return ''; - } + // content + node.content = content(); - /** - * Attribute. - */ + // children + var child; + while ((child = tag())) { + node.children.push(child); + } - function attribute() { - var m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/); - if (!m) return; - return {name: m[1], value: strip(m[2])}; - } + // closing + match(/^<\/[\w-:.]+>\s*/); - /** - * Strip quotes from `val`. - */ + return node; + } - function strip(val) { - return val.replace(/^['"]|['"]$/g, ''); - } + /** + * Text content. + */ - /** - * Match `re` and advance the string. - */ + function content() { + var m = match(/^([^<]*)/); + if (m) return m[1]; + return ''; + } - function match(re) { - var m = xml.match(re); - if (!m) return; - xml = xml.slice(m[0].length); - return m; - } + /** + * Attribute. + */ - /** - * End-of-source. - */ + function attribute() { + var m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/); + if (!m) return; + return {name: m[1], value: strip(m[2])}; + } - function eos() { - return 0 == xml.length; - } + /** + * Strip quotes from `val`. + */ - /** - * Check for `prefix`. - */ + function strip(val) { + return val.replace(/^['"]|['"]$/g, ''); + } + + /** + * Match `re` and advance the string. + */ + + function match(re) { + var m = xml.match(re); + if (!m) return; + xml = xml.slice(m[0].length); + return m; + } + + /** + * End-of-source. + */ - function is(prefix) { - return 0 == xml.indexOf(prefix); + function eos() { + return 0 == xml.length; + } + + /** + * Check for `prefix`. + */ + + function is(prefix) { + return 0 == xml.indexOf(prefix); + } } } -} diff --git a/src/functions/date.js b/src/functions/date.js index 33f7ca17ee..1535fd915f 100644 --- a/src/functions/date.js +++ b/src/functions/date.js @@ -13,202 +13,201 @@ export function newDate(d) { export default function install(alasql) { var stdfn = alasql.stdfn; -// -// Date functions -// -// (c) 2014, Andrey Gershun -// + // + // Date functions + // + // (c) 2014, Andrey Gershun + // -/** Standard JavaScript data types */ + /** Standard JavaScript data types */ -alasql.fn.Date = Object; -alasql.fn.Date = Date; -alasql.fn.Number = Number; -alasql.fn.String = String; -alasql.fn.Boolean = Boolean; + alasql.fn.Date = Object; + alasql.fn.Date = Date; + alasql.fn.Number = Number; + alasql.fn.String = String; + alasql.fn.Boolean = Boolean; -/** Extend Object with properties */ -stdfn.EXTEND = alasql.utils.extend; + /** Extend Object with properties */ + stdfn.EXTEND = alasql.utils.extend; -stdfn.CHAR = String.fromCharCode.bind(String); -stdfn.ASCII = function (a) { - return a.charCodeAt(0); -}; + stdfn.CHAR = String.fromCharCode.bind(String); + stdfn.ASCII = function (a) { + return a.charCodeAt(0); + }; -/** + /** Return first non-null argument See https://msdn.microsoft.com/en-us/library/ms190349.aspx */ -stdfn.COALESCE = function () { - for (var i = 0; i < arguments.length; i++) { - if (arguments[i] === null) continue; - if (typeof arguments[i] == 'undefined') continue; - if (typeof arguments[i] == 'number' && isNaN(arguments[i])) continue; - return arguments[i]; - } - return undefined; -}; - -stdfn.USER = function () { - return 'alasql'; -}; - -stdfn.OBJECT_ID = function (objid) { - return !!alasql.tables[objid]; -}; - -stdfn.DATE = function (d) { - if (!isNaN(d) && d.length === 8) - return new Date(+d.substr(0, 4), +d.substr(4, 2) - 1, +d.substr(6, 2)); - return newDate(d); -}; - -stdfn.NOW = function () { - if (alasql.options.dateAsString) { - var d = new Date(); - var s = - d.getFullYear() + - '-' + - ('0' + (d.getMonth() + 1)).substr(-2) + - '-' + - ('0' + d.getDate()).substr(-2); - s += - ' ' + - ('0' + d.getHours()).substr(-2) + - ':' + - ('0' + d.getMinutes()).substr(-2) + - ':' + - ('0' + d.getSeconds()).substr(-2); - s += '.' + ('00' + d.getMilliseconds()).substr(-3); - return s; - } - return new Date(); -}; - -stdfn.GETDATE = stdfn.NOW; -stdfn.CURRENT_TIMESTAMP = stdfn.NOW; - -/** - * Returns the current date, without time component. - * @returns date object without time component - */ -stdfn.CURDATE = stdfn.CURRENT_DATE = function () { - var date = new Date(); - date.setHours(0, 0, 0, 0); - if (alasql.options.dateAsString) { - var s = - date.getFullYear() + - '-' + - ('0' + (date.getMonth() + 1)).substr(-2) + - '-' + - ('0' + date.getDate()).substr(-2); - return s; - } - return date; -}; - -// stdfn.GETDATE = function(){ -// var d = new Date(); -// var s = d.getFullYear()+"."+("0"+(d.getMonth()+1)).substr(-2)+"."+("0"+d.getDate()).substr(-2); -// return s; -// } - -stdfn.SECOND = function (d) { - var d = newDate(d); - return d.getSeconds(); -}; - -stdfn.MINUTE = function (d) { - var d = newDate(d); - return d.getMinutes(); -}; - -stdfn.HOUR = function (d) { - var d = newDate(d); - return d.getHours(); -}; - -stdfn.DAYOFWEEK = stdfn.WEEKDAY = function (d) { - var d = newDate(d); - return d.getDay(); -}; - -stdfn.DAY = stdfn.DAYOFMONTH = function (d) { - var d = newDate(d); - return d.getDate(); -}; - -stdfn.MONTH = function (d) { - var d = newDate(d); - return d.getMonth() + 1; -}; - -stdfn.YEAR = function (d) { - var d = newDate(d); - return d.getFullYear(); -}; - -var PERIODS = { - year: 1000 * 3600 * 24 * 365, - quarter: (1000 * 3600 * 24 * 365) / 4, - month: 1000 * 3600 * 24 * 30, - week: 1000 * 3600 * 24 * 7, - day: 1000 * 3600 * 24, - dayofyear: 1000 * 3600 * 24, - weekday: 1000 * 3600 * 24, - hour: 1000 * 3600, - minute: 1000 * 60, - second: 1000, - millisecond: 1, - microsecond: 0.001, -}; - -function periodLength(period) { - if (!PERIODS.hasOwnProperty(period)) { - throw new Error('Unknown date part: ' + period); - } - return PERIODS[period]; -} - -alasql.stdfn.DATEDIFF = function (period, d1, d2) { - var interval = newDate(d2).getTime() - newDate(d1).getTime(); - return (interval / periodLength(period.toLowerCase())) | 0; -}; - -alasql.stdfn.DATEADD = function (period, interval, d) { - var nd = newDate(d); - var period = period.toLowerCase(); - - switch (period) { - case 'year': - nd.setFullYear(nd.getFullYear() + interval); - break; - case 'quarter': - nd.setMonth(nd.getMonth() + interval * 3); - break; - case 'month': - nd.setMonth(nd.getMonth() + interval); - break; - default: - nd = new Date(nd.getTime() + interval * periodLength(period)); - break; + stdfn.COALESCE = function () { + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] === null) continue; + if (typeof arguments[i] == 'undefined') continue; + if (typeof arguments[i] == 'number' && isNaN(arguments[i])) continue; + return arguments[i]; + } + return undefined; + }; + + stdfn.USER = function () { + return 'alasql'; + }; + + stdfn.OBJECT_ID = function (objid) { + return !!alasql.tables[objid]; + }; + + stdfn.DATE = function (d) { + if (!isNaN(d) && d.length === 8) + return new Date(+d.substr(0, 4), +d.substr(4, 2) - 1, +d.substr(6, 2)); + return newDate(d); + }; + + stdfn.NOW = function () { + if (alasql.options.dateAsString) { + var d = new Date(); + var s = + d.getFullYear() + + '-' + + ('0' + (d.getMonth() + 1)).substr(-2) + + '-' + + ('0' + d.getDate()).substr(-2); + s += + ' ' + + ('0' + d.getHours()).substr(-2) + + ':' + + ('0' + d.getMinutes()).substr(-2) + + ':' + + ('0' + d.getSeconds()).substr(-2); + s += '.' + ('00' + d.getMilliseconds()).substr(-3); + return s; + } + return new Date(); + }; + + stdfn.GETDATE = stdfn.NOW; + stdfn.CURRENT_TIMESTAMP = stdfn.NOW; + + /** + * Returns the current date, without time component. + * @returns date object without time component + */ + stdfn.CURDATE = stdfn.CURRENT_DATE = function () { + var date = new Date(); + date.setHours(0, 0, 0, 0); + if (alasql.options.dateAsString) { + var s = + date.getFullYear() + + '-' + + ('0' + (date.getMonth() + 1)).substr(-2) + + '-' + + ('0' + date.getDate()).substr(-2); + return s; + } + return date; + }; + + // stdfn.GETDATE = function(){ + // var d = new Date(); + // var s = d.getFullYear()+"."+("0"+(d.getMonth()+1)).substr(-2)+"."+("0"+d.getDate()).substr(-2); + // return s; + // } + + stdfn.SECOND = function (d) { + var d = newDate(d); + return d.getSeconds(); + }; + + stdfn.MINUTE = function (d) { + var d = newDate(d); + return d.getMinutes(); + }; + + stdfn.HOUR = function (d) { + var d = newDate(d); + return d.getHours(); + }; + + stdfn.DAYOFWEEK = stdfn.WEEKDAY = function (d) { + var d = newDate(d); + return d.getDay(); + }; + + stdfn.DAY = stdfn.DAYOFMONTH = function (d) { + var d = newDate(d); + return d.getDate(); + }; + + stdfn.MONTH = function (d) { + var d = newDate(d); + return d.getMonth() + 1; + }; + + stdfn.YEAR = function (d) { + var d = newDate(d); + return d.getFullYear(); + }; + + var PERIODS = { + year: 1000 * 3600 * 24 * 365, + quarter: (1000 * 3600 * 24 * 365) / 4, + month: 1000 * 3600 * 24 * 30, + week: 1000 * 3600 * 24 * 7, + day: 1000 * 3600 * 24, + dayofyear: 1000 * 3600 * 24, + weekday: 1000 * 3600 * 24, + hour: 1000 * 3600, + minute: 1000 * 60, + second: 1000, + millisecond: 1, + microsecond: 0.001, + }; + + function periodLength(period) { + if (!PERIODS.hasOwnProperty(period)) { + throw new Error('Unknown date part: ' + period); + } + return PERIODS[period]; } - return nd; -}; + alasql.stdfn.DATEDIFF = function (period, d1, d2) { + var interval = newDate(d2).getTime() - newDate(d1).getTime(); + return (interval / periodLength(period.toLowerCase())) | 0; + }; + + alasql.stdfn.DATEADD = function (period, interval, d) { + var nd = newDate(d); + var period = period.toLowerCase(); + + switch (period) { + case 'year': + nd.setFullYear(nd.getFullYear() + interval); + break; + case 'quarter': + nd.setMonth(nd.getMonth() + interval * 3); + break; + case 'month': + nd.setMonth(nd.getMonth() + interval); + break; + default: + nd = new Date(nd.getTime() + interval * periodLength(period)); + break; + } -alasql.stdfn.INTERVAL = function (interval, period) { - return interval * PERIODS[period.toLowerCase()]; -}; + return nd; + }; -alasql.stdfn.DATE_ADD = alasql.stdfn.ADDDATE = function (d, interval) { - var nd = newDate(d).getTime() + interval; - return new Date(nd); -}; + alasql.stdfn.INTERVAL = function (interval, period) { + return interval * PERIODS[period.toLowerCase()]; + }; -alasql.stdfn.DATE_SUB = alasql.stdfn.SUBDATE = function (d, interval) { - var nd = newDate(d).getTime() - interval; - return new Date(nd); -}; + alasql.stdfn.DATE_ADD = alasql.stdfn.ADDDATE = function (d, interval) { + var nd = newDate(d).getTime() + interval; + return new Date(nd); + }; + alasql.stdfn.DATE_SUB = alasql.stdfn.SUBDATE = function (d, interval) { + var nd = newDate(d).getTime() - interval; + return new Date(nd); + }; } diff --git a/src/functions/functions.js b/src/functions/functions.js index b1ab3d3ecf..315bc759bd 100644 --- a/src/functions/functions.js +++ b/src/functions/functions.js @@ -6,7 +6,7 @@ export let re_invalidFnNameChars = /[^0-9A-Z_$]+/i; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // Functions for Alasql.js // Date: 03.11.2014 @@ -14,100 +14,100 @@ export default function install(alasql) { // */ -yy.FuncValue = function (params) { - return Object.assign(this, params); -}; - -yy.FuncValue.prototype.toString = function () { - let s = ''; + yy.FuncValue = function (params) { + return Object.assign(this, params); + }; - if (alasql.fn[this.funcid]) s += this.funcid; - else if (alasql.aggr[this.funcid]) s += this.funcid; - else if (alasql.stdlib[this.funcid.toUpperCase()] || alasql.stdfn[this.funcid.toUpperCase()]) - s += this.funcid.toUpperCase().replace(re_invalidFnNameChars, ''); + yy.FuncValue.prototype.toString = function () { + let s = ''; + + if (alasql.fn[this.funcid]) s += this.funcid; + else if (alasql.aggr[this.funcid]) s += this.funcid; + else if (alasql.stdlib[this.funcid.toUpperCase()] || alasql.stdfn[this.funcid.toUpperCase()]) + s += this.funcid.toUpperCase().replace(re_invalidFnNameChars, ''); + + if (this.funcid !== 'CURRENT_TIMESTAMP') { + s += '('; + if (this.args && this.args.length > 0) { + s += this.args + .map(function (arg) { + return arg.toString(); + }) + .join(','); + } + s += ')'; + } - if (this.funcid !== 'CURRENT_TIMESTAMP') { - s += '('; - if (this.args && this.args.length > 0) { - s += this.args - .map(function (arg) { - return arg.toString(); - }) - .join(','); + // Add OVER clause if present + if (this.over) { + s += ' ' + this.over.toString(); } - s += ')'; - } - // Add OVER clause if present - if (this.over) { - s += ' ' + this.over.toString(); - } + return s; + }; - return s; -}; - -yy.FuncValue.prototype.execute = function (databaseid, params, cb) { - let res = 1; - alasql.precompile(this, databaseid, params); // Precompile queries - // console.log(34,this.toJS('','',null)); - let expr = new Function('params,alasql', 'var y;return ' + this.toJS('', '', null)); - expr(params, alasql); - if (cb) res = cb(res); - return res; -}; - -yy.FuncValue.prototype.findAggregator = function (query) { - if (this.args && this.args.length > 0) { - this.args.forEach(function (arg) { - if (arg.findAggregator) arg.findAggregator(query); - }); - } -}; + yy.FuncValue.prototype.execute = function (databaseid, params, cb) { + let res = 1; + alasql.precompile(this, databaseid, params); // Precompile queries + // console.log(34,this.toJS('','',null)); + let expr = new Function('params,alasql', 'var y;return ' + this.toJS('', '', null)); + expr(params, alasql); + if (cb) res = cb(res); + return res; + }; -yy.FuncValue.prototype.toJS = function (context, tableid, defcols) { - var s = ''; - var funcid = this.funcid; - // IF this is standard compile functions - if (!alasql.fn[funcid] && alasql.stdlib[funcid.toUpperCase()]) { + yy.FuncValue.prototype.findAggregator = function (query) { if (this.args && this.args.length > 0) { - s += alasql.stdlib[funcid.toUpperCase()].apply( - this, - this.args.map(function (arg) { - return arg.toJS(context, tableid); - }) - ); - } else { - s += alasql.stdlib[funcid.toUpperCase()](); - } - } else if (!alasql.fn[funcid] && alasql.stdfn[funcid.toUpperCase()]) { - if (this.newid) s += 'new '; - s += 'alasql.stdfn[' + JSON.stringify(this.funcid.toUpperCase()) + ']('; - if (this.args && this.args.length > 0) { - s += this.args - .map(function (arg) { - return arg.toJS(context, tableid, defcols); - }) - .join(','); + this.args.forEach(function (arg) { + if (arg.findAggregator) arg.findAggregator(query); + }); } - s += ')'; - } else { - // This is user-defined run-time function - // TODO arguments!!! - // var s = ''; - if (this.newid) s += 'new '; - s += 'alasql.fn[' + JSON.stringify(this.funcid) + ']('; - if (this.args && this.args.length > 0) { - s += this.args - .map(function (arg) { - return arg.toJS(context, tableid, defcols); - }) - .join(','); + }; + + yy.FuncValue.prototype.toJS = function (context, tableid, defcols) { + var s = ''; + var funcid = this.funcid; + // IF this is standard compile functions + if (!alasql.fn[funcid] && alasql.stdlib[funcid.toUpperCase()]) { + if (this.args && this.args.length > 0) { + s += alasql.stdlib[funcid.toUpperCase()].apply( + this, + this.args.map(function (arg) { + return arg.toJS(context, tableid); + }) + ); + } else { + s += alasql.stdlib[funcid.toUpperCase()](); + } + } else if (!alasql.fn[funcid] && alasql.stdfn[funcid.toUpperCase()]) { + if (this.newid) s += 'new '; + s += 'alasql.stdfn[' + JSON.stringify(this.funcid.toUpperCase()) + ']('; + if (this.args && this.args.length > 0) { + s += this.args + .map(function (arg) { + return arg.toJS(context, tableid, defcols); + }) + .join(','); + } + s += ')'; + } else { + // This is user-defined run-time function + // TODO arguments!!! + // var s = ''; + if (this.newid) s += 'new '; + s += 'alasql.fn[' + JSON.stringify(this.funcid) + ']('; + if (this.args && this.args.length > 0) { + s += this.args + .map(function (arg) { + return arg.toJS(context, tableid, defcols); + }) + .join(','); + } + s += ')'; } - s += ')'; - } - return s; -}; -/* + return s; + }; + /* // // SQL FUNCTIONS COMPILERS // Based on SQLite functions @@ -118,490 +118,490 @@ yy.FuncValue.prototype.toJS = function (context, tableid, defcols) { //alasql.userlib = alasql.fn; */ -var stdlib = (alasql.stdlib = {}); -var stdfn = (alasql.stdfn = {}); - -stdlib.ABS = function (a) { - return 'Math.abs(' + a + ')'; -}; -stdlib.CLONEDEEP = function (a) { - return 'alasql.utils.cloneDeep(' + a + ')'; -}; - -stdfn.CONCAT = function () { - return Array.prototype.slice.call(arguments).join(''); -}; -stdlib.EXP = function (a) { - return 'Math.pow(Math.E,' + a + ')'; -}; - -stdlib.IIF = function (a, b, c) { - if (arguments.length === 3) { - return `((${a}) ? (${b}) : (${c}))`; - } else { - throw new Error('Number of arguments of IFF is not equals to 3'); - } -}; -stdlib.IFNULL = function (a, b) { - return `((typeof ${a} === "undefined" || ${a} === null) ? ${b} : ${a})`; -}; -stdlib.INSTR = function (s, p) { - return `((${s}).indexOf(${p}) + 1)`; -}; - -//stdlib.LEN = stdlib.LENGTH = function(s) {return '('+s+'+"").length';}; - -stdlib.LEN = stdlib.LENGTH = function (s) { - return und(s, 'y.length'); -}; -//stdlib.LENGTH = function(s) {return '('+s+').length'}; - -stdlib.LOWER = stdlib.LCASE = function (s) { - return und(s, 'String(y).toLowerCase()'); -}; -//stdlib.LCASE = function(s) {return '('+s+').toLowerCase()';} - -// Returns a character expression after it removes leading blanks. -// see https://docs.microsoft.com/en-us/sql/t-sql/functions/ltrim-transact-sql -stdlib.LTRIM = function (s) { - return und(s, 'y.replace(/^[ ]+/,"")'); -}; - -// Returns a character string after truncating all trailing spaces. -// see https://docs.microsoft.com/en-us/sql/t-sql/functions/rtrim-transact-sql -stdlib.RTRIM = function (s) { - return und(s, 'y.replace(/[ ]+$/,"")'); -}; - -stdlib.MAX = stdlib.GREATEST = function () { - return ( - '[' + - Array.prototype.join.call(arguments, ',') + - '].reduce(function (a, b) { return a > b ? a : b; })' - ); -}; - -stdlib.MIN = stdlib.LEAST = function () { - return ( - '[' + - Array.prototype.join.call(arguments, ',') + - '].reduce(function (a, b) { return a < b ? a : b; })' - ); -}; - -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 + ')'); - }; + var stdlib = (alasql.stdlib = {}); + var stdfn = (alasql.stdfn = {}); -stdfn.REGEXP_LIKE = function (a, b, c) { - // console.log(a,b,c); - // Convert MySQL word boundaries to JavaScript word boundaries - // [[:<:]] -> \b (start of word) - // [[:>:]] -> \b (end of word) - var pattern = b.replace(/\[\[:<:\]\]/g, '\\b').replace(/\[\[:>:\]\]/g, '\\b'); - return (a || '').search(RegExp(pattern, c)) > -1; -}; - -// Here we uses undefined instead of null -stdlib.ISNULL = stdlib.NULLIF = function (a, b) { - return '(' + a + '==' + b + '?undefined:' + a + ')'; -}; - -stdlib.POWER = function (a, b) { - return 'Math.pow(' + a + ',' + b + ')'; -}; - -stdlib.RANDOM = function (r) { - if (arguments.length == 0) { - return 'Math.random()'; - } else { - return '(Math.random()*(' + r + ')|0)'; - } -}; -stdfn.ROUND = function (s, d) { - if (s == null || (typeof s === 'string' && s.trim() === '')) return undefined; - s = Number(s); - if (isNaN(s)) return undefined; - if (arguments.length < 2) return Math.round(s); - return Math.round(s * Math.pow(10, d)) / Math.pow(10, d); -}; -stdlib.CEIL = stdlib.CEILING = function (s) { - return 'Math.ceil(' + s + ')'; -}; -stdlib.FLOOR = function (s) { - return 'Math.floor(' + s + ')'; -}; - -stdlib.ROWNUM = function () { - return '1'; -}; -stdlib.ROW_NUMBER = function () { - return '1'; -}; -stdlib.GROUP_ROW_NUMBER = function () { - return '1'; -}; - -stdlib.SQRT = function (s) { - return 'Math.sqrt(' + s + ')'; -}; - -stdlib.TRIM = function (s) { - return und(s, 'y.trim()'); -}; - -stdlib.UPPER = stdlib.UCASE = function (s) { - return und(s, 'String(y).toUpperCase()'); -}; - -// Concatination of strings -stdfn.CONCAT_WS = function () { - var args = Array.prototype.slice.call(arguments); - args = args.filter(x => !(x === null || typeof x === 'undefined')); - return args.slice(1, args.length).join(args[0] || ''); -}; - -//stdlib.UCASE = function(s) {return '('+s+').toUpperCase()';} -//REPLACE -// RTRIM -// SUBSTR -// TRIM -//REPLACE -// RTRIM -// SUBSTR -// TRIM - -// Aggregator for joining strings -// Can be called with options: alasql.aggr.GROUP_CONCAT(v, s, stage, separator, orderDirection) -alasql.aggr.group_concat = alasql.aggr.GROUP_CONCAT = function ( - v, - s, - stage, - separator, - orderDirection -) { - // Default separator is comma - if (separator === undefined) { - separator = ','; - } + stdlib.ABS = function (a) { + return 'Math.abs(' + a + ')'; + }; + stdlib.CLONEDEEP = function (a) { + return 'alasql.utils.cloneDeep(' + a + ')'; + }; - if (stage === 1) { - // Initialize: create array to collect values - // Store as object with values array and metadata - if (v === null || v === undefined) { - return {values: [], separator: separator, orderDirection: orderDirection}; - } - return {values: [v], separator: separator, orderDirection: orderDirection}; - } else if (stage === 2) { - // Accumulate: add to values array, skip null values - if (v === null || v === undefined) { - return s; - } - // If accumulator is null/undefined, initialize it - if (s === null || s === undefined) { - return {values: [v], separator: separator, orderDirection: orderDirection}; - } - // Handle both old string format (for backwards compatibility) and new object format - if (typeof s === 'string') { - // Old format - convert to new format - s = {values: s.split(','), separator: ',', orderDirection: undefined}; - } - s.values.push(v); - return s; - } else { - // Stage 3 (or final): sort if needed and join - if (s === null || s === undefined) { - return undefined; - } - // Handle both old string format and new object format - if (typeof s === 'string') { - return s; // Already formatted + stdfn.CONCAT = function () { + return Array.prototype.slice.call(arguments).join(''); + }; + stdlib.EXP = function (a) { + return 'Math.pow(Math.E,' + a + ')'; + }; + + stdlib.IIF = function (a, b, c) { + if (arguments.length === 3) { + return `((${a}) ? (${b}) : (${c}))`; + } else { + throw new Error('Number of arguments of IFF is not equals to 3'); } + }; + stdlib.IFNULL = function (a, b) { + return `((typeof ${a} === "undefined" || ${a} === null) ? ${b} : ${a})`; + }; + stdlib.INSTR = function (s, p) { + return `((${s}).indexOf(${p}) + 1)`; + }; - let values = s.values; + //stdlib.LEN = stdlib.LENGTH = function(s) {return '('+s+'+"").length';}; - // If no values were collected (all nulls), return undefined - if (values.length === 0) { - return undefined; - } + stdlib.LEN = stdlib.LENGTH = function (s) { + return und(s, 'y.length'); + }; + //stdlib.LENGTH = function(s) {return '('+s+').length'}; - // Sort if orderDirection is provided (check for actual undefined, not the string 'undefined') - if (s.orderDirection && s.orderDirection !== undefined) { - let ascending = s.orderDirection === 'ASC'; - values = values.slice().sort((a, b) => { - if (a === b) return 0; - if (a === null || a === undefined) return 1; - if (b === null || b === undefined) return -1; - if (typeof a === 'string' && typeof b === 'string') { - return ascending ? a.localeCompare(b) : b.localeCompare(a); - } - // For numbers and other types - add parentheses for clarity - return ascending ? (a < b ? -1 : 1) : b < a ? -1 : 1; - }); - } + stdlib.LOWER = stdlib.LCASE = function (s) { + return und(s, 'String(y).toLowerCase()'); + }; + //stdlib.LCASE = function(s) {return '('+s+').toLowerCase()';} - return values.join(s.separator); - } -}; + // Returns a character expression after it removes leading blanks. + // see https://docs.microsoft.com/en-us/sql/t-sql/functions/ltrim-transact-sql + stdlib.LTRIM = function (s) { + return und(s, 'y.replace(/^[ ]+/,"")'); + }; -alasql.aggr.median = alasql.aggr.MEDIAN = function (v, s, stage) { - if (stage === 2) { - if (v !== null) { - s.push(v); - } - return s; - } + // Returns a character string after truncating all trailing spaces. + // see https://docs.microsoft.com/en-us/sql/t-sql/functions/rtrim-transact-sql + stdlib.RTRIM = function (s) { + return und(s, 'y.replace(/[ ]+$/,"")'); + }; + + stdlib.MAX = stdlib.GREATEST = function () { + return ( + '[' + + Array.prototype.join.call(arguments, ',') + + '].reduce(function (a, b) { return a > b ? a : b; })' + ); + }; - if (stage === 1) { - if (v === null) { - return []; + stdlib.MIN = stdlib.LEAST = function () { + return ( + '[' + + Array.prototype.join.call(arguments, ',') + + '].reduce(function (a, b) { return a < b ? a : b; })' + ); + }; + + 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 + ')'); + }; + + stdfn.REGEXP_LIKE = function (a, b, c) { + // console.log(a,b,c); + // Convert MySQL word boundaries to JavaScript word boundaries + // [[:<:]] -> \b (start of word) + // [[:>:]] -> \b (end of word) + var pattern = b.replace(/\[\[:<:\]\]/g, '\\b').replace(/\[\[:>:\]\]/g, '\\b'); + return (a || '').search(RegExp(pattern, c)) > -1; + }; + + // Here we uses undefined instead of null + stdlib.ISNULL = stdlib.NULLIF = function (a, b) { + return '(' + a + '==' + b + '?undefined:' + a + ')'; + }; + + stdlib.POWER = function (a, b) { + return 'Math.pow(' + a + ',' + b + ')'; + }; + + stdlib.RANDOM = function (r) { + if (arguments.length == 0) { + return 'Math.random()'; + } else { + return '(Math.random()*(' + r + ')|0)'; } - return [v]; - } + }; + stdfn.ROUND = function (s, d) { + if (s == null || (typeof s === 'string' && s.trim() === '')) return undefined; + s = Number(s); + if (isNaN(s)) return undefined; + if (arguments.length < 2) return Math.round(s); + return Math.round(s * Math.pow(10, d)) / Math.pow(10, d); + }; + stdlib.CEIL = stdlib.CEILING = function (s) { + return 'Math.ceil(' + s + ')'; + }; + stdlib.FLOOR = function (s) { + return 'Math.floor(' + s + ')'; + }; - if (!s.length) { - return undefined; - } + stdlib.ROWNUM = function () { + return '1'; + }; + stdlib.ROW_NUMBER = function () { + return '1'; + }; + stdlib.GROUP_ROW_NUMBER = function () { + return '1'; + }; - let r = s.sort((a, b) => { - if (a > b) return 1; - if (a < b) return -1; - return 0; - }); + stdlib.SQRT = function (s) { + return 'Math.sqrt(' + s + ')'; + }; - let middle = (r.length + 1) / 2; - let middleFloor = middle | 0; - let el = r[middleFloor - 1]; + stdlib.TRIM = function (s) { + return und(s, 'y.trim()'); + }; - if (middle === middleFloor || (typeof el !== 'number' && !(el instanceof Number))) { - return el; - } else { - return (el + r[middleFloor]) / 2; - } -}; + stdlib.UPPER = stdlib.UCASE = function (s) { + return und(s, 'String(y).toUpperCase()'); + }; -alasql.aggr.mode = alasql.aggr.MODE = function (v, s, stage) { - if (stage === 1) { - if (v == null) { - return {counts: new Map(), maxCount: 0}; + // Concatination of strings + stdfn.CONCAT_WS = function () { + var args = Array.prototype.slice.call(arguments); + args = args.filter(x => !(x === null || typeof x === 'undefined')); + return args.slice(1, args.length).join(args[0] || ''); + }; + + //stdlib.UCASE = function(s) {return '('+s+').toUpperCase()';} + //REPLACE + // RTRIM + // SUBSTR + // TRIM + //REPLACE + // RTRIM + // SUBSTR + // TRIM + + // Aggregator for joining strings + // Can be called with options: alasql.aggr.GROUP_CONCAT(v, s, stage, separator, orderDirection) + alasql.aggr.group_concat = alasql.aggr.GROUP_CONCAT = function ( + v, + s, + stage, + separator, + orderDirection + ) { + // Default separator is comma + if (separator === undefined) { + separator = ','; } - return {counts: new Map([[v, 1]]), maxCount: 1}; - } - if (stage === 2) { - if (!s || !s.counts) { - s = {counts: new Map(), maxCount: 0}; + if (stage === 1) { + // Initialize: create array to collect values + // Store as object with values array and metadata + if (v === null || v === undefined) { + return {values: [], separator: separator, orderDirection: orderDirection}; + } + return {values: [v], separator: separator, orderDirection: orderDirection}; + } else if (stage === 2) { + // Accumulate: add to values array, skip null values + if (v === null || v === undefined) { + return s; + } + // If accumulator is null/undefined, initialize it + if (s === null || s === undefined) { + return {values: [v], separator: separator, orderDirection: orderDirection}; + } + // Handle both old string format (for backwards compatibility) and new object format + if (typeof s === 'string') { + // Old format - convert to new format + s = {values: s.split(','), separator: ',', orderDirection: undefined}; + } + s.values.push(v); + return s; + } else { + // Stage 3 (or final): sort if needed and join + if (s === null || s === undefined) { + return undefined; + } + // Handle both old string format and new object format + if (typeof s === 'string') { + return s; // Already formatted + } + + let values = s.values; + + // If no values were collected (all nulls), return undefined + if (values.length === 0) { + return undefined; + } + + // Sort if orderDirection is provided (check for actual undefined, not the string 'undefined') + if (s.orderDirection && s.orderDirection !== undefined) { + let ascending = s.orderDirection === 'ASC'; + values = values.slice().sort((a, b) => { + if (a === b) return 0; + if (a === null || a === undefined) return 1; + if (b === null || b === undefined) return -1; + if (typeof a === 'string' && typeof b === 'string') { + return ascending ? a.localeCompare(b) : b.localeCompare(a); + } + // For numbers and other types - add parentheses for clarity + return ascending ? (a < b ? -1 : 1) : b < a ? -1 : 1; + }); + } + + return values.join(s.separator); } - if (v == null) { + }; + + alasql.aggr.median = alasql.aggr.MEDIAN = function (v, s, stage) { + if (stage === 2) { + if (v !== null) { + s.push(v); + } return s; } - const count = (s.counts.get(v) || 0) + 1; - s.counts.set(v, count); - if (count > s.maxCount) { - s.maxCount = count; + + if (stage === 1) { + if (v === null) { + return []; + } + return [v]; } - return s; - } - if (stage === 3) { - if (!s || s.maxCount === 0) { + if (!s.length) { return undefined; } - let result; - let hasResult = false; + let r = s.sort((a, b) => { + if (a > b) return 1; + if (a < b) return -1; + return 0; + }); + + let middle = (r.length + 1) / 2; + let middleFloor = middle | 0; + let el = r[middleFloor - 1]; + + if (middle === middleFloor || (typeof el !== 'number' && !(el instanceof Number))) { + return el; + } else { + return (el + r[middleFloor]) / 2; + } + }; - for (const [value, count] of s.counts) { - if (count !== s.maxCount) { - continue; + alasql.aggr.mode = alasql.aggr.MODE = function (v, s, stage) { + if (stage === 1) { + if (v == null) { + return {counts: new Map(), maxCount: 0}; } - if (!hasResult || value < result) { - result = value; - hasResult = true; + return {counts: new Map([[v, 1]]), maxCount: 1}; + } + + if (stage === 2) { + if (!s || !s.counts) { + s = {counts: new Map(), maxCount: 0}; + } + if (v == null) { + return s; } + const count = (s.counts.get(v) || 0) + 1; + s.counts.set(v, count); + if (count > s.maxCount) { + s.maxCount = count; + } + return s; } - return result; - } + if (stage === 3) { + if (!s || s.maxCount === 0) { + return undefined; + } + + let result; + let hasResult = false; - return undefined; -}; + for (const [value, count] of s.counts) { + if (count !== s.maxCount) { + continue; + } + if (!hasResult || value < result) { + result = value; + hasResult = true; + } + } -alasql.aggr.QUART = function (v, s, stage, nth) { - //Quartile (first quartile per default or input param) - if (stage === 2) { - if (v !== null) { - s.push(v); + return result; } - return s; - } - if (stage === 1) { - if (v === null) { - return []; + return undefined; + }; + + alasql.aggr.QUART = function (v, s, stage, nth) { + //Quartile (first quartile per default or input param) + if (stage === 2) { + if (v !== null) { + s.push(v); + } + return s; } - return [v]; - } - if (!s.length) { - return s; - } - nth = !nth ? 1 : nth; - var r = s.sort(function (a, b) { - if (a === b) return 0; + if (stage === 1) { + if (v === null) { + return []; + } + return [v]; + } + if (!s.length) { + return s; + } - if (a > b) return 1; + nth = !nth ? 1 : nth; + var r = s.sort(function (a, b) { + if (a === b) return 0; - return -1; - }); + if (a > b) return 1; - let p = (nth * (r.length + 1)) / 4; + return -1; + }); - if (Number.isInteger(p)) { - return r[p - 1]; //Integer value - } + let p = (nth * (r.length + 1)) / 4; - return r[Math.floor(p)]; //Math.ceil -1 or Math.floor -}; - -alasql.aggr.QUART2 = function (v, s, stage) { - //Second Quartile - return alasql.aggr.QUART(v, s, stage, 2); -}; -alasql.aggr.QUART3 = function (v, s, stage) { - //Third Quartile - return alasql.aggr.QUART(v, s, stage, 3); -}; - -// Standard deviation -alasql.aggr.VAR = function (v, s, stage) { - if (stage === 1) { - // Initialise sum, sum of squares, and count - return v === null ? {sum: 0, sumSq: 0, count: 0} : {sum: v, sumSq: v * v, count: 1}; - } else if (stage === 2) { - // Update sum, sum of squares, and count - if (v !== null) { - s.sum += v; - s.sumSq += v * v; - s.count++; + if (Number.isInteger(p)) { + return r[p - 1]; //Integer value } - return s; - } else { - // Calculate variance using the formula: variance = (sumSq - (sum^2 / count)) / (count - 1) - // This avoids the need to store and iterate over all values - if (s.count > 1) { - return (s.sumSq - (s.sum * s.sum) / s.count) / (s.count - 1); + + return r[Math.floor(p)]; //Math.ceil -1 or Math.floor + }; + + alasql.aggr.QUART2 = function (v, s, stage) { + //Second Quartile + return alasql.aggr.QUART(v, s, stage, 2); + }; + alasql.aggr.QUART3 = function (v, s, stage) { + //Third Quartile + return alasql.aggr.QUART(v, s, stage, 3); + }; + + // Standard deviation + alasql.aggr.VAR = function (v, s, stage) { + if (stage === 1) { + // Initialise sum, sum of squares, and count + return v === null ? {sum: 0, sumSq: 0, count: 0} : {sum: v, sumSq: v * v, count: 1}; + } else if (stage === 2) { + // Update sum, sum of squares, and count + if (v !== null) { + s.sum += v; + s.sumSq += v * v; + s.count++; + } + return s; } else { - // Handling for cases with less than 2 values (variance is undefined or zero) - return 0; + // Calculate variance using the formula: variance = (sumSq - (sum^2 / count)) / (count - 1) + // This avoids the need to store and iterate over all values + if (s.count > 1) { + return (s.sumSq - (s.sum * s.sum) / s.count) / (s.count - 1); + } else { + // Handling for cases with less than 2 values (variance is undefined or zero) + return 0; + } } - } -}; + }; -alasql.aggr.STDEV = function (v, s, stage) { - if (stage === 1 || stage === 2) { - return alasql.aggr.VAR(v, s, stage); - } else { - return Math.sqrt(alasql.aggr.VAR(v, s, stage)); - } -}; + alasql.aggr.STDEV = function (v, s, stage) { + if (stage === 1 || stage === 2) { + return alasql.aggr.VAR(v, s, stage); + } else { + return Math.sqrt(alasql.aggr.VAR(v, s, stage)); + } + }; -alasql.aggr.STDEV = function (v, s, stage) { - if (stage === 1 || stage === 2) { - return alasql.aggr.VAR(v, s, stage); - } else { - return Math.sqrt(alasql.aggr.VAR(v, s, stage)); - } -}; - -alasql.aggr.VARP = function (value, accumulator, stage) { - if (stage === 1) { - // Initialise accumulator with count, sum, and sum of squares - return {count: 1, sum: value, sumSq: value * value}; - } else if (stage === 2) { - // Update accumulator - accumulator.count++; - accumulator.sum += value; - accumulator.sumSq += value * value; - return accumulator; - } else { - // Final stage: Calculate variance - if (accumulator.count > 0) { - const mean = accumulator.sum / accumulator.count; - const variance = accumulator.sumSq / accumulator.count - mean * mean; - return variance; + alasql.aggr.STDEV = function (v, s, stage) { + if (stage === 1 || stage === 2) { + return alasql.aggr.VAR(v, s, stage); } else { - return 0; // Return 0 variance if no values were aggregated + return Math.sqrt(alasql.aggr.VAR(v, s, stage)); } - } -}; - -alasql.aggr.STD = - alasql.aggr.STDDEV = - alasql.aggr.STDEVP = - function (v, s, stage) { - if (stage == 1 || stage == 2) { - return alasql.aggr.VARP(v, s, stage); + }; + + alasql.aggr.VARP = function (value, accumulator, stage) { + if (stage === 1) { + // Initialise accumulator with count, sum, and sum of squares + return {count: 1, sum: value, sumSq: value * value}; + } else if (stage === 2) { + // Update accumulator + accumulator.count++; + accumulator.sum += value; + accumulator.sumSq += value * value; + return accumulator; + } else { + // Final stage: Calculate variance + if (accumulator.count > 0) { + const mean = accumulator.sum / accumulator.count; + const variance = accumulator.sumSq / accumulator.count - mean * mean; + return variance; } else { - return Math.sqrt(alasql.aggr.VARP(v, s, stage)); + return 0; // Return 0 variance if no values were aggregated } + } + }; + + alasql.aggr.STD = + alasql.aggr.STDDEV = + alasql.aggr.STDEVP = + function (v, s, stage) { + if (stage == 1 || stage == 2) { + return alasql.aggr.VARP(v, s, stage); + } else { + return Math.sqrt(alasql.aggr.VARP(v, s, stage)); + } + }; + + alasql._aggrOriginal = alasql.aggr; + alasql.aggr = {}; + Object.keys(alasql._aggrOriginal).forEach(function (k) { + alasql.aggr[k] = function (v, s, stage) { + if (stage === 3 && typeof s === 'undefined') return undefined; + return alasql._aggrOriginal[k].apply(null, arguments); }; + }); -alasql._aggrOriginal = alasql.aggr; -alasql.aggr = {}; -Object.keys(alasql._aggrOriginal).forEach(function (k) { - alasql.aggr[k] = function (v, s, stage) { - if (stage === 3 && typeof s === 'undefined') return undefined; - return alasql._aggrOriginal[k].apply(null, arguments); - }; -}); - -// String functions -stdfn.REPLACE = function (target, pattern, replacement) { - return String(target ?? '') - .split(String(pattern ?? '')) - .join(String(replacement ?? '')); -}; - -// This array is required for fast GUID generation -var lut = []; -for (var i = 0; i < 256; i++) { - lut[i] = (i < 16 ? '0' : '') + i.toString(16); -} + // String functions + stdfn.REPLACE = function (target, pattern, replacement) { + return String(target ?? '') + .split(String(pattern ?? '')) + .join(String(replacement ?? '')); + }; -stdfn.NEWID = - stdfn.UUID = - stdfn.GEN_RANDOM_UUID = - function () { - var d0 = (Math.random() * 0xffffffff) | 0; - var d1 = (Math.random() * 0xffffffff) | 0; - var d2 = (Math.random() * 0xffffffff) | 0; - var d3 = (Math.random() * 0xffffffff) | 0; - return ( - lut[d0 & 0xff] + - lut[(d0 >> 8) & 0xff] + - lut[(d0 >> 16) & 0xff] + - lut[(d0 >> 24) & 0xff] + - '-' + - lut[d1 & 0xff] + - lut[(d1 >> 8) & 0xff] + - '-' + - lut[((d1 >> 16) & 0x0f) | 0x40] + - lut[(d1 >> 24) & 0xff] + - '-' + - lut[(d2 & 0x3f) | 0x80] + - lut[(d2 >> 8) & 0xff] + - '-' + - lut[(d2 >> 16) & 0xff] + - lut[(d2 >> 24) & 0xff] + - lut[d3 & 0xff] + - lut[(d3 >> 8) & 0xff] + - lut[(d3 >> 16) & 0xff] + - lut[(d3 >> 24) & 0xff] - ); - }; + // This array is required for fast GUID generation + var lut = []; + for (var i = 0; i < 256; i++) { + lut[i] = (i < 16 ? '0' : '') + i.toString(16); + } + + stdfn.NEWID = + stdfn.UUID = + stdfn.GEN_RANDOM_UUID = + function () { + var d0 = (Math.random() * 0xffffffff) | 0; + var d1 = (Math.random() * 0xffffffff) | 0; + var d2 = (Math.random() * 0xffffffff) | 0; + var d3 = (Math.random() * 0xffffffff) | 0; + return ( + lut[d0 & 0xff] + + lut[(d0 >> 8) & 0xff] + + lut[(d0 >> 16) & 0xff] + + lut[(d0 >> 24) & 0xff] + + '-' + + lut[d1 & 0xff] + + lut[(d1 >> 8) & 0xff] + + '-' + + lut[((d1 >> 16) & 0x0f) | 0x40] + + lut[(d1 >> 24) & 0xff] + + '-' + + lut[(d2 & 0x3f) | 0x80] + + lut[(d2 >> 8) & 0xff] + + '-' + + lut[(d2 >> 16) & 0xff] + + lut[(d2 >> 24) & 0xff] + + lut[d3 & 0xff] + + lut[(d3 >> 8) & 0xff] + + lut[(d3 >> 16) & 0xff] + + lut[(d3 >> 24) & 0xff] + ); + }; } diff --git a/src/graph/create.js b/src/graph/create.js index a01842809a..f8cb9dbd3f 100755 --- a/src/graph/create.js +++ b/src/graph/create.js @@ -2,7 +2,7 @@ import {extend} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // CREATE VERTEX for AlaSQL // Date: 21.04.2015 @@ -10,368 +10,371 @@ export default function install(alasql) { // */ -yy.CreateVertex = function (params) { - return Object.assign(this, params); -}; -yy.CreateVertex.prototype.toString = function () { - var s = 'CREATE VERTEX '; - if (this.class) { - s += this.class + ' '; - } - if (this.sharp) { - s += '#' + this.sharp + ' '; - } - if (this.sets) { - s += this.sets.toString(); - } else if (this.content) { - s += this.content.toString(); - } else if (this.select) { - s += this.select.toString(); - } - - return s; -}; - -yy.CreateVertex.prototype.toJS = function (context) { - // console.log('yy.CreateVertex.toJS'); - var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; - // var s = ''; - return s; -}; - -// CREATE TABLE - -yy.CreateVertex.prototype.compile = function (databaseid) { - var dbid = databaseid; - - // CREATE VERTEX #id - var sharp = this.sharp; - - // CREATE VERTEX "Name" - if (typeof this.name !== 'undefined') { - var s = 'x.name=' + this.name.toJS(); - var namefn = new Function('x', s); - } - - if (this.sets && this.sets.length > 0) { - var s = this.sets - .map(function (st) { - return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); - }) - .join(';'); - var setfn = new Function('x,params,alasql', s); - } - - // Todo: check for content, select and default - - var statement = function (params, cb) { - var res; - - // CREATE VERTEX without parameters - var db = alasql.databases[dbid]; - var id; - if (typeof sharp !== 'undefined') { - id = sharp; - } else { - id = db.counter++; + yy.CreateVertex = function (params) { + return Object.assign(this, params); + }; + yy.CreateVertex.prototype.toString = function () { + var s = 'CREATE VERTEX '; + if (this.class) { + s += this.class + ' '; } - var vertex = {$id: id, $node: 'VERTEX'}; - db.objects[vertex.$id] = vertex; - res = vertex; - if (namefn) { - namefn(vertex); + if (this.sharp) { + s += '#' + this.sharp + ' '; } - if (setfn) { - setfn(vertex, params, alasql); + if (this.sets) { + s += this.sets.toString(); + } else if (this.content) { + s += this.content.toString(); + } else if (this.select) { + s += this.select.toString(); } - if (cb) { - res = cb(res); - } - return res; + return s; }; - return statement; -}; - -yy.CreateEdge = function (params) { - return Object.assign(this, params); -}; -yy.CreateEdge.prototype.toString = function () { - // console.log('here!'); - var s = 'CREATE EDGE' + ' '; - if (this.class) { - s += this.class + ' '; - } - // todo: SET - // todo: CONTENT - // todo: SELECT - return s; -}; - -yy.CreateEdge.prototype.toJS = function (context) { - var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; - return s; -}; - -// CREATE TABLE - -yy.CreateEdge.prototype.compile = function (databaseid) { - var dbid = databaseid; - var fromfn = new Function('params,alasql', 'var y;return ' + this.from.toJS()); - var tofn = new Function('params,alasql', 'var y;return ' + this.to.toJS()); - - // CREATE VERTEX "Name" - if (typeof this.name !== 'undefined') { - var s = 'x.name=' + this.name.toJS(); - var namefn = new Function('x', s); - } - - if (this.sets && this.sets.length > 0) { - var s = this.sets - .map(function (st) { - return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); - }) - .join(';'); - var setfn = new Function('x,params,alasql', 'var y;' + s); - } - - const statement = (params, cb) => { - let res = 0; - let db = alasql.databases[dbid]; - let edge = {$id: db.counter++, $node: 'EDGE'}; - let v1 = fromfn(params, alasql); - let v2 = tofn(params, alasql); - - // Set link - edge.$in = [v1.$id]; - edge.$out = [v2.$id]; - - // Initialize and set sides - v1.$out = v1.$out || []; - v1.$out.push(edge.$id); - - v2.$in = v2.$in || []; - v2.$in.push(edge.$id); - - // Save in objects - db.objects[edge.$id] = edge; - res = edge; - - // Optional functions - namefn?.(edge); - setfn?.(edge, params, alasql); - - // Callback - return cb ? cb(res) : res; + + yy.CreateVertex.prototype.toJS = function (context) { + // console.log('yy.CreateVertex.toJS'); + var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; + // var s = ''; + return s; }; - return statement; -}; - -yy.CreateGraph = function (params) { - return Object.assign(this, params); -}; -yy.CreateGraph.prototype.toString = function () { - var s = 'CREATE GRAPH' + ' '; - if (this.class) { - s += this.class + ' '; - } - return s; -}; - -yy.CreateGraph.prototype.execute = function (databaseid, params, cb) { - var res = []; - if (this.from) { - if (alasql.from[this.from.funcid]) { - this.graph = alasql.from[this.from.funcid.toUpperCase()]; + + // CREATE TABLE + + yy.CreateVertex.prototype.compile = function (databaseid) { + var dbid = databaseid; + + // CREATE VERTEX #id + var sharp = this.sharp; + + // CREATE VERTEX "Name" + if (typeof this.name !== 'undefined') { + var s = 'x.name=' + this.name.toJS(); + var namefn = new Function('x', s); + } + + if (this.sets && this.sets.length > 0) { + var s = this.sets + .map(function (st) { + return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); + }) + .join(';'); + var setfn = new Function('x,params,alasql', s); } - } - - // stop; - this.graph.forEach(g => { - if (!g.source) { - createVertex(g); - } else { - // CREATE EDGE - let e = {}; - if (g.as !== undefined) alasql.vars[g.as] = e; - if (g.prop !== undefined) e.name = g.prop; - if (g.sharp !== undefined) e.$id = g.sharp; - if (g.name !== undefined) e.name = g.name; - if (g.class !== undefined) e.$class = g.class; - - let db = alasql.databases[databaseid]; - e.$id = e.$id !== undefined ? e.$id : db.counter++; - e.$node = 'EDGE'; - - if (g.json !== undefined) { - Object.assign(e, new Function('params, alasql', `return ${g.json.toJS()}`)(params, alasql)); + + // Todo: check for content, select and default + + var statement = function (params, cb) { + var res; + + // CREATE VERTEX without parameters + var db = alasql.databases[dbid]; + var id; + if (typeof sharp !== 'undefined') { + id = sharp; + } else { + id = db.counter++; + } + var vertex = {$id: id, $node: 'VERTEX'}; + db.objects[vertex.$id] = vertex; + res = vertex; + if (namefn) { + namefn(vertex); + } + if (setfn) { + setfn(vertex, params, alasql); } - const resolveVertex = (sourceOrTarget, isSource) => { - let vertex, vo; - if (sourceOrTarget.vars) { - vo = alasql.vars[sourceOrTarget.vars]; - vertex = typeof vo === 'object' ? vo : db.objects[vo]; - } else { - let av = sourceOrTarget.sharp || sourceOrTarget.prop; - vertex = db.objects[av]; - if ( - vertex === undefined && - alasql.options.autovertex && - (sourceOrTarget.prop || sourceOrTarget.name) - ) { - vertex = - findVertex(sourceOrTarget.prop || sourceOrTarget.name) || - createVertex(sourceOrTarget); - } - } - if (isSource && vertex && typeof vertex.$out === 'undefined') vertex.$out = []; - if (!isSource && vertex && typeof vertex.$in === 'undefined') vertex.$in = []; - return vertex; - }; - - let v1 = resolveVertex(g.source, true); - let v2 = resolveVertex(g.target, false); - - // Set link and sides - e.$in = [v1.$id]; - e.$out = [v2.$id]; - v1.$out.push(e.$id); - v2.$in.push(e.$id); - - db.objects[e.$id] = e; - - if (e.$class !== undefined) { - let classTable = alasql.databases[databaseid].tables[e.$class]; - if (classTable === undefined) { - throw new Error('No such class. Please use CREATE CLASS'); - } else { - classTable.data.push(e); - } + if (cb) { + res = cb(res); } + return res; + }; + return statement; + }; - res.push(e.$id); + yy.CreateEdge = function (params) { + return Object.assign(this, params); + }; + yy.CreateEdge.prototype.toString = function () { + // console.log('here!'); + var s = 'CREATE EDGE' + ' '; + if (this.class) { + s += this.class + ' '; } - }); + // todo: SET + // todo: CONTENT + // todo: SELECT + return s; + }; - if (cb) { - res = cb(res); - } + yy.CreateEdge.prototype.toJS = function (context) { + var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; + return s; + }; - return res; + // CREATE TABLE - // Find vertex by name - function findVertex(name) { - var objects = alasql.databases[alasql.useid].objects; - for (var k in objects) { - if (objects[k].name === name) { - return objects[k]; - } - } - return undefined; - } - - function createVertex(g) { - // GREATE VERTEX - var v = {}; - if (typeof g.as !== 'undefined') { - alasql.vars[g.as] = v; - } + yy.CreateEdge.prototype.compile = function (databaseid) { + var dbid = databaseid; + var fromfn = new Function('params,alasql', 'var y;return ' + this.from.toJS()); + var tofn = new Function('params,alasql', 'var y;return ' + this.to.toJS()); - if (typeof g.prop !== 'undefined') { - // v[g.prop] = true; - v.$id = g.prop; - v.name = g.prop; + // CREATE VERTEX "Name" + if (typeof this.name !== 'undefined') { + var s = 'x.name=' + this.name.toJS(); + var namefn = new Function('x', s); } - if (typeof g.sharp !== 'undefined') { - v.$id = g.sharp; - } - if (typeof g.name !== 'undefined') { - v.name = g.name; - } - if (typeof g.class !== 'undefined') { - v.$class = g.class; + if (this.sets && this.sets.length > 0) { + var s = this.sets + .map(function (st) { + return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); + }) + .join(';'); + var setfn = new Function('x,params,alasql', 'var y;' + s); } - var db = alasql.databases[databaseid]; - if (typeof v.$id === 'undefined') { - v.$id = db.counter++; + const statement = (params, cb) => { + let res = 0; + let db = alasql.databases[dbid]; + let edge = {$id: db.counter++, $node: 'EDGE'}; + let v1 = fromfn(params, alasql); + let v2 = tofn(params, alasql); + + // Set link + edge.$in = [v1.$id]; + edge.$out = [v2.$id]; + + // Initialize and set sides + v1.$out = v1.$out || []; + v1.$out.push(edge.$id); + + v2.$in = v2.$in || []; + v2.$in.push(edge.$id); + + // Save in objects + db.objects[edge.$id] = edge; + res = edge; + + // Optional functions + namefn?.(edge); + setfn?.(edge, params, alasql); + + // Callback + return cb ? cb(res) : res; + }; + return statement; + }; + + yy.CreateGraph = function (params) { + return Object.assign(this, params); + }; + yy.CreateGraph.prototype.toString = function () { + var s = 'CREATE GRAPH' + ' '; + if (this.class) { + s += this.class + ' '; } - v.$node = 'VERTEX'; - if (typeof g.json !== 'undefined') { - extend(v, new Function('params,alasql', 'var y;return ' + g.json.toJS())(params, alasql)); + return s; + }; + + yy.CreateGraph.prototype.execute = function (databaseid, params, cb) { + var res = []; + if (this.from) { + if (alasql.from[this.from.funcid]) { + this.graph = alasql.from[this.from.funcid.toUpperCase()]; + } } - db.objects[v.$id] = v; - if (typeof v.$class !== 'undefined') { - if (typeof alasql.databases[databaseid].tables[v.$class] === 'undefined') { - throw new Error('No such class. Pleace use CREATE CLASS'); + + // stop; + this.graph.forEach(g => { + if (!g.source) { + createVertex(g); } else { - // TODO - add insert() - alasql.databases[databaseid].tables[v.$class].data.push(v); + // CREATE EDGE + let e = {}; + if (g.as !== undefined) alasql.vars[g.as] = e; + if (g.prop !== undefined) e.name = g.prop; + if (g.sharp !== undefined) e.$id = g.sharp; + if (g.name !== undefined) e.name = g.name; + if (g.class !== undefined) e.$class = g.class; + + let db = alasql.databases[databaseid]; + e.$id = e.$id !== undefined ? e.$id : db.counter++; + e.$node = 'EDGE'; + + if (g.json !== undefined) { + Object.assign( + e, + new Function('params, alasql', `return ${g.json.toJS()}`)(params, alasql) + ); + } + + const resolveVertex = (sourceOrTarget, isSource) => { + let vertex, vo; + if (sourceOrTarget.vars) { + vo = alasql.vars[sourceOrTarget.vars]; + vertex = typeof vo === 'object' ? vo : db.objects[vo]; + } else { + let av = sourceOrTarget.sharp || sourceOrTarget.prop; + vertex = db.objects[av]; + if ( + vertex === undefined && + alasql.options.autovertex && + (sourceOrTarget.prop || sourceOrTarget.name) + ) { + vertex = + findVertex(sourceOrTarget.prop || sourceOrTarget.name) || + createVertex(sourceOrTarget); + } + } + if (isSource && vertex && typeof vertex.$out === 'undefined') vertex.$out = []; + if (!isSource && vertex && typeof vertex.$in === 'undefined') vertex.$in = []; + return vertex; + }; + + let v1 = resolveVertex(g.source, true); + let v2 = resolveVertex(g.target, false); + + // Set link and sides + e.$in = [v1.$id]; + e.$out = [v2.$id]; + v1.$out.push(e.$id); + v2.$in.push(e.$id); + + db.objects[e.$id] = e; + + if (e.$class !== undefined) { + let classTable = alasql.databases[databaseid].tables[e.$class]; + if (classTable === undefined) { + throw new Error('No such class. Please use CREATE CLASS'); + } else { + classTable.data.push(e); + } + } + + res.push(e.$id); } + }); + + if (cb) { + res = cb(res); } - res.push(v.$id); - return v; - } -}; -yy.CreateGraph.prototype.compile1 = function (databaseid) { - const dbid = databaseid; - const fromfn = new Function('params, alasql', `return ${this.from.toJS()}`); - const tofn = new Function('params, alasql', `return ${this.to.toJS()}`); - - let namefn, setfn; - - // CREATE VERTEX "Name" - if (this.name !== undefined) { - const s = `x.name = ${this.name.toJS()}`; - namefn = new Function('x', s); - } - - if (this.sets && this.sets.length > 0) { - const s = this.sets - .map(st => `x[${JSON.stringify(st.column.columnid)}] = ${st.expression.toJS('x', '')}`) - .join(';'); - setfn = new Function('x, params, alasql', `var y; ${s}`); - } - - // Todo: handle content, select and default - - const statement = (params, cb) => { - let res = 0; - const db = alasql.databases[dbid]; - const edge = {$id: db.counter++, $node: 'EDGE'}; - const v1 = fromfn(params, alasql); - const v2 = tofn(params, alasql); - - // Set link - edge.$in = [v1.$id]; - edge.$out = [v2.$id]; - - // Set sides - v1.$out = v1.$out || []; - v1.$out.push(edge.$id); - - v2.$in = v2.$in || []; - v2.$in.push(edge.$id); - - // Save in objects - db.objects[edge.$id] = edge; - res = edge; - - if (namefn) { - namefn(edge); + return res; + + // Find vertex by name + function findVertex(name) { + var objects = alasql.databases[alasql.useid].objects; + for (var k in objects) { + if (objects[k].name === name) { + return objects[k]; + } + } + return undefined; } - if (setfn) { - setfn(edge, params, alasql); + + function createVertex(g) { + // GREATE VERTEX + var v = {}; + if (typeof g.as !== 'undefined') { + alasql.vars[g.as] = v; + } + + if (typeof g.prop !== 'undefined') { + // v[g.prop] = true; + v.$id = g.prop; + v.name = g.prop; + } + + if (typeof g.sharp !== 'undefined') { + v.$id = g.sharp; + } + if (typeof g.name !== 'undefined') { + v.name = g.name; + } + if (typeof g.class !== 'undefined') { + v.$class = g.class; + } + + var db = alasql.databases[databaseid]; + if (typeof v.$id === 'undefined') { + v.$id = db.counter++; + } + v.$node = 'VERTEX'; + if (typeof g.json !== 'undefined') { + extend(v, new Function('params,alasql', 'var y;return ' + g.json.toJS())(params, alasql)); + } + db.objects[v.$id] = v; + if (typeof v.$class !== 'undefined') { + if (typeof alasql.databases[databaseid].tables[v.$class] === 'undefined') { + throw new Error('No such class. Pleace use CREATE CLASS'); + } else { + // TODO - add insert() + alasql.databases[databaseid].tables[v.$class].data.push(v); + } + } + + res.push(v.$id); + return v; } + }; + yy.CreateGraph.prototype.compile1 = function (databaseid) { + const dbid = databaseid; + const fromfn = new Function('params, alasql', `return ${this.from.toJS()}`); + const tofn = new Function('params, alasql', `return ${this.to.toJS()}`); - if (cb) { - res = cb(res); + let namefn, setfn; + + // CREATE VERTEX "Name" + if (this.name !== undefined) { + const s = `x.name = ${this.name.toJS()}`; + namefn = new Function('x', s); } - return res; + + if (this.sets && this.sets.length > 0) { + const s = this.sets + .map(st => `x[${JSON.stringify(st.column.columnid)}] = ${st.expression.toJS('x', '')}`) + .join(';'); + setfn = new Function('x, params, alasql', `var y; ${s}`); + } + + // Todo: handle content, select and default + + const statement = (params, cb) => { + let res = 0; + const db = alasql.databases[dbid]; + const edge = {$id: db.counter++, $node: 'EDGE'}; + const v1 = fromfn(params, alasql); + const v2 = tofn(params, alasql); + + // Set link + edge.$in = [v1.$id]; + edge.$out = [v2.$id]; + + // Set sides + v1.$out = v1.$out || []; + v1.$out.push(edge.$id); + + v2.$in = v2.$in || []; + v2.$in.push(edge.$id); + + // Save in objects + db.objects[edge.$id] = edge; + res = edge; + + if (namefn) { + namefn(edge); + } + if (setfn) { + setfn(edge, params, alasql); + } + + if (cb) { + res = cb(res); + } + return res; + }; + return statement; }; - return statement; -}; } diff --git a/src/graph/search.js b/src/graph/search.js index b0256f550f..d8d7b6a461 100755 --- a/src/graph/search.js +++ b/src/graph/search.js @@ -3,9 +3,9 @@ import {cloneDeep, distinctArray} from '../utils/helpers.js'; export default function install(alasql, internal) { var yy = alasql.yy; var modify = internal.modify; -/* global alasql */ -/* global yy */ -/* + /* global alasql */ + /* global yy */ + /* // // SEARCH for Alasql.js // Date: 04.05.2015 @@ -13,54 +13,54 @@ export default function install(alasql, internal) { // */ -/** + /** Search class @example SEARCH SUM(/a) FROM ? -- search over parameter object */ -yy.Search = class Search { - constructor(params) { - Object.assign(this, params); - } + yy.Search = class Search { + constructor(params) { + Object.assign(this, params); + } + + toString() { + let s = 'SEARCH '; + if (this.selectors) s += this.selectors.toString(); + if (this.from) s += 'FROM ' + this.from.toString(); + return s; + } - toString() { - let s = 'SEARCH '; - if (this.selectors) s += this.selectors.toString(); - if (this.from) s += 'FROM ' + this.from.toString(); - return s; - } + toJS(context) { + const s = `this.queriesfn[${this.queriesidx - 1}](this.params,null,${context})`; + return s; + } - toJS(context) { - const s = `this.queriesfn[${this.queriesidx - 1}](this.params,null,${context})`; - return s; - } + compile(databaseid) { + var dbid = databaseid; - compile(databaseid) { - var dbid = databaseid; + var statement = (params, cb) => { + var res; + this.#doSearch(dbid, params, function (data) { + res = modify(statement.query, data); + if (cb) res = cb(res); + }); + // if(cb) res = cb(res); + return res; + }; + statement.query = {}; + return statement; + } - var statement = (params, cb) => { + #doSearch(databaseid, params, cb) { var res; - this.#doSearch(dbid, params, function (data) { - res = modify(statement.query, data); - if (cb) res = cb(res); - }); - // if(cb) res = cb(res); - return res; - }; - statement.query = {}; - return statement; - } - - #doSearch(databaseid, params, cb) { - var res; - var stope = {}; - var fromdata; - var selectors = cloneDeep(this.selectors); - - function processSelector(selectors, sidx, value) { - // var val; - /*/* if(sidx == 0) { + var stope = {}; + var fromdata; + var selectors = cloneDeep(this.selectors); + + function processSelector(selectors, sidx, value) { + // var val; + /*/* if(sidx == 0) { if(selectors.length > 0 && selectors[0].srchid == 'SHARP') { val = alasql.databases[alasql.useid].objects[selectors[0].args[0]]; return processSelector(selectors,sidx+1,val); @@ -79,1079 +79,1082 @@ yy.Search = class Search { } } */ - var val, // temp values use many places - nest, // temp value used many places - r, // temp value used many places - sel = selectors[sidx]; - - var INFINITE_LOOP_BREAK = alasql.options.loopbreak || 100000; - - if (sel.selid) { - // TODO Process Selector - if (sel.selid === 'PATH') { - var queue = [{node: value, stack: []}]; - var visited = {}; - //var path = []; - var objects = alasql.databases[alasql.useid].objects; - while (queue.length > 0) { - var q = queue.shift(); - var node = q.node; - var stack = q.stack; - var r = processSelector(sel.args, 0, node); - if (r.length > 0) { - if (sidx + 1 + 1 > selectors.length) { - return stack; + var val, // temp values use many places + nest, // temp value used many places + r, // temp value used many places + sel = selectors[sidx]; + + var INFINITE_LOOP_BREAK = alasql.options.loopbreak || 100000; + + if (sel.selid) { + // TODO Process Selector + if (sel.selid === 'PATH') { + var queue = [{node: value, stack: []}]; + var visited = {}; + //var path = []; + var objects = alasql.databases[alasql.useid].objects; + while (queue.length > 0) { + var q = queue.shift(); + var node = q.node; + var stack = q.stack; + var r = processSelector(sel.args, 0, node); + if (r.length > 0) { + if (sidx + 1 + 1 > selectors.length) { + return stack; + } else { + var rv = []; + if (stack && stack.length > 0) { + stack.forEach(function (stv) { + rv = rv.concat(processSelector(selectors, sidx + 1, stv)); + }); + } + return rv; + // return processSelector(selectors,sidx+1,stack); + } } else { - var rv = []; - if (stack && stack.length > 0) { - stack.forEach(function (stv) { - rv = rv.concat(processSelector(selectors, sidx + 1, stv)); - }); + if (typeof visited[node.$id] !== 'undefined') { + continue; + } else { + // console.log(node.$id, node.$out); + visited[node.$id] = true; + if (node.$out && node.$out.length > 0) { + node.$out.forEach(function (edgeid) { + var edge = objects[edgeid]; + var stack2 = stack.concat(edge); + stack2.push(objects[edge.$out[0]]); + queue.push({ + node: objects[edge.$out[0]], + stack: stack2, + }); + }); + } } - return rv; - // return processSelector(selectors,sidx+1,stack); } + } + // Else return fail + return []; + } + if (sel.selid === 'NOT') { + var nest = processSelector(sel.args, 0, value); + //console.log(1,nest); + if (nest.length > 0) { + return []; } else { - if (typeof visited[node.$id] !== 'undefined') { - continue; + if (sidx + 1 + 1 > selectors.length) { + return [value]; } else { - // console.log(node.$id, node.$out); - visited[node.$id] = true; - if (node.$out && node.$out.length > 0) { - node.$out.forEach(function (edgeid) { - var edge = objects[edgeid]; - var stack2 = stack.concat(edge); - stack2.push(objects[edge.$out[0]]); - queue.push({ - node: objects[edge.$out[0]], - stack: stack2, - }); + return processSelector(selectors, sidx + 1, value); + } + } + } else if (sel.selid === 'DISTINCT') { + var nest; + if (typeof sel.args === 'undefined' || sel.args.length === 0) { + nest = distinctArray(value); + } else { + nest = processSelector(sel.args, 0, value); + } + if (nest.length === 0) { + return []; + } else { + var res = distinctArray(nest); + if (sidx + 1 + 1 > selectors.length) { + return res; + } else { + return processSelector(selectors, sidx + 1, res); + } + } + } else if (sel.selid === 'AND') { + var res = true; + sel.args.forEach(function (se) { + res = res && processSelector(se, 0, value).length > 0; + }); + if (!res) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return [value]; + } else { + return processSelector(selectors, sidx + 1, value); + } + } + } else if (sel.selid === 'OR') { + var res = false; + sel.args.forEach(function (se) { + res = res || processSelector(se, 0, value).length > 0; + }); + if (!res) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return [value]; + } else { + return processSelector(selectors, sidx + 1, value); + } + } + } else if (sel.selid === 'ALL') { + var nest = processSelector(sel.args[0], 0, value); + if (nest.length === 0) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return nest; + } else { + return processSelector(selectors, sidx + 1, nest); + } + } + } else if (sel.selid === 'ANY') { + var nest = processSelector(sel.args[0], 0, value); + if (nest.length === 0) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return [nest[0]]; + } else { + return processSelector(selectors, sidx + 1, [nest[0]]); + } + } + } else if (sel.selid === 'UNIONALL') { + var nest = []; + sel.args.forEach(function (se) { + nest = nest.concat(processSelector(se, 0, value)); + }); + if (nest.length === 0) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return nest; + } else { + return processSelector(selectors, sidx + 1, nest); + } + } + } else if (sel.selid === 'UNION') { + var nest = []; + sel.args.forEach(function (se) { + nest = nest.concat(processSelector(se, 0, value)); + }); + var nest = distinctArray(nest); + if (nest.length === 0) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return nest; + } else { + return processSelector(selectors, sidx + 1, nest); + } + } + } else if (sel.selid === 'IF') { + var nest = processSelector(sel.args, 0, value); + //console.log(1,nest); + if (nest.length === 0) { + return []; + } else { + if (sidx + 1 + 1 > selectors.length) { + return [value]; + } else { + return processSelector(selectors, sidx + 1, value); + } + } + } else if (sel.selid === 'REPEAT') { + // console.log(352,sel.sels); + var lvar, + lmax, + lmin = sel.args[0].value; + if (!sel.args[1]) { + lmax = lmin; // Add security break + } else { + lmax = sel.args[1].value; + } + if (sel.args[2]) { + lvar = sel.args[2].variable; + } + //var lsel = sel.sels; + // console.log(351,lmin,lmax,lvar); + + var retval = []; + + if (lmin === 0) { + if (sidx + 1 + 1 > selectors.length) { + retval = [value]; + } else { + if (lvar) { + alasql.vars[lvar] = 0; + } + retval = retval.concat(processSelector(selectors, sidx + 1, value)); + } + } + + // console.log(364,retval); + //console.log(370,sel.sels); + // var nests = processSelector(sel.sels,0,value).slice(); + if (lmax > 0) { + var nests = [{value: value, lvl: 1}]; + /*/* + // if(lvl >= lmin) { + // if(sidx+1+1 > selectors.length) { + // retval = retval.concat(nests); + // } else { + // retval = retval.concat(processSelector(selectors,sidx+1,value)); + // } + // } + */ + //console.log(371,nests); + var i = 0; + while (nests.length > 0) { + var nest = nests[0]; + //console.log(375,nest); + nests.shift(); + if (nest.lvl <= lmax) { + if (lvar) { + alasql.vars[lvar] = nest.lvl; + } + // console.log(394,sel.sels); + var nest1 = processSelector(sel.sels, 0, nest.value); + // console.log(397,nest1); + + nest1.forEach(function (n) { + nests.push({value: n, lvl: nest.lvl + 1}); }); + if (nest.lvl >= lmin) { + if (sidx + 1 + 1 > selectors.length) { + retval = retval.concat(nest1); + //return nests; + } else { + nest1.forEach(function (n) { + retval = retval.concat(processSelector(selectors, sidx + 1, n)); + }); + } + } + } + // Security brake + i++; + if (i > INFINITE_LOOP_BREAK) { + throw new Error('Infinite loop brake. Number of iterations = ' + i); } } } - } - // Else return fail - return []; - } - if (sel.selid === 'NOT') { - var nest = processSelector(sel.args, 0, value); - //console.log(1,nest); - if (nest.length > 0) { - return []; - } else { + return retval; + } else if (sel.selid === 'OF') { if (sidx + 1 + 1 > selectors.length) { return [value]; } else { - return processSelector(selectors, sidx + 1, value); + var r1 = []; + Object.keys(value).forEach(function (keyv) { + alasql.vars[sel.args[0].variable] = keyv; + r1 = r1.concat(processSelector(selectors, sidx + 1, value[keyv])); + }); + return r1; } - } - } else if (sel.selid === 'DISTINCT') { - var nest; - if (typeof sel.args === 'undefined' || sel.args.length === 0) { - nest = distinctArray(value); - } else { - nest = processSelector(sel.args, 0, value); - } - if (nest.length === 0) { - return []; - } else { - var res = distinctArray(nest); - if (sidx + 1 + 1 > selectors.length) { - return res; + } else if (sel.selid === 'TO') { + // console.log(347,value,sel.args[0]); + var oldv = alasql.vars[sel.args[0]]; + var newv = []; + if (oldv !== undefined) { + // console.log(353,typeof oldv); + newv = oldv.slice(0); + // console.log(429, oldv, newv); } else { - return processSelector(selectors, sidx + 1, res); + newv = []; } - } - } else if (sel.selid === 'AND') { - var res = true; - sel.args.forEach(function (se) { - res = res && processSelector(se, 0, value).length > 0; - }); - if (!res) { - return []; - } else { + newv.push(value); + // console.log(428,oldv,newv, value); + // console.log(435,sidx+1+1,selectors.length); + // console.log(355,alasql.vars[sel.args[0]]); if (sidx + 1 + 1 > selectors.length) { return [value]; } else { - return processSelector(selectors, sidx + 1, value); + alasql.vars[sel.args[0]] = newv; + var r1 = processSelector(selectors, sidx + 1, value); + // console.log('r1 =',r1); + alasql.vars[sel.args[0]] = oldv; + return r1; } - } - } else if (sel.selid === 'OR') { - var res = false; - sel.args.forEach(function (se) { - res = res || processSelector(se, 0, value).length > 0; - }); - if (!res) { - return []; - } else { - if (sidx + 1 + 1 > selectors.length) { - return [value]; + } else if (sel.selid === 'ARRAY') { + var nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + val = nest; } else { - return processSelector(selectors, sidx + 1, value); + return []; } - } - } else if (sel.selid === 'ALL') { - var nest = processSelector(sel.args[0], 0, value); - if (nest.length === 0) { - return []; - } else { if (sidx + 1 + 1 > selectors.length) { - return nest; + return [val]; } else { - return processSelector(selectors, sidx + 1, nest); + return processSelector(selectors, sidx + 1, val); } - } - } else if (sel.selid === 'ANY') { - var nest = processSelector(sel.args[0], 0, value); - if (nest.length === 0) { - return []; - } else { - if (sidx + 1 + 1 > selectors.length) { - return [nest[0]]; + } else if (sel.selid === 'SUM') { + var nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + var val = nest.reduce(function (sum, current) { + return sum + current; + }, 0); } else { - return processSelector(selectors, sidx + 1, [nest[0]]); + return []; } - } - } else if (sel.selid === 'UNIONALL') { - var nest = []; - sel.args.forEach(function (se) { - nest = nest.concat(processSelector(se, 0, value)); - }); - if (nest.length === 0) { - return []; - } else { if (sidx + 1 + 1 > selectors.length) { - return nest; + return [val]; } else { - return processSelector(selectors, sidx + 1, nest); + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'AVG') { + nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + val = + nest.reduce(function (sum, current) { + return sum + current; + }, 0) / nest.length; + } else { + return []; } - } - } else if (sel.selid === 'UNION') { - var nest = []; - sel.args.forEach(function (se) { - nest = nest.concat(processSelector(se, 0, value)); - }); - var nest = distinctArray(nest); - if (nest.length === 0) { - return []; - } else { if (sidx + 1 + 1 > selectors.length) { - return nest; + return [val]; } else { - return processSelector(selectors, sidx + 1, nest); + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'COUNT') { + nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + val = nest.length; + } else { + return []; } - } - } else if (sel.selid === 'IF') { - var nest = processSelector(sel.args, 0, value); - //console.log(1,nest); - if (nest.length === 0) { - return []; - } else { if (sidx + 1 + 1 > selectors.length) { - return [value]; + return [val]; } else { - return processSelector(selectors, sidx + 1, value); + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'FIRST') { + nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + val = nest[0]; + } else { + return []; } - } - } else if (sel.selid === 'REPEAT') { - // console.log(352,sel.sels); - var lvar, - lmax, - lmin = sel.args[0].value; - if (!sel.args[1]) { - lmax = lmin; // Add security break - } else { - lmax = sel.args[1].value; - } - if (sel.args[2]) { - lvar = sel.args[2].variable; - } - //var lsel = sel.sels; - // console.log(351,lmin,lmax,lvar); - var retval = []; + if (sidx + 1 + 1 > selectors.length) { + return [val]; + } else { + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'LAST') { + nest = processSelector(sel.args, 0, value); + if (nest.length > 0) { + val = nest[nest.length - 1]; + } else { + return []; + } - if (lmin === 0) { if (sidx + 1 + 1 > selectors.length) { - retval = [value]; + return [val]; } else { - if (lvar) { - alasql.vars[lvar] = 0; - } - retval = retval.concat(processSelector(selectors, sidx + 1, value)); + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'MIN') { + nest = processSelector(sel.args, 0, value); + if (nest.length === 0) { + return []; + } + var val = nest.reduce(function (min, current) { + return Math.min(min, current); + }, Infinity); + if (sidx + 1 + 1 > selectors.length) { + return [val]; + } else { + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'MAX') { + var nest = processSelector(sel.args, 0, value); + if (nest.length === 0) { + return []; + } + var val = nest.reduce(function (max, current) { + return Math.max(max, current); + }, -Infinity); + if (sidx + 1 + 1 > selectors.length) { + return [val]; + } else { + return processSelector(selectors, sidx + 1, val); + } + } else if (sel.selid === 'PLUS') { + var retval = []; + // retval = retval.concat(processSelector(selectors,sidx+1,n)) + var nests = processSelector(sel.args, 0, value).slice(); + if (sidx + 1 + 1 > selectors.length) { + retval = retval.concat(nests); + } else { + nests.forEach(function (n) { + retval = retval.concat(processSelector(selectors, sidx + 1, n)); + }); } - } - // console.log(364,retval); - //console.log(370,sel.sels); - // var nests = processSelector(sel.sels,0,value).slice(); - if (lmax > 0) { - var nests = [{value: value, lvl: 1}]; - /*/* - // if(lvl >= lmin) { - // if(sidx+1+1 > selectors.length) { - // retval = retval.concat(nests); - // } else { - // retval = retval.concat(processSelector(selectors,sidx+1,value)); - // } - // } - */ - //console.log(371,nests); var i = 0; while (nests.length > 0) { - var nest = nests[0]; - //console.log(375,nest); - nests.shift(); - if (nest.lvl <= lmax) { - if (lvar) { - alasql.vars[lvar] = nest.lvl; - } - // console.log(394,sel.sels); - var nest1 = processSelector(sel.sels, 0, nest.value); - // console.log(397,nest1); + // nest = nests[0]; + // nests.shift(); + var nest = nests.shift(); + + // console.log(281,nest); + // console.log(nest,nests); + nest = processSelector(sel.args, 0, nest); + // console.log(284,nest); + // console.log('nest',nest,'nests',nests); + nests = nests.concat(nest); + //console.log(retval,nests); - nest1.forEach(function (n) { - nests.push({value: n, lvl: nest.lvl + 1}); + if (sidx + 1 + 1 > selectors.length) { + retval = retval.concat(nest); + //return retval; + } else { + nest.forEach(function (n) { + // console.log(293,n); + var rn = processSelector(selectors, sidx + 1, n); + // console.log(294,rn, retval); + retval = retval.concat(rn); }); - if (nest.lvl >= lmin) { - if (sidx + 1 + 1 > selectors.length) { - retval = retval.concat(nest1); - //return nests; - } else { - nest1.forEach(function (n) { - retval = retval.concat(processSelector(selectors, sidx + 1, n)); - }); - } - } } + // Security brake i++; if (i > INFINITE_LOOP_BREAK) { throw new Error('Infinite loop brake. Number of iterations = ' + i); } } - } - return retval; - } else if (sel.selid === 'OF') { - if (sidx + 1 + 1 > selectors.length) { - return [value]; - } else { - var r1 = []; - Object.keys(value).forEach(function (keyv) { - alasql.vars[sel.args[0].variable] = keyv; - r1 = r1.concat(processSelector(selectors, sidx + 1, value[keyv])); - }); - return r1; - } - } else if (sel.selid === 'TO') { - // console.log(347,value,sel.args[0]); - var oldv = alasql.vars[sel.args[0]]; - var newv = []; - if (oldv !== undefined) { - // console.log(353,typeof oldv); - newv = oldv.slice(0); - // console.log(429, oldv, newv); - } else { - newv = []; - } - newv.push(value); - // console.log(428,oldv,newv, value); - // console.log(435,sidx+1+1,selectors.length); - // console.log(355,alasql.vars[sel.args[0]]); - if (sidx + 1 + 1 > selectors.length) { - return [value]; - } else { - alasql.vars[sel.args[0]] = newv; - var r1 = processSelector(selectors, sidx + 1, value); - // console.log('r1 =',r1); - alasql.vars[sel.args[0]] = oldv; - return r1; - } - } else if (sel.selid === 'ARRAY') { - var nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - val = nest; - } else { - return []; - } - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'SUM') { - var nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - var val = nest.reduce(function (sum, current) { - return sum + current; - }, 0); - } else { - return []; - } - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'AVG') { - nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - val = - nest.reduce(function (sum, current) { - return sum + current; - }, 0) / nest.length; - } else { - return []; - } - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'COUNT') { - nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - val = nest.length; - } else { - return []; - } - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'FIRST') { - nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - val = nest[0]; - } else { - return []; - } - - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'LAST') { - nest = processSelector(sel.args, 0, value); - if (nest.length > 0) { - val = nest[nest.length - 1]; - } else { - return []; - } - - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'MIN') { - nest = processSelector(sel.args, 0, value); - if (nest.length === 0) { - return []; - } - var val = nest.reduce(function (min, current) { - return Math.min(min, current); - }, Infinity); - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'MAX') { - var nest = processSelector(sel.args, 0, value); - if (nest.length === 0) { - return []; - } - var val = nest.reduce(function (max, current) { - return Math.max(max, current); - }, -Infinity); - if (sidx + 1 + 1 > selectors.length) { - return [val]; - } else { - return processSelector(selectors, sidx + 1, val); - } - } else if (sel.selid === 'PLUS') { - var retval = []; - // retval = retval.concat(processSelector(selectors,sidx+1,n)) - var nests = processSelector(sel.args, 0, value).slice(); - if (sidx + 1 + 1 > selectors.length) { - retval = retval.concat(nests); - } else { - nests.forEach(function (n) { - retval = retval.concat(processSelector(selectors, sidx + 1, n)); - }); - } - - var i = 0; - while (nests.length > 0) { - // nest = nests[0]; - // nests.shift(); - var nest = nests.shift(); - - // console.log(281,nest); - // console.log(nest,nests); - nest = processSelector(sel.args, 0, nest); - // console.log(284,nest); - // console.log('nest',nest,'nests',nests); - nests = nests.concat(nest); - //console.log(retval,nests); - + return retval; + //console.log(1,nest); + } else if (sel.selid === 'STAR') { + var retval = []; + retval = processSelector(selectors, sidx + 1, value); + var nests = processSelector(sel.args, 0, value).slice(); if (sidx + 1 + 1 > selectors.length) { - retval = retval.concat(nest); - //return retval; + retval = retval.concat(nests); + //return nests; } else { - nest.forEach(function (n) { - // console.log(293,n); - var rn = processSelector(selectors, sidx + 1, n); - // console.log(294,rn, retval); - retval = retval.concat(rn); + nests.forEach(function (n) { + retval = retval.concat(processSelector(selectors, sidx + 1, n)); }); } + var i = 0; + while (nests.length > 0) { + var nest = nests[0]; + nests.shift(); + // console.log(nest,nests); + nest = processSelector(sel.args, 0, nest); + // console.log('nest',nest,'nests',nests); + nests = nests.concat(nest); + + if (sidx + 1 + 1 <= selectors.length) { + nest.forEach(function (n) { + retval = retval.concat(processSelector(selectors, sidx + 1, n)); + }); + } - // Security brake - i++; - if (i > INFINITE_LOOP_BREAK) { - throw new Error('Infinite loop brake. Number of iterations = ' + i); + // Security brake + i++; + if (i > INFINITE_LOOP_BREAK) { + throw new Error('Infinite loop brake. Number of iterations = ' + i); + } } - } - return retval; - //console.log(1,nest); - } else if (sel.selid === 'STAR') { - var retval = []; - retval = processSelector(selectors, sidx + 1, value); - var nests = processSelector(sel.args, 0, value).slice(); - if (sidx + 1 + 1 > selectors.length) { - retval = retval.concat(nests); - //return nests; - } else { - nests.forEach(function (n) { - retval = retval.concat(processSelector(selectors, sidx + 1, n)); - }); - } - var i = 0; - while (nests.length > 0) { - var nest = nests[0]; - nests.shift(); - // console.log(nest,nests); - nest = processSelector(sel.args, 0, nest); - // console.log('nest',nest,'nests',nests); - nests = nests.concat(nest); + return retval; + } else if (sel.selid === 'QUESTION') { + var retval = []; + retval = retval.concat(processSelector(selectors, sidx + 1, value)); + var nest = processSelector(sel.args, 0, value); if (sidx + 1 + 1 <= selectors.length) { nest.forEach(function (n) { retval = retval.concat(processSelector(selectors, sidx + 1, n)); }); } - - // Security brake - i++; - if (i > INFINITE_LOOP_BREAK) { - throw new Error('Infinite loop brake. Number of iterations = ' + i); - } - } - - return retval; - } else if (sel.selid === 'QUESTION') { - var retval = []; - retval = retval.concat(processSelector(selectors, sidx + 1, value)); - var nest = processSelector(sel.args, 0, value); - if (sidx + 1 + 1 <= selectors.length) { - nest.forEach(function (n) { - retval = retval.concat(processSelector(selectors, sidx + 1, n)); - }); - } - return retval; - } else if (sel.selid === 'WITH') { - var nest = processSelector(sel.args, 0, value); - // console.log('WITH',nest); - if (nest.length === 0) { - return []; - } else { - /*/* + return retval; + } else if (sel.selid === 'WITH') { + var nest = processSelector(sel.args, 0, value); + // console.log('WITH',nest); + if (nest.length === 0) { + return []; + } else { + /*/* // if(sidx+1+1 > selectors.length) { // return [nest]; // } else { // return processSelector(selectors,sidx+1,nest); // } */ - var r = {status: 1, values: nest}; - } - } else if (sel.selid === 'ROOT') { - if (sidx + 1 + 1 > selectors.length) { - return [value]; + var r = {status: 1, values: nest}; + } + } else if (sel.selid === 'ROOT') { + if (sidx + 1 + 1 > selectors.length) { + return [value]; + } else { + return processSelector(selectors, sidx + 1, fromdata); + } } else { - return processSelector(selectors, sidx + 1, fromdata); + throw new Error('Wrong selector ' + sel.selid); } + } else if (sel.srchid) { + var r = alasql.srch[sel.srchid.toUpperCase()](value, sel.args, stope, params); + // console.log(sel.srchid,r); } else { - throw new Error('Wrong selector ' + sel.selid); + throw new Error('Selector not found'); + } + // console.log(356,sidx,r); + if (typeof r === 'undefined') { + r = {status: 1, values: [value]}; } - } else if (sel.srchid) { - var r = alasql.srch[sel.srchid.toUpperCase()](value, sel.args, stope, params); - // console.log(sel.srchid,r); - } else { - throw new Error('Selector not found'); - } - // console.log(356,sidx,r); - if (typeof r === 'undefined') { - r = {status: 1, values: [value]}; - } - var res = []; - if (r.status === 1) { - var arr = r.values; + var res = []; + if (r.status === 1) { + var arr = r.values; - if (sidx + 1 + 1 > selectors.length) { - // if(sidx+1+1 > selectors.length) { - res = arr; - // console.log('res',r) - } else { - for (var i = 0; i < r.values.length; i++) { - res = res.concat(processSelector(selectors, sidx + 1, arr[i])); + if (sidx + 1 + 1 > selectors.length) { + // if(sidx+1+1 > selectors.length) { + res = arr; + // console.log('res',r) + } else { + for (var i = 0; i < r.values.length; i++) { + res = res.concat(processSelector(selectors, sidx + 1, arr[i])); + } } } + return res; } - return res; - } - if (selectors !== undefined && selectors.length > 0) { - // console.log(selectors[0].args[0].toUpperCase()); - if ( - selectors && - selectors[0] && - selectors[0].srchid === 'PROP' && - selectors[0].args && - selectors[0].args[0] - ) { - // console.log(selectors[0].args[0]); - if (selectors[0].args[0].toUpperCase() === 'XML') { - stope.mode = 'XML'; - selectors.shift(); - } else if (selectors[0].args[0].toUpperCase() === 'HTML') { - stope.mode = 'HTML'; - selectors.shift(); - } else if (selectors[0].args[0].toUpperCase() === 'JSON') { - stope.mode = 'JSON'; + if (selectors !== undefined && selectors.length > 0) { + // console.log(selectors[0].args[0].toUpperCase()); + if ( + selectors && + selectors[0] && + selectors[0].srchid === 'PROP' && + selectors[0].args && + selectors[0].args[0] + ) { + // console.log(selectors[0].args[0]); + if (selectors[0].args[0].toUpperCase() === 'XML') { + stope.mode = 'XML'; + selectors.shift(); + } else if (selectors[0].args[0].toUpperCase() === 'HTML') { + stope.mode = 'HTML'; + selectors.shift(); + } else if (selectors[0].args[0].toUpperCase() === 'JSON') { + stope.mode = 'JSON'; + selectors.shift(); + } + } + if (selectors.length > 0 && selectors[0].srchid === 'VALUE') { + stope.value = true; selectors.shift(); } } - if (selectors.length > 0 && selectors[0].srchid === 'VALUE') { - stope.value = true; - selectors.shift(); - } - } - if (this.from instanceof yy.Column) { - var dbid = this.from.databaseid || databaseid; - fromdata = alasql.databases[dbid].tables[this.from.columnid].data; - //selectors.unshift({srchid:'CHILD'}); - } else if (this.from instanceof yy.FuncValue && alasql.from[this.from.funcid.toUpperCase()]) { - var args = this.from.args.map(function (arg) { - var as = arg.toJS(); - // console.log(as); - var fn = new Function('params,alasql', 'var y;return ' + as).bind(this); - return fn(params, alasql); - }); - // console.log(args); - fromdata = alasql.from[this.from.funcid.toUpperCase()].apply(this, args); - // console.log(92,fromdata); - } else if (typeof this.from === 'undefined') { - fromdata = alasql.databases[databaseid].objects; - } else { - var fromfn = new Function('params,alasql', 'var y;return ' + this.from.toJS()); - fromdata = fromfn(params, alasql); - // Check for Mogo Collections - if ( - typeof Mongo === 'object' && - typeof Mongo.Collection !== 'object' && - fromdata instanceof Mongo.Collection - ) { - fromdata = fromdata.find().fetch(); + if (this.from instanceof yy.Column) { + var dbid = this.from.databaseid || databaseid; + fromdata = alasql.databases[dbid].tables[this.from.columnid].data; + //selectors.unshift({srchid:'CHILD'}); + } else if (this.from instanceof yy.FuncValue && alasql.from[this.from.funcid.toUpperCase()]) { + var args = this.from.args.map(function (arg) { + var as = arg.toJS(); + // console.log(as); + var fn = new Function('params,alasql', 'var y;return ' + as).bind(this); + return fn(params, alasql); + }); + // console.log(args); + fromdata = alasql.from[this.from.funcid.toUpperCase()].apply(this, args); + // console.log(92,fromdata); + } else if (typeof this.from === 'undefined') { + fromdata = alasql.databases[databaseid].objects; + } else { + var fromfn = new Function('params,alasql', 'var y;return ' + this.from.toJS()); + fromdata = fromfn(params, alasql); + // Check for Mogo Collections + if ( + typeof Mongo === 'object' && + typeof Mongo.Collection !== 'object' && + fromdata instanceof Mongo.Collection + ) { + fromdata = fromdata.find().fetch(); + } } - } - // If source data is array than first step is to run over array - // var selidx = 0; - // var selvalue = fromdata; + // If source data is array than first step is to run over array + // var selidx = 0; + // var selvalue = fromdata; - if (selectors !== undefined && selectors.length > 0) { - // Init variables for TO() selectors + if (selectors !== undefined && selectors.length > 0) { + // Init variables for TO() selectors - if (false) { - selectors.forEach(function (selector) { - if (selector.srchid === 'TO') { - //* @todo move to TO selector - alasql.vars[selector.args[0]] = []; - // TODO - process nested selectors - } - }); - } + if (false) { + selectors.forEach(function (selector) { + if (selector.srchid === 'TO') { + //* @todo move to TO selector + alasql.vars[selector.args[0]] = []; + // TODO - process nested selectors + } + }); + } - res = processSelector(selectors, 0, fromdata); - } else { - res = fromdata; - } + res = processSelector(selectors, 0, fromdata); + } else { + res = fromdata; + } - if (this.into) { - // Handle different INTO types - if (this.into instanceof yy.ParamValue) { - // INTO $variable or INTO ? - if (typeof this.into.param === 'string') { - // $variable syntax - replace the variable - params[this.into.param] = res; + if (this.into) { + // Handle different INTO types + if (this.into instanceof yy.ParamValue) { + // INTO $variable or INTO ? + if (typeof this.into.param === 'string') { + // $variable syntax - replace the variable + params[this.into.param] = res; + } else { + // ? syntax - assign to parameter + params[this.into.param] = res; + } + if (cb) res = cb(res); + } else if (this.into instanceof yy.VarValue) { + // INTO @variable + alasql.vars[this.into.variable] = res; + if (cb) res = cb(res); } else { - // ? syntax - assign to parameter - params[this.into.param] = res; + // INTO function (TXT(), JSON(), etc.) + var a1, a2; + if (typeof this.into.args[0] !== 'undefined') { + a1 = new Function('params,alasql', 'var y;return ' + this.into.args[0].toJS())( + params, + alasql + ); + } + if (typeof this.into.args[1] !== 'undefined') { + a2 = new Function('params,alasql', 'var y;return ' + this.into.args[1].toJS())( + params, + alasql + ); + } + res = alasql.into[this.into.funcid.toUpperCase()](a1, a2, res, [], cb); } - if (cb) res = cb(res); - } else if (this.into instanceof yy.VarValue) { - // INTO @variable - alasql.vars[this.into.variable] = res; - if (cb) res = cb(res); } else { - // INTO function (TXT(), JSON(), etc.) - var a1, a2; - if (typeof this.into.args[0] !== 'undefined') { - a1 = new Function('params,alasql', 'var y;return ' + this.into.args[0].toJS())( - params, - alasql - ); + if (stope.value && res.length > 0) { + res = res[0]; } - if (typeof this.into.args[1] !== 'undefined') { - a2 = new Function('params,alasql', 'var y;return ' + this.into.args[1].toJS())( - params, - alasql - ); + if (cb) { + res = cb(res); } - res = alasql.into[this.into.funcid.toUpperCase()](a1, a2, res, [], cb); - } - } else { - if (stope.value && res.length > 0) { - res = res[0]; - } - if (cb) { - res = cb(res); } + return res; } - return res; - } -}; - -// List of search functions -alasql.srch = { - PROP(val, args, stope) { - // console.log('PROP',args[0],val); - if (stope.mode === 'XML') { - const values = val.children.filter(v => v.name.toUpperCase() === args[0].toUpperCase()); - - return { - status: values.length ? 1 : -1, - values, - }; - } else { + }; + + // List of search functions + alasql.srch = { + PROP(val, args, stope) { + // console.log('PROP',args[0],val); + if (stope.mode === 'XML') { + const values = val.children.filter(v => v.name.toUpperCase() === args[0].toUpperCase()); + + return { + status: values.length ? 1 : -1, + values, + }; + } else { + if ( + typeof val !== 'object' || + val === null || + typeof args !== 'object' || + typeof val[args[0]] === 'undefined' + ) { + return {status: -1, values: []}; + } else { + return {status: 1, values: [val[args[0]]]}; + } + } + }, + + APROP(val, args) { if ( typeof val !== 'object' || val === null || typeof args !== 'object' || typeof val[args[0]] === 'undefined' ) { - return {status: -1, values: []}; + return {status: 1, values: [undefined]}; } else { return {status: 1, values: [val[args[0]]]}; } - } - }, - - APROP(val, args) { - if ( - typeof val !== 'object' || - val === null || - typeof args !== 'object' || - typeof val[args[0]] === 'undefined' - ) { - return {status: 1, values: [undefined]}; - } else { - return {status: 1, values: [val[args[0]]]}; - } - }, + }, - EQ(val, args, stope, params) { - var exprs = args[0].toJS('x', ''); - var exprfn = new Function('x,alasql,params', 'return ' + exprs); - if (val === exprfn(val, alasql, params)) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, - - // Test expression - LIKE(val, args, stope, params) { - var exprs = args[0].toJS('x', ''); - var exprfn = new Function('x,alasql,params', 'return ' + exprs); - if ( - val - .toUpperCase() - .match( - new RegExp( - '^' + - exprfn(val, alasql, params).toUpperCase().replace(/%/g, '.*').replace(/\?|_/g, '.') + - '$' - ), - 'g' - ) - ) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + EQ(val, args, stope, params) { + var exprs = args[0].toJS('x', ''); + var exprfn = new Function('x,alasql,params', 'return ' + exprs); + if (val === exprfn(val, alasql, params)) { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - ATTR(val, args, stope) { - if (stope.mode === 'XML') { - if (typeof args === 'undefined') { - return {status: 1, values: [val.attributes]}; + // Test expression + LIKE(val, args, stope, params) { + var exprs = args[0].toJS('x', ''); + var exprfn = new Function('x,alasql,params', 'return ' + exprs); + if ( + val + .toUpperCase() + .match( + new RegExp( + '^' + + exprfn(val, alasql, params) + .toUpperCase() + .replace(/%/g, '.*') + .replace(/\?|_/g, '.') + + '$' + ), + 'g' + ) + ) { + return {status: 1, values: [val]}; } else { - if ( - typeof val === 'object' && - typeof val.attributes === 'object' && - typeof val.attributes[args[0]] !== 'undefined' - ) { - return {status: 1, values: [val.attributes[args[0]]]}; + return {status: -1, values: []}; + } + }, + + ATTR(val, args, stope) { + if (stope.mode === 'XML') { + if (typeof args === 'undefined') { + return {status: 1, values: [val.attributes]}; } else { - return {status: -1, values: []}; + if ( + typeof val === 'object' && + typeof val.attributes === 'object' && + typeof val.attributes[args[0]] !== 'undefined' + ) { + return {status: 1, values: [val.attributes[args[0]]]}; + } else { + return {status: -1, values: []}; + } } + } else { + throw new Error('ATTR is not using in usual mode'); } - } else { - throw new Error('ATTR is not using in usual mode'); - } - }, + }, - CONTENT(val, args, stope) { - if (stope.mode !== 'XML') { - throw new Error('ATTR is not using in usual mode'); - } - return {status: 1, values: [val.content]}; - }, + CONTENT(val, args, stope) { + if (stope.mode !== 'XML') { + throw new Error('ATTR is not using in usual mode'); + } + return {status: 1, values: [val.content]}; + }, - SHARP(val, args) { - const obj = alasql.databases[alasql.useid].objects[args[0]]; - if (val !== undefined && val === obj) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + SHARP(val, args) { + const obj = alasql.databases[alasql.useid].objects[args[0]]; + if (val !== undefined && val === obj) { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - PARENT(/*val,args,stope*/) { - // TODO: implement - console.error('PARENT not implemented', arguments); + PARENT(/*val,args,stope*/) { + // TODO: implement + console.error('PARENT not implemented', arguments); - return {status: -1, values: []}; - }, + return {status: -1, values: []}; + }, - CHILD(val, args, stope) { - // console.log(641,val); - if (typeof val === 'object') { - if (Array.isArray(val)) { - return {status: 1, values: val}; - } else { - if (stope.mode === 'XML') { - return { - status: 1, - values: Object.keys(val.children).map(function (key) { - return val.children[key]; - }), - }; + CHILD(val, args, stope) { + // console.log(641,val); + if (typeof val === 'object') { + if (Array.isArray(val)) { + return {status: 1, values: val}; } else { - return { - status: 1, - values: Object.keys(val).map(function (key) { - return val[key]; - }), - }; + if (stope.mode === 'XML') { + return { + status: 1, + values: Object.keys(val.children).map(function (key) { + return val.children[key]; + }), + }; + } else { + return { + status: 1, + values: Object.keys(val).map(function (key) { + return val[key]; + }), + }; + } } + } else { + // If primitive value + return {status: 1, values: []}; } - } else { - // If primitive value - return {status: 1, values: []}; - } - }, - - // Return all keys - KEYS(val) { - if (typeof val === 'object' && val !== null) { - return {status: 1, values: Object.keys(val)}; - } else { - // If primitive value - return {status: 1, values: []}; - } - }, - - // Test expression - WHERE(val, args, stope, params) { - var exprs = args[0].toJS('x', ''); - var exprfn = new Function('x,alasql,params', 'return ' + exprs); - if (exprfn(val, alasql, params)) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + }, - NAME(val, args) { - if (val.name === args[0]) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + // Return all keys + KEYS(val) { + if (typeof val === 'object' && val !== null) { + return {status: 1, values: Object.keys(val)}; + } else { + // If primitive value + return {status: 1, values: []}; + } + }, + + // Test expression + WHERE(val, args, stope, params) { + var exprs = args[0].toJS('x', ''); + var exprfn = new Function('x,alasql,params', 'return ' + exprs); + if (exprfn(val, alasql, params)) { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - CLASS(val, args) { - // console.log(val,args); - // Please avoid `===` here - if (val.$class == args) { - // jshint ignore:line - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + NAME(val, args) { + if (val.name === args[0]) { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, + + CLASS(val, args) { + // console.log(val,args); + // Please avoid `===` here + if (val.$class == args) { + // jshint ignore:line + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - // Transform expression - VERTEX(val) { - if (val.$node === 'VERTEX') { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + // Transform expression + VERTEX(val) { + if (val.$node === 'VERTEX') { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - // Transform expression - INSTANCEOF(val, args) { - if (val instanceof alasql.fn[args[0]]) { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, + // Transform expression + INSTANCEOF(val, args) { + if (val instanceof alasql.fn[args[0]]) { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, - // Transform expression - EDGE(val) { - if (val.$node === 'EDGE') { - return {status: 1, values: [val]}; - } else { - return {status: -1, values: []}; - } - }, - - // Transform expression - EX(val, args, stope, params) { - var exprs = args[0].toJS('x', ''); - var exprfn = new Function('x,alasql,params', 'return ' + exprs); - return {status: 1, values: [exprfn(val, alasql, params)]}; - }, - - // Transform expression - RETURN(val, args, stope, params) { - var res = {}; - if (args && args.length > 0) { - args.forEach(function (arg) { - var exprs = arg.toJS('x', ''); - var exprfn = new Function('x,alasql,params', 'return ' + exprs); - if (typeof arg.as === 'undefined') { - arg.as = arg.toString(); - } - res[arg.as] = exprfn(val, alasql, params); - }); - } - return {status: 1, values: [res]}; - }, - - // Transform expression - REF(val) { - return {status: 1, values: [alasql.databases[alasql.useid].objects[val]]}; - }, - - // Transform expression - OUT(val) { - if (val.$out && val.$out.length > 0) { - var res = val.$out.map(function (v) { - return alasql.databases[alasql.useid].objects[v]; - }); - return {status: 1, values: res}; - } else { - return {status: -1, values: []}; - } - }, - - OUTOUT(val) { - if (val.$out && val.$out.length > 0) { - var res = []; - val.$out.forEach(function (v) { - var av = alasql.databases[alasql.useid].objects[v]; - if (av && av.$out && av.$out.length > 0) { - av.$out.forEach(function (vv) { - res = res.concat(alasql.databases[alasql.useid].objects[vv]); - }); - } - }); - return {status: 1, values: res}; - } else { - return {status: -1, values: []}; - } - }, + // Transform expression + EDGE(val) { + if (val.$node === 'EDGE') { + return {status: 1, values: [val]}; + } else { + return {status: -1, values: []}; + } + }, + + // Transform expression + EX(val, args, stope, params) { + var exprs = args[0].toJS('x', ''); + var exprfn = new Function('x,alasql,params', 'return ' + exprs); + return {status: 1, values: [exprfn(val, alasql, params)]}; + }, + + // Transform expression + RETURN(val, args, stope, params) { + var res = {}; + if (args && args.length > 0) { + args.forEach(function (arg) { + var exprs = arg.toJS('x', ''); + var exprfn = new Function('x,alasql,params', 'return ' + exprs); + if (typeof arg.as === 'undefined') { + arg.as = arg.toString(); + } + res[arg.as] = exprfn(val, alasql, params); + }); + } + return {status: 1, values: [res]}; + }, + + // Transform expression + REF(val) { + return {status: 1, values: [alasql.databases[alasql.useid].objects[val]]}; + }, + + // Transform expression + OUT(val) { + if (val.$out && val.$out.length > 0) { + var res = val.$out.map(function (v) { + return alasql.databases[alasql.useid].objects[v]; + }); + return {status: 1, values: res}; + } else { + return {status: -1, values: []}; + } + }, + + OUTOUT(val) { + if (val.$out && val.$out.length > 0) { + var res = []; + val.$out.forEach(function (v) { + var av = alasql.databases[alasql.useid].objects[v]; + if (av && av.$out && av.$out.length > 0) { + av.$out.forEach(function (vv) { + res = res.concat(alasql.databases[alasql.useid].objects[vv]); + }); + } + }); + return {status: 1, values: res}; + } else { + return {status: -1, values: []}; + } + }, - // Transform expression - IN(val) { - if (val.$in && val.$in.length > 0) { - var res = val.$in.map(function (v) { - return alasql.databases[alasql.useid].objects[v]; - }); - return {status: 1, values: res}; - } else { - return {status: -1, values: []}; - } - }, - - ININ(val) { - if (val.$in && val.$in.length > 0) { - var res = []; - val.$in.forEach(function (v) { - var av = alasql.databases[alasql.useid].objects[v]; - if (av && av.$in && av.$in.length > 0) { - av.$in.forEach(function (vv) { - res = res.concat(alasql.databases[alasql.useid].objects[vv]); - }); - } - }); - return {status: 1, values: res}; - } else { - return {status: -1, values: []}; - } - }, - - // Transform expression - AS(val, args) { - alasql.vars[args[0]] = val; - return {status: 1, values: [val]}; - }, - - // Transform expression - AT(val, args) { - var v = alasql.vars[args[0]]; - return {status: 1, values: [v]}; - }, - - // Transform expression - CLONEDEEP(val) { - // TODO something wrong - var z = cloneDeep(val); - return {status: 1, values: [z]}; - }, - - // // Transform expression - // DELETE (val,args) { - // // TODO something wrong - // delete val; - // return {status: 1, values: []}; - // }, - - // Transform expression - SET(val, args, stope, params) { - // console.log(arguments); - var s = args - .map(function (st) { - if (st.method === '@') { - return `alasql.vars[${JSON.stringify(st.variable)}]=` + st.expression.toJS('x', ''); - } else if (st.method === '$') { - return `params[${JSON.stringify(st.variable)}]=` + st.expression.toJS('x', ''); - } else { - return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); - } - }) - .join(';'); - var setfn = new Function('x,params,alasql', s); + // Transform expression + IN(val) { + if (val.$in && val.$in.length > 0) { + var res = val.$in.map(function (v) { + return alasql.databases[alasql.useid].objects[v]; + }); + return {status: 1, values: res}; + } else { + return {status: -1, values: []}; + } + }, + + ININ(val) { + if (val.$in && val.$in.length > 0) { + var res = []; + val.$in.forEach(function (v) { + var av = alasql.databases[alasql.useid].objects[v]; + if (av && av.$in && av.$in.length > 0) { + av.$in.forEach(function (vv) { + res = res.concat(alasql.databases[alasql.useid].objects[vv]); + }); + } + }); + return {status: 1, values: res}; + } else { + return {status: -1, values: []}; + } + }, - setfn(val, params, alasql); + // Transform expression + AS(val, args) { + alasql.vars[args[0]] = val; + return {status: 1, values: [val]}; + }, + + // Transform expression + AT(val, args) { + var v = alasql.vars[args[0]]; + return {status: 1, values: [v]}; + }, + + // Transform expression + CLONEDEEP(val) { + // TODO something wrong + var z = cloneDeep(val); + return {status: 1, values: [z]}; + }, + + // // Transform expression + // DELETE (val,args) { + // // TODO something wrong + // delete val; + // return {status: 1, values: []}; + // }, + + // Transform expression + SET(val, args, stope, params) { + // console.log(arguments); + var s = args + .map(function (st) { + if (st.method === '@') { + return `alasql.vars[${JSON.stringify(st.variable)}]=` + st.expression.toJS('x', ''); + } else if (st.method === '$') { + return `params[${JSON.stringify(st.variable)}]=` + st.expression.toJS('x', ''); + } else { + return `x[${JSON.stringify(st.column.columnid)}]=` + st.expression.toJS('x', ''); + } + }) + .join(';'); + var setfn = new Function('x,params,alasql', s); - return {status: 1, values: [val]}; - }, + setfn(val, params, alasql); - ROW(val, args, stope, params) { - var s = 'var y;return ['; - s += args.map(arg => arg.toJS('x', '')).join(','); - s += ']'; - var setfn = new Function('x,params,alasql', s); - var rv = setfn(val, params, alasql); + return {status: 1, values: [val]}; + }, + + ROW(val, args, stope, params) { + var s = 'var y;return ['; + s += args.map(arg => arg.toJS('x', '')).join(','); + s += ']'; + var setfn = new Function('x,params,alasql', s); + var rv = setfn(val, params, alasql); + + return {status: 1, values: [rv]}; + }, + + D3(val) { + if (val.$node !== 'VERTEX' && val.$node === 'EDGE') { + val.source = val.$in[0]; + val.target = val.$out[0]; + } - return {status: 1, values: [rv]}; - }, + return {status: 1, values: [val]}; + }, - D3(val) { - if (val.$node !== 'VERTEX' && val.$node === 'EDGE') { - val.source = val.$in[0]; - val.target = val.$out[0]; - } + ORDERBY(val, args /*, stope */) { + var res = val.sort(compileSearchOrder(args)); + return {status: 1, values: res}; + }, + }; + + var compileSearchOrder = function (order) { + if (order) { + if (typeof order?.[0]?.expression === 'function') { + var func = order[0].expression; + return function (a, b) { + var ra = func(a), + rb = func(b); + if (ra > rb) { + return 1; + } + if (ra === rb) { + return 0; + } + return -1; + }; + } - return {status: 1, values: [val]}; - }, - - ORDERBY(val, args /*, stope */) { - var res = val.sort(compileSearchOrder(args)); - return {status: 1, values: res}; - }, -}; - -var compileSearchOrder = function (order) { - if (order) { - if (typeof order?.[0]?.expression === 'function') { - var func = order[0].expression; - return function (a, b) { - var ra = func(a), - rb = func(b); - if (ra > rb) { - return 1; + var s = ''; + var sk = ''; + order.forEach(function (ord) { + // console.log(ord instanceof yy.Expression); + // console.log(ord.toJS('a','')); + // console.log(ord.expression instanceof yy.Column); + + // Date conversion + var dg = ''; + //console.log(ord.expression, ord.expression instanceof yy.NumValue); + if (ord.expression instanceof yy.NumValue) { + ord.expression = self.columns[ord.expression.value - 1]; } - if (ra === rb) { - return 0; - } - return -1; - }; - } - - var s = ''; - var sk = ''; - order.forEach(function (ord) { - // console.log(ord instanceof yy.Expression); - // console.log(ord.toJS('a','')); - // console.log(ord.expression instanceof yy.Column); - - // Date conversion - var dg = ''; - //console.log(ord.expression, ord.expression instanceof yy.NumValue); - if (ord.expression instanceof yy.NumValue) { - ord.expression = self.columns[ord.expression.value - 1]; - } - if (ord.expression instanceof yy.Column) { - var columnid = ord.expression.columnid; + if (ord.expression instanceof yy.Column) { + var columnid = ord.expression.columnid; - if (alasql.options.valueof) { - dg = '.valueOf()'; // TODO Check - } - // COLLATE NOCASE - if (ord.nocase) { - dg += '.toUpperCase()'; - } + if (alasql.options.valueof) { + dg = '.valueOf()'; // TODO Check + } + // COLLATE NOCASE + if (ord.nocase) { + dg += '.toUpperCase()'; + } - if (columnid === '_') { - s += 'if(a' + dg + (ord.direction === 'ASC' ? '>' : '<') + 'b' + dg + ')return 1;'; - s += 'if(a' + dg + '==b' + dg + '){'; - } else { - s += `if ( + if (columnid === '_') { + s += 'if(a' + dg + (ord.direction === 'ASC' ? '>' : '<') + 'b' + dg + ')return 1;'; + s += 'if(a' + dg + '==b' + dg + '){'; + } else { + s += `if ( (a[${JSON.stringify(columnid)}]||'')${dg} ${ord.direction === 'ASC' ? '>' : '<'} (b[${JSON.stringify(columnid)}]||'')${dg} @@ -1163,14 +1166,14 @@ var compileSearchOrder = function (order) { (b[${JSON.stringify(columnid)}]||'')${dg} ){ `; - } - } else { - dg = '.valueOf()'; - // COLLATE NOCASE - if (ord.nocase) { - dg += '.toUpperCase()'; - } - s += ` + } + } else { + dg = '.valueOf()'; + // COLLATE NOCASE + if (ord.nocase) { + dg += '.toUpperCase()'; + } + s += ` if ( (${ord.toJS('a', '')} || '')${dg} ${ord.direction === 'ASC' ? '>' : '<'} @@ -1181,18 +1184,18 @@ var compileSearchOrder = function (order) { (${ord.toJS('a', '')} || '')${dg} == (${ord.toJS('b', '')} || '')${dg} ) {`; - } + } - // TODO Add date comparision - // s += 'if(a[\''+columnid+"']"+dg+(ord.direction == 'ASC'?'>':'<')+'b[\''+columnid+"']"+dg+')return 1;'; - // s += 'if(a[\''+columnid+"']"+dg+'==b[\''+columnid+"']"+dg+'){'; - // } - sk += '}'; - }); - s += 'return 0;'; - s += sk + 'return -1'; - //console.log(s); - return new Function('a,b', s); - } -}; + // TODO Add date comparision + // s += 'if(a[\''+columnid+"']"+dg+(ord.direction == 'ASC'?'>':'<')+'b[\''+columnid+"']"+dg+')return 1;'; + // s += 'if(a[\''+columnid+"']"+dg+'==b[\''+columnid+"']"+dg+'){'; + // } + sk += '}'; + }); + s += 'return 0;'; + s += sk + 'return -1'; + //console.log(s); + return new Function('a,b', s); + } + }; } diff --git a/src/select/apply.js b/src/select/apply.js index 137618b426..fcf168cd4b 100755 --- a/src/select/apply.js +++ b/src/select/apply.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // CROSS AND OUTER APPLY for Alasql.js // Date: 03.11.2014 @@ -8,19 +8,19 @@ export default function install(alasql) { // */ -yy.Apply = class Apply { - constructor(params) { - Object.assign(this, params); - } + yy.Apply = class Apply { + constructor(params) { + Object.assign(this, params); + } - toString() { - let s = `${this.applymode} APPLY (${this.select.toString()})`; + toString() { + let s = `${this.applymode} APPLY (${this.select.toString()})`; - if (this.as) { - s += ` AS ${this.as}`; - } + if (this.as) { + s += ` AS ${this.as}`; + } - return s; - } -}; + return s; + } + }; } diff --git a/src/select/columns.js b/src/select/columns.js index 42edf499d2..b490513fd5 100755 --- a/src/select/columns.js +++ b/src/select/columns.js @@ -2,7 +2,7 @@ import {cloneDeep, escapeq, n2u} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // Select compiler part for Alasql.js // Date: 03.11.2014 @@ -10,312 +10,338 @@ export default function install(alasql) { // */ -// yy.Select.prototype.compileSources = function(query) { -// return sources; -// }; - -// Regular expression to match aggregate functions that require expression compilation -var re_aggrWithExpression = /^(SUM|MAX|MIN|FIRST|LAST|AVG|ARRAY|REDUCE|TOTAL)$/; - -function compileSelectStar(query, aliases, joinstar) { - var sp = '', - ss = [], - columnIds = {}; - - aliases.forEach(function (alias) { - // console.log(query.aliases[alias]); - // console.log(query,alias); - // console.log(query.aliases[alias].tableid); - // console.log(42,631,alias); - // console.log(query.aliases); - // if(!alias) { - // sp += 'for(var k1 in p) var w=p[k1];for(var k2 in w){r[k2]=w[k2]};'; - // } else { - - // TODO move this out of this function - query.ixsources = {}; - query.sources.forEach(function (source) { - query.ixsources[source.alias] = source; - }); + // yy.Select.prototype.compileSources = function(query) { + // return sources; + // }; + + // Regular expression to match aggregate functions that require expression compilation + var re_aggrWithExpression = /^(SUM|MAX|MIN|FIRST|LAST|AVG|ARRAY|REDUCE|TOTAL)$/; + + function compileSelectStar(query, aliases, joinstar) { + var sp = '', + ss = [], + columnIds = {}; + + aliases.forEach(function (alias) { + // console.log(query.aliases[alias]); + // console.log(query,alias); + // console.log(query.aliases[alias].tableid); + // console.log(42,631,alias); + // console.log(query.aliases); + // if(!alias) { + // sp += 'for(var k1 in p) var w=p[k1];for(var k2 in w){r[k2]=w[k2]};'; + // } else { + + // TODO move this out of this function + query.ixsources = {}; + query.sources.forEach(function (source) { + query.ixsources[source.alias] = source; + }); - // Fixed - var columns; - if (query.ixsources[alias]) { - var columns = query.ixsources[alias].columns; - } + // Fixed + var columns; + if (query.ixsources[alias]) { + var columns = query.ixsources[alias].columns; + } - // if(columns.length == 0 && query.aliases[alias].tableid) { - // var columns = alasql.databases[query.aliases[alias].databaseid].tables[query.aliases[alias].tableid].columns; - // }; + // if(columns.length == 0 && query.aliases[alias].tableid) { + // var columns = alasql.databases[query.aliases[alias].databaseid].tables[query.aliases[alias].tableid].columns; + // }; - // Check if this is a Table or other - if (joinstar && alasql.options.joinstar == 'json') { - sp += "r['" + alias + "']={};"; - } + // Check if this is a Table or other + if (joinstar && alasql.options.joinstar == 'json') { + sp += "r['" + alias + "']={};"; + } - if (columns && columns.length > 0) { - columns.forEach(function (tcol) { - const escapedColumnId = escapeq(tcol.columnid); - if (joinstar && alasql.options.joinstar == 'underscore') { - ss.push( - "'" + alias + '_' + escapedColumnId + "':p['" + alias + "']['" + escapedColumnId + "']" - ); - } else if (joinstar && alasql.options.joinstar == 'json') { - // ss.push('\''+alias+'_'+tcol.columnid+'\':p[\''+alias+'\'][\''+tcol.columnid+'\']'); - sp += - "r['" + - alias + - "']['" + - escapedColumnId + - "']=p['" + - alias + - "']['" + - escapedColumnId + - "'];"; - } else { - var value = "p['" + alias + "']['" + escapedColumnId + "']"; - if (!columnIds[tcol.columnid]) { - var key = "'" + escapedColumnId + "':"; - ss.push(key + value); - columnIds[tcol.columnid] = { - id: ss.length - 1, - value: value, - key: key, - }; + if (columns && columns.length > 0) { + columns.forEach(function (tcol) { + const escapedColumnId = escapeq(tcol.columnid); + if (joinstar && alasql.options.joinstar == 'underscore') { + ss.push( + "'" + + alias + + '_' + + escapedColumnId + + "':p['" + + alias + + "']['" + + escapedColumnId + + "']" + ); + } else if (joinstar && alasql.options.joinstar == 'json') { + // ss.push('\''+alias+'_'+tcol.columnid+'\':p[\''+alias+'\'][\''+tcol.columnid+'\']'); + sp += + "r['" + + alias + + "']['" + + escapedColumnId + + "']=p['" + + alias + + "']['" + + escapedColumnId + + "'];"; } else { - var newValue = - value + ' !== undefined ? ' + value + ' : ' + columnIds[tcol.columnid].value; - ss[columnIds[tcol.columnid].id] = columnIds[tcol.columnid].key + newValue; - columnIds[tcol.columnid].value = newValue; + var value = "p['" + alias + "']['" + escapedColumnId + "']"; + if (!columnIds[tcol.columnid]) { + var key = "'" + escapedColumnId + "':"; + ss.push(key + value); + columnIds[tcol.columnid] = { + id: ss.length - 1, + value: value, + key: key, + }; + } else { + var newValue = + value + ' !== undefined ? ' + value + ' : ' + columnIds[tcol.columnid].value; + ss[columnIds[tcol.columnid].id] = columnIds[tcol.columnid].key + newValue; + columnIds[tcol.columnid].value = newValue; + } } - } - query.selectColumns[escapedColumnId] = true; + query.selectColumns[escapedColumnId] = true; - // console.log('ok',tcol); + // console.log('ok',tcol); - var coldef = { - columnid: tcol.columnid, - dbtypeid: tcol.dbtypeid, - dbsize: tcol.dbsize, - dbprecision: tcol.dbprecision, - dbenum: tcol.dbenum, - }; - query.columns.push(coldef); - query.xcolumns[coldef.columnid] = coldef; - }); - //console.log(999,columns); - } else { - // console.log(60,alias,columns); - - // If columns are not known (e.g., with inline data using ? placeholders), - // copy all properties dynamically respecting the joinstar option: - // - 'json': Nested objects by alias (e.g., {a: {col: val}, b: {col: val}}) - // - 'underscore': Prefix columns with alias (e.g., {a_col: val, b_col: val}) - // - 'overwrite': Later columns overwrite earlier ones (default) - if (joinstar && alasql.options.joinstar == 'json') { - // For json mode, create nested object with alias as key - sp += "r['" + escapeq(alias) + "']=p['" + escapeq(alias) + "'];"; - } else if (joinstar && alasql.options.joinstar == 'underscore') { - // For underscore mode, prefix each key with alias_ - sp += - 'var w=p["' + escapeq(alias) + '"];for(var k in w){r["' + escapeq(alias) + '_"+k]=w[k]};'; + var coldef = { + columnid: tcol.columnid, + dbtypeid: tcol.dbtypeid, + dbsize: tcol.dbsize, + dbprecision: tcol.dbprecision, + dbenum: tcol.dbenum, + }; + query.columns.push(coldef); + query.xcolumns[coldef.columnid] = coldef; + }); + //console.log(999,columns); } else { - // Default overwrite mode - sp += 'var w=p["' + escapeq(alias) + '"];for(var k in w){r[k]=w[k]};'; + // console.log(60,alias,columns); + + // If columns are not known (e.g., with inline data using ? placeholders), + // copy all properties dynamically respecting the joinstar option: + // - 'json': Nested objects by alias (e.g., {a: {col: val}, b: {col: val}}) + // - 'underscore': Prefix columns with alias (e.g., {a_col: val, b_col: val}) + // - 'overwrite': Later columns overwrite earlier ones (default) + if (joinstar && alasql.options.joinstar == 'json') { + // For json mode, create nested object with alias as key + sp += "r['" + escapeq(alias) + "']=p['" + escapeq(alias) + "'];"; + } else if (joinstar && alasql.options.joinstar == 'underscore') { + // For underscore mode, prefix each key with alias_ + sp += + 'var w=p["' + + escapeq(alias) + + '"];for(var k in w){r["' + + escapeq(alias) + + '_"+k]=w[k]};'; + } else { + // Default overwrite mode + sp += 'var w=p["' + escapeq(alias) + '"];for(var k in w){r[k]=w[k]};'; + } + //console.log(777, sp); + query.dirtyColumns = true; } - //console.log(777, sp); - query.dirtyColumns = true; - } - // } - //console.log(87,{s:ss.join(','),sp:sp}); - }); - - return {s: ss.join(','), sp: sp}; -} + // } + //console.log(87,{s:ss.join(','),sp:sp}); + }); -// Helper function to check if an expression is an arrow operation and extract its path -// Returns null if not an arrow op, or an array of path parts if it is -function getArrowPath(expr) { - if (!expr || expr.op !== '->') { - return null; + return {s: ss.join(','), sp: sp}; } - var path = []; - var current = expr; - while (current && current.op === '->') { - // The right side is the property name - if (typeof current.right === 'string') { - path.unshift(current.right); - } else if (typeof current.right === 'number') { - path.unshift(current.right); - } else { - // Complex expression on right side, can't extract path + + // Helper function to check if an expression is an arrow operation and extract its path + // Returns null if not an arrow op, or an array of path parts if it is + function getArrowPath(expr) { + if (!expr || expr.op !== '->') { return null; } - current = current.left; - } - // The leftmost should be a column - if (current && current.columnid) { - path.unshift(current.columnid); - return path; + var path = []; + var current = expr; + while (current && current.op === '->') { + // The right side is the property name + if (typeof current.right === 'string') { + path.unshift(current.right); + } else if (typeof current.right === 'number') { + path.unshift(current.right); + } else { + // Complex expression on right side, can't extract path + return null; + } + current = current.left; + } + // The leftmost should be a column + if (current && current.columnid) { + path.unshift(current.columnid); + return path; + } + return null; } - return null; -} -yy.Select.prototype.compileSelect1 = function (query, params) { - var self = this; - query.columns = []; - query.xcolumns = {}; - query.selectColumns = {}; - query.dirtyColumns = false; - var s = 'var r={'; - var sp = ''; - var ss = []; - - //console.log(42,87,this.columns); - - this.columns.forEach(function (col) { - if (col instanceof yy.Column) { - if (col.columnid === '*') { - if (col.func) { - sp += - "r=params['" + col.param + "'](p['" + query.sources[0].alias + "'],p,params,alasql);"; - } else if (col.tableid) { - //Copy all - var ret = compileSelectStar(query, [col.tableid], false); - if (ret.s) { - ss = ss.concat(ret.s); - } - sp += ret.sp; - } else { - // console.log('aliases', query.aliases); - var ret = compileSelectStar(query, Object.keys(query.aliases), true); //query.aliases[alias].tableid); - if (ret.s) { - ss = ss.concat(ret.s); - } - sp += ret.sp; + yy.Select.prototype.compileSelect1 = function (query, params) { + var self = this; + query.columns = []; + query.xcolumns = {}; + query.selectColumns = {}; + query.dirtyColumns = false; + var s = 'var r={'; + var sp = ''; + var ss = []; - // TODO Remove these lines - // In case of no information - // sp += 'for(var k1 in p){var w=p[k1];'+ - // 'for(k2 in w) {r[k2]=w[k2]}}' - } - } else { - // If field, otherwise - expression - var tbid = col.tableid; - // console.log(query.sources); - var dbid = col.databaseid || query.sources[0].databaseid || query.database.databaseid; - if (!tbid) tbid = query.defcols[col.columnid]; - if (!tbid) tbid = query.defaultTableid; - if (col.columnid !== '_') { - if (false && tbid && !query.defcols['.'][col.tableid] && !query.defcols[col.columnid]) { - ss.push( - "'" + - escapeq(col.as || col.columnid) + - "':p['" + - query.defaultTableid + - "']['" + - col.tableid + - "']['" + - col.columnid + - "']" - ); + //console.log(42,87,this.columns); + + this.columns.forEach(function (col) { + if (col instanceof yy.Column) { + if (col.columnid === '*') { + if (col.func) { + sp += + "r=params['" + col.param + "'](p['" + query.sources[0].alias + "'],p,params,alasql);"; + } else if (col.tableid) { + //Copy all + var ret = compileSelectStar(query, [col.tableid], false); + if (ret.s) { + ss = ss.concat(ret.s); + } + sp += ret.sp; } else { - // workaround for multisheet xlsx export with custom COLUMNS - var isMultisheetParam = - params && - params.length > 1 && - Array.isArray(params[0]) && - params[0].length >= 1 && - params[0][0].hasOwnProperty('sheetid'); - if (isMultisheetParam) { - sp = - 'var r={};var w=p["' + - tbid + - '"];' + - 'var cols=[' + - self.columns - .map(function (col) { - return "'" + col.columnid + "'"; - }) - .join(',') + - '];var colas=[' + - self.columns - .map(function (col) { - return "'" + (col.as || col.columnid) + "'"; - }) - .join(',') + - '];' + - "for (var i=0;i 1 && - (!query.defcols[col.columnid] || query.defcols[col.columnid] === '-'); - - if (needsRuntimeResolution) { - ss.push( - "'" + - escapeq(col.as || col.columnid) + - "':" + - yy.Column.fromAnySource('p', Object.keys(query.aliases), col.columnid) - ); - // Only check it against the default table if no other source can have it - var inOtherSource = query.sources.some( - src => - src.alias !== tbid && - (!src.columns?.length || src.columns.some(c => c.columnid === col.columnid)) - ); - if (inOtherSource) tbid = undefined; + // workaround for multisheet xlsx export with custom COLUMNS + var isMultisheetParam = + params && + params.length > 1 && + Array.isArray(params[0]) && + params[0].length >= 1 && + params[0][0].hasOwnProperty('sheetid'); + if (isMultisheetParam) { + sp = + 'var r={};var w=p["' + + tbid + + '"];' + + 'var cols=[' + + self.columns + .map(function (col) { + return "'" + col.columnid + "'"; + }) + .join(',') + + '];var colas=[' + + self.columns + .map(function (col) { + return "'" + (col.as || col.columnid) + "'"; + }) + .join(',') + + '];' + + "for (var i=0;i 1 && + (!query.defcols[col.columnid] || query.defcols[col.columnid] === '-'); + + if (needsRuntimeResolution) { + ss.push( + "'" + + escapeq(col.as || col.columnid) + + "':" + + yy.Column.fromAnySource('p', Object.keys(query.aliases), col.columnid) + ); + // Only check it against the default table if no other source can have it + var inOtherSource = query.sources.some( + src => + src.alias !== tbid && + (!src.columns?.length || src.columns.some(c => c.columnid === col.columnid)) + ); + if (inOtherSource) tbid = undefined; + } else { + ss.push( + "'" + + escapeq(col.as || col.columnid) + + "':p['" + + tbid + + "']['" + + col.columnid + + "']" + ); + } } } + } else { + ss.push("'" + escapeq(col.as || col.columnid) + "':p['" + tbid + "']"); } - } else { - ss.push("'" + escapeq(col.as || col.columnid) + "':p['" + tbid + "']"); - } - query.selectColumns[escapeq(col.as || col.columnid)] = true; + query.selectColumns[escapeq(col.as || col.columnid)] = true; - if (query.aliases[tbid] && query.aliases[tbid].type === 'table') { - if (!alasql.databases[dbid].tables[query.aliases[tbid].tableid]) { - // console.log(query.database,tbid,query.aliases[tbid].tableid); - throw new Error("Table '" + tbid + "' does not exist in database"); - } - var columns = alasql.databases[dbid].tables[query.aliases[tbid].tableid].columns; - var xcolumns = alasql.databases[dbid].tables[query.aliases[tbid].tableid].xcolumns; - //console.log(xcolumns, col,123); - // console.log(0); - if (xcolumns && columns.length > 0) { - // console.log(1); - var tcol = xcolumns[col.columnid]; - - if (undefined === tcol) { - throw new Error('Column does not exist: ' + col.columnid); + if (query.aliases[tbid] && query.aliases[tbid].type === 'table') { + if (!alasql.databases[dbid].tables[query.aliases[tbid].tableid]) { + // console.log(query.database,tbid,query.aliases[tbid].tableid); + throw new Error("Table '" + tbid + "' does not exist in database"); } + var columns = alasql.databases[dbid].tables[query.aliases[tbid].tableid].columns; + var xcolumns = alasql.databases[dbid].tables[query.aliases[tbid].tableid].xcolumns; + //console.log(xcolumns, col,123); + // console.log(0); + if (xcolumns && columns.length > 0) { + // console.log(1); + var tcol = xcolumns[col.columnid]; + + if (undefined === tcol) { + throw new Error('Column does not exist: ' + col.columnid); + } - var coldef = { - columnid: col.as || col.columnid, - dbtypeid: tcol.dbtypeid, - dbsize: tcol.dbsize, - dbpecision: tcol.dbprecision, - dbenum: tcol.dbenum, - }; - // console.log(2); - query.columns.push(coldef); - query.xcolumns[coldef.columnid] = coldef; + var coldef = { + columnid: col.as || col.columnid, + dbtypeid: tcol.dbtypeid, + dbsize: tcol.dbsize, + dbpecision: tcol.dbprecision, + dbenum: tcol.dbenum, + }; + // console.log(2); + query.columns.push(coldef); + query.xcolumns[coldef.columnid] = coldef; + } else { + var coldef = { + columnid: col.as || col.columnid, + // dbtypeid:tcol.dbtypeid, + // dbsize:tcol.dbsize, + // dbpecision:tcol.dbprecision, + // dbenum: tcol.dbenum, + }; + // console.log(2); + query.columns.push(coldef); + query.xcolumns[coldef.columnid] = coldef; + + query.dirtyColumns = true; + } } else { var coldef = { columnid: col.as || col.columnid, @@ -327,12 +353,99 @@ yy.Select.prototype.compileSelect1 = function (query, params) { // console.log(2); query.columns.push(coldef); query.xcolumns[coldef.columnid] = coldef; + // This is a subquery? + // throw new Error('There is now such table \''+col.tableid+'\''); + } + } + } else if (col instanceof yy.AggrValue) { + // Set alias if not provided + if (!col.as) col.as = escapeq(col.toString()); + + // Check if this aggregate has an OVER clause (window function) + if (col.over) { + // Track window aggregate for post-processing + query.windowaggrs.push({ + as: col.as, + aggregatorid: col.aggregatorid, + expression: col.expression, + partitionColumns: col.over.partition + ? col.over.partition.map(function (p) { + return p.columnid || p.toString(); + }) + : [], + }); + } else { + // Regular aggregate - trigger GROUP BY + if (!self.group) self.group = ['']; - query.dirtyColumns = true; + if (re_aggrWithExpression.test(col.aggregatorid)) { + ss.push( + "'" + + escapeq(col.as) + + "':" + + n2u(col.expression.toJS('p', query.defaultTableid, query.defcols)) + ); + } else if (col.aggregatorid === 'COUNT') { + ss.push("'" + escapeq(col.as) + "':1"); + } + } + + // Add column definition for both window and regular aggregates + var coldef = { + columnid: col.as || col.columnid || col.toString(), + }; + query.columns.push(coldef); + query.xcolumns[coldef.columnid] = coldef; + } else { + // console.log(203,col.as,col.columnid,col.toString()); + // Check if this is an arrow expression and we're outputting to OBJECT + var arrowPath = query.intoObject && !col.as ? getArrowPath(col) : null; + if (arrowPath && arrowPath.length > 1) { + // For arrow expressions in INTO OBJECT(), generate nested object assignment + // This will be added to sp (post-processing) instead of ss (inline object) + var valueJs = n2u(col.toJS('p', query.defaultTableid, query.defcols)); + // Generate code to create nested structure + // e.g., for path ['details', 'stock']: r['details'] = r['details'] || {}; r['details']['stock'] = value; + for (var i = 0; i < arrowPath.length - 1; i++) { + var pathSoFar = arrowPath.slice(0, i + 1); + var accessor = pathSoFar + .map(function (p) { + return "['" + escapeq(p) + "']"; + }) + .join(''); + sp += 'r' + accessor + ' = r' + accessor + ' || {};'; + } + var fullAccessor = arrowPath + .map(function (p) { + return "['" + escapeq(p) + "']"; + }) + .join(''); + sp += 'r' + fullAccessor + ' = ' + valueJs + ';'; + + // Use the first part of the path as the column name for metadata + var colName = arrowPath[0]; + query.selectColumns[escapeq(colName)] = true; + var coldef = { + columnid: colName, + }; + // Only add if not already added + if (!query.xcolumns[coldef.columnid]) { + query.columns.push(coldef); + query.xcolumns[coldef.columnid] = coldef; } } else { + ss.push( + "'" + + escapeq(col.as || col.columnid || col.toString()) + + "':" + + n2u(col.toJS('p', query.defaultTableid, query.defcols)) + ); + // ss.push('\''+escapeq(col.toString())+'\':'+col.toJS("p",query.defaultTableid)); + //if(col instanceof yy.Expression) { + query.selectColumns[escapeq(col.as || col.columnid || col.toString())] = true; + var coldef = { - columnid: col.as || col.columnid, + columnid: col.as || col.columnid || col.toString(), // dbtypeid:tcol.dbtypeid, // dbsize:tcol.dbsize, // dbpecision:tcol.dbprecision, @@ -341,290 +454,189 @@ yy.Select.prototype.compileSelect1 = function (query, params) { // console.log(2); query.columns.push(coldef); query.xcolumns[coldef.columnid] = coldef; - // This is a subquery? - // throw new Error('There is now such table \''+col.tableid+'\''); } } - } else if (col instanceof yy.AggrValue) { - // Set alias if not provided - if (!col.as) col.as = escapeq(col.toString()); - - // Check if this aggregate has an OVER clause (window function) - if (col.over) { - // Track window aggregate for post-processing - query.windowaggrs.push({ - as: col.as, - aggregatorid: col.aggregatorid, - expression: col.expression, - partitionColumns: col.over.partition - ? col.over.partition.map(function (p) { - return p.columnid || p.toString(); - }) - : [], - }); - } else { - // Regular aggregate - trigger GROUP BY - if (!self.group) self.group = ['']; - - if (re_aggrWithExpression.test(col.aggregatorid)) { - ss.push( - "'" + - escapeq(col.as) + - "':" + - n2u(col.expression.toJS('p', query.defaultTableid, query.defcols)) - ); - } else if (col.aggregatorid === 'COUNT') { - ss.push("'" + escapeq(col.as) + "':1"); + }); + s += ss.join(',') + '};' + sp; + return s; + //console.log(42,753,query.xcolumns, query.selectColumns); + }; + yy.Select.prototype.compileSelect2 = function (query, params) { + var s = query.selectfns; + // Only add order keys if there's no union operation (otherwise they'll be added later) + if ( + this.orderColumns && + this.orderColumns.length > 0 && + !this.union && + !this.unionall && + !this.except && + !this.intersect + ) { + this.orderColumns.forEach(function (v, idx) { + var key = '$$$' + idx; + // Handle positional column reference (for SELECT * with ORDER BY numeric) + if (v._useColumnIndex !== undefined) { + // Use Object.keys to get column names and access by index + s += "var keys=Object.keys(r);r['" + key + "']=r[keys[" + v.columnIndex + ']];'; + } else if (v instanceof yy.Column && query.xcolumns[v.columnid]) { + s += "r['" + key + "']=r['" + v.columnid + "'];"; + } else if (v instanceof yy.ParamValue && query.xcolumns[params[v.param]]) { + s += "r['" + key + "']=r['" + params[v.param] + "'];"; + } else { + s += "r['" + key + "']=" + v.toJS('p', query.defaultTableid, query.defcols) + ';'; } - } - - // Add column definition for both window and regular aggregates - var coldef = { - columnid: col.as || col.columnid || col.toString(), - }; - query.columns.push(coldef); - query.xcolumns[coldef.columnid] = coldef; - } else { - // console.log(203,col.as,col.columnid,col.toString()); - // Check if this is an arrow expression and we're outputting to OBJECT - var arrowPath = query.intoObject && !col.as ? getArrowPath(col) : null; - if (arrowPath && arrowPath.length > 1) { - // For arrow expressions in INTO OBJECT(), generate nested object assignment - // This will be added to sp (post-processing) instead of ss (inline object) - var valueJs = n2u(col.toJS('p', query.defaultTableid, query.defcols)); - // Generate code to create nested structure - // e.g., for path ['details', 'stock']: r['details'] = r['details'] || {}; r['details']['stock'] = value; - for (var i = 0; i < arrowPath.length - 1; i++) { - var pathSoFar = arrowPath.slice(0, i + 1); - var accessor = pathSoFar - .map(function (p) { - return "['" + escapeq(p) + "']"; - }) - .join(''); - sp += 'r' + accessor + ' = r' + accessor + ' || {};'; + query.removeKeys.push(key); + }); + } + return new Function('p,params,alasql', 'var y;' + s + 'return r'); + }; + + yy.Select.prototype.compileSelectGroup0 = function (query) { + var self = this; + + // Optimization: Build lookup structures upfront to avoid O(n*m) complexity in the main loop + // Only build these if GROUP BY exists, as they're only used for alias resolution + var groupByAliasMap = null; + var selectColumnNames = null; + + if (self.group) { + // Build map of GROUP BY columns that reference aliases (for O(1) lookup) + groupByAliasMap = {}; + self.group.forEach(function (gp, idx) { + if (gp instanceof yy.Column && gp.columnid && !gp.tableid) { + groupByAliasMap[gp.columnid] = idx; } - var fullAccessor = arrowPath - .map(function (p) { - return "['" + escapeq(p) + "']"; - }) - .join(''); - sp += 'r' + fullAccessor + ' = ' + valueJs + ';'; + }); - // Use the first part of the path as the column name for metadata - var colName = arrowPath[0]; - query.selectColumns[escapeq(colName)] = true; - var coldef = { - columnid: colName, - }; - // Only add if not already added - if (!query.xcolumns[coldef.columnid]) { - query.columns.push(coldef); - query.xcolumns[coldef.columnid] = coldef; + // Build set of actual column names in SELECT to distinguish pure aliases from column renames + // This prevents incorrect replacement of "GROUP BY b" when "SELECT a AS b, b AS c" exists + selectColumnNames = {}; + self.columns.forEach(function (col) { + if (col instanceof yy.Column && col.columnid) { + selectColumnNames[col.columnid] = true; } - } else { - ss.push( - "'" + - escapeq(col.as || col.columnid || col.toString()) + - "':" + - n2u(col.toJS('p', query.defaultTableid, query.defcols)) - ); - // ss.push('\''+escapeq(col.toString())+'\':'+col.toJS("p",query.defaultTableid)); - //if(col instanceof yy.Expression) { - query.selectColumns[escapeq(col.as || col.columnid || col.toString())] = true; - - var coldef = { - columnid: col.as || col.columnid || col.toString(), - // dbtypeid:tcol.dbtypeid, - // dbsize:tcol.dbsize, - // dbpecision:tcol.dbprecision, - // dbenum: tcol.dbenum, - }; - // console.log(2); - query.columns.push(coldef); - query.xcolumns[coldef.columnid] = coldef; - } + }); } - }); - s += ss.join(',') + '};' + sp; - return s; - //console.log(42,753,query.xcolumns, query.selectColumns); -}; -yy.Select.prototype.compileSelect2 = function (query, params) { - var s = query.selectfns; - // Only add order keys if there's no union operation (otherwise they'll be added later) - if ( - this.orderColumns && - this.orderColumns.length > 0 && - !this.union && - !this.unionall && - !this.except && - !this.intersect - ) { - this.orderColumns.forEach(function (v, idx) { - var key = '$$$' + idx; - // Handle positional column reference (for SELECT * with ORDER BY numeric) - if (v._useColumnIndex !== undefined) { - // Use Object.keys to get column names and access by index - s += "var keys=Object.keys(r);r['" + key + "']=r[keys[" + v.columnIndex + ']];'; - } else if (v instanceof yy.Column && query.xcolumns[v.columnid]) { - s += "r['" + key + "']=r['" + v.columnid + "'];"; - } else if (v instanceof yy.ParamValue && query.xcolumns[params[v.param]]) { - s += "r['" + key + "']=r['" + params[v.param] + "'];"; - } else { - s += "r['" + key + "']=" + v.toJS('p', query.defaultTableid, query.defcols) + ';'; - } - query.removeKeys.push(key); - }); - } - return new Function('p,params,alasql', 'var y;' + s + 'return r'); -}; - -yy.Select.prototype.compileSelectGroup0 = function (query) { - var self = this; - - // Optimization: Build lookup structures upfront to avoid O(n*m) complexity in the main loop - // Only build these if GROUP BY exists, as they're only used for alias resolution - var groupByAliasMap = null; - var selectColumnNames = null; - - if (self.group) { - // Build map of GROUP BY columns that reference aliases (for O(1) lookup) - groupByAliasMap = {}; - self.group.forEach(function (gp, idx) { - if (gp instanceof yy.Column && gp.columnid && !gp.tableid) { - groupByAliasMap[gp.columnid] = idx; - } - }); - - // Build set of actual column names in SELECT to distinguish pure aliases from column renames - // This prevents incorrect replacement of "GROUP BY b" when "SELECT a AS b, b AS c" exists - selectColumnNames = {}; - self.columns.forEach(function (col) { - if (col instanceof yy.Column && col.columnid) { - selectColumnNames[col.columnid] = true; - } - }); - } - self.columns.forEach(function (col, idx) { - if (!(col instanceof yy.Column && col.columnid === '*')) { - var colas; - // = col.as; - if (col instanceof yy.Column) { - colas = escapeq(col.columnid); - } else { - colas = escapeq(col.toString(true)); - // console.log(273,colas); - } - for (var i = 0; i < idx; i++) { - if (colas === self.columns[i].nick) { - colas = self.columns[i].nick + ':' + idx; - break; + self.columns.forEach(function (col, idx) { + if (!(col instanceof yy.Column && col.columnid === '*')) { + var colas; + // = col.as; + if (col instanceof yy.Column) { + colas = escapeq(col.columnid); + } else { + colas = escapeq(col.toString(true)); + // console.log(273,colas); } - } - // } - col.nick = colas; + for (var i = 0; i < idx; i++) { + if (colas === self.columns[i].nick) { + colas = self.columns[i].nick + ':' + idx; + break; + } + } + // } + col.nick = colas; - if (self.group) { - // Match GROUP BY columns to SELECT columns by columnid and tableid (for real columns) - var groupIdx = self.group.findIndex(function (gp) { - return gp.columnid === col.columnid && gp.tableid === col.tableid; - }); - if (groupIdx > -1) { - self.group[groupIdx].nick = colas; + if (self.group) { + // Match GROUP BY columns to SELECT columns by columnid and tableid (for real columns) + var groupIdx = self.group.findIndex(function (gp) { + return gp.columnid === col.columnid && gp.tableid === col.tableid; + }); + if (groupIdx > -1) { + self.group[groupIdx].nick = colas; + } + + // Also match GROUP BY columns that reference SELECT column aliases + // This handles cases like: SELECT CASE ... END AS age_group ... GROUP BY age_group + // Only apply if: + // 1. The SELECT column has an alias + // 2. That alias matches a GROUP BY column name + // 3. The alias is NOT an actual column name (pure alias, not renaming) + if ( + col.as && + groupByAliasMap && + groupByAliasMap.hasOwnProperty(col.as) && + !selectColumnNames[col.as] + ) { + var aliasGroupIdx = groupByAliasMap[col.as]; + // Replace the GROUP BY column reference with a deep copy of the SELECT expression + // We use deep cloning to ensure nested objects (like CASE whens/elses) are copied + var groupExpr = cloneDeep(col); + // Clear SELECT-specific properties that shouldn't be in GROUP BY + delete groupExpr.as; + groupExpr.nick = colas; + self.group[aliasGroupIdx] = groupExpr; + } } - // Also match GROUP BY columns that reference SELECT column aliases - // This handles cases like: SELECT CASE ... END AS age_group ... GROUP BY age_group - // Only apply if: - // 1. The SELECT column has an alias - // 2. That alias matches a GROUP BY column name - // 3. The alias is NOT an actual column name (pure alias, not renaming) if ( - col.as && - groupByAliasMap && - groupByAliasMap.hasOwnProperty(col.as) && - !selectColumnNames[col.as] + col.funcid && + (col.funcid.toUpperCase() === 'ROWNUM' || col.funcid.toUpperCase() === 'ROW_NUMBER') ) { - var aliasGroupIdx = groupByAliasMap[col.as]; - // Replace the GROUP BY column reference with a deep copy of the SELECT expression - // We use deep cloning to ensure nested objects (like CASE whens/elses) are copied - var groupExpr = cloneDeep(col); - // Clear SELECT-specific properties that shouldn't be in GROUP BY - delete groupExpr.as; - groupExpr.nick = colas; - self.group[aliasGroupIdx] = groupExpr; + // Check if this has OVER clause with PARTITION BY + if (col.over && col.over.partition) { + // Window function with partition - track for post-processing + query.grouprownums.push({ + as: col.as, + partitionColumns: col.over.partition.map(function (p) { + return p.columnid || p.toString(); + }), + }); + } else { + // Regular ROW_NUMBER without partition + query.rownums.push(col.as); + } } + if (col.funcid && col.funcid.toUpperCase() === 'GROUP_ROW_NUMBER') { + query.grouprownums.push({as: col.as, columnIndex: 0}); // Track which column to use for grouping + } + // console.log("colas:",colas); + // } + } else { + query.groupStar = col.tableid || 'default'; } + }); - if ( - col.funcid && - (col.funcid.toUpperCase() === 'ROWNUM' || col.funcid.toUpperCase() === 'ROW_NUMBER') - ) { - // Check if this has OVER clause with PARTITION BY - if (col.over && col.over.partition) { - // Window function with partition - track for post-processing - query.grouprownums.push({ - as: col.as, - partitionColumns: col.over.partition.map(function (p) { - return p.columnid || p.toString(); - }), - }); - } else { - // Regular ROW_NUMBER without partition - query.rownums.push(col.as); - } + this.columns.forEach(function (col) { + if (col.findAggregator) { + col.findAggregator(query); } - if (col.funcid && col.funcid.toUpperCase() === 'GROUP_ROW_NUMBER') { - query.grouprownums.push({as: col.as, columnIndex: 0}); // Track which column to use for grouping + }); + + if (this.having) { + if (this.having.findAggregator) { + this.having.findAggregator(query); } - // console.log("colas:",colas); - // } - } else { - query.groupStar = col.tableid || 'default'; } - }); + }; - this.columns.forEach(function (col) { - if (col.findAggregator) { - col.findAggregator(query); - } - }); + yy.Select.prototype.compileSelectGroup1 = function (query) { + var self = this; + var s = 'var r = {};'; - if (this.having) { - if (this.having.findAggregator) { - this.having.findAggregator(query); - } - } -}; - -yy.Select.prototype.compileSelectGroup1 = function (query) { - var self = this; - var s = 'var r = {};'; - - self.columns.forEach(function (col) { - // console.log(col); - if (col instanceof yy.Column && col.columnid === '*') { - // s += 'for(var k in g){r[k]=g[k]};'; - // s += 'for(var k in this.query.groupColumns){r[k]=g[this.query.groupColumns[k]]};'; - - s += 'for(var k in g) {r[k]=g[k]};'; - return ''; - - // console.log(query); - } else { - // var colas = col.as; - var colas = col.as; - if (colas === undefined) { - if (col instanceof yy.Column) { - colas = escapeq(col.columnid); - } else { - colas = col.nick; + self.columns.forEach(function (col) { + // console.log(col); + if (col instanceof yy.Column && col.columnid === '*') { + // s += 'for(var k in g){r[k]=g[k]};'; + // s += 'for(var k in this.query.groupColumns){r[k]=g[this.query.groupColumns[k]]};'; + + s += 'for(var k in g) {r[k]=g[k]};'; + return ''; + + // console.log(query); + } else { + // var colas = col.as; + var colas = col.as; + if (colas === undefined) { + if (col instanceof yy.Column) { + colas = escapeq(col.columnid); + } else { + colas = col.nick; + } } - } - query.groupColumns[colas] = col.nick; + query.groupColumns[colas] = col.nick; - /*/* if(typeof colas == 'undefined') { + /*/* if(typeof colas == 'undefined') { if(col instanceof yy.Column) { colas = col.columnid; } else { @@ -639,20 +651,20 @@ yy.Select.prototype.compileSelectGroup1 = function (query) { } } */ - // if(col.as) { - s += "r['" + colas + "']="; - // // } else { - // // s += 'r[\''+escapeq()+'\']='; - // // }; - // // s += ';'; - // console.log(col);//,col.toJS('g','')); + // if(col.as) { + s += "r['" + colas + "']="; + // // } else { + // // s += 'r[\''+escapeq()+'\']='; + // // }; + // // s += ';'; + // console.log(col);//,col.toJS('g','')); - if (col instanceof yy.Column) { - s += n2u(col.toJS('(this.groupSources.get(g) || g)', query.defaultTableid)) + ';'; - } else { - s += n2u(col.toJS('g', '')) + ';'; - } - /*/* + if (col instanceof yy.Column) { + s += n2u(col.toJS('(this.groupSources.get(g) || g)', query.defaultTableid)) + ';'; + } else { + s += n2u(col.toJS('g', '')) + ';'; + } + /*/* s += 'g[\''+col.nick+'\'];'; * / @@ -675,144 +687,144 @@ yy.Select.prototype.compileSelectGroup1 = function (query) { // } // s += col.toJS('g','')+';'; */ - //console.log(colas,query.removeKeys); - for (var i = 0; i < query.removeKeys.length; i++) { - // THis part should be intellectual - if (query.removeKeys[i] === colas) { - query.removeKeys.splice(i, 1); - break; + //console.log(colas,query.removeKeys); + for (var i = 0; i < query.removeKeys.length; i++) { + // THis part should be intellectual + if (query.removeKeys[i] === colas) { + query.removeKeys.splice(i, 1); + break; + } } } - } - }); - // return new Function('g,params,alasql',s+'return r'); - return s; -}; - -yy.Select.prototype.compileSelectGroup2 = function (query) { - var self = this; - var s = query.selectgfns; - var hasOrderColumns = - this.orderColumns && - this.orderColumns.length > 0 && - !this.union && - !this.unionall && - !this.except && - !this.intersect; - var needsOrderColumnMaps = - hasOrderColumns && - this.orderColumns.some(function (col) { - return col instanceof yy.Column; }); + // return new Function('g,params,alasql',s+'return r'); + return s; + }; + + yy.Select.prototype.compileSelectGroup2 = function (query) { + var self = this; + var s = query.selectgfns; + var hasOrderColumns = + this.orderColumns && + this.orderColumns.length > 0 && + !this.union && + !this.unionall && + !this.except && + !this.intersect; + var needsOrderColumnMaps = + hasOrderColumns && + this.orderColumns.some(function (col) { + return col instanceof yy.Column; + }); - // Create a lookup map for GROUP BY columns to optimize performance - var groupColMap = {}; - var groupProjectedColumnMap = {}; - var projectedSelectColumnMap = {}; - if (self.group) { - self.group.forEach(function (gp) { - var key = (gp.tableid || '') + '\t' + gp.columnid; - groupColMap[key] = gp; - }); + // Create a lookup map for GROUP BY columns to optimize performance + var groupColMap = {}; + var groupProjectedColumnMap = {}; + var projectedSelectColumnMap = {}; + if (self.group) { + self.group.forEach(function (gp) { + var key = (gp.tableid || '') + '\t' + gp.columnid; + groupColMap[key] = gp; + }); + if (needsOrderColumnMaps) { + self.columns.forEach(function (col) { + if (col instanceof yy.Column) { + var key = (col.tableid || '') + '\t' + col.columnid; + if (groupColMap[key]) { + if (!col.as) { + groupProjectedColumnMap[key] = col.columnid; + } else if (!groupProjectedColumnMap[key]) { + groupProjectedColumnMap[key] = col.as; + } + } + } + }); + } + } if (needsOrderColumnMaps) { self.columns.forEach(function (col) { - if (col instanceof yy.Column) { - var key = (col.tableid || '') + '\t' + col.columnid; - if (groupColMap[key]) { - if (!col.as) { - groupProjectedColumnMap[key] = col.columnid; - } else if (!groupProjectedColumnMap[key]) { - groupProjectedColumnMap[key] = col.as; - } + if (!(col instanceof yy.Column && col.columnid === '*')) { + var projectedSelectKey = col.as || (col instanceof yy.Column ? col.columnid : col.nick); + if (projectedSelectKey) { + projectedSelectColumnMap[projectedSelectKey] = true; } } }); } - } - if (needsOrderColumnMaps) { + self.columns.forEach(function (col) { - if (!(col instanceof yy.Column && col.columnid === '*')) { - var projectedSelectKey = col.as || (col instanceof yy.Column ? col.columnid : col.nick); - if (projectedSelectKey) { - projectedSelectColumnMap[projectedSelectKey] = true; - } + // console.log(col); + // Skip SELECT * columns as they are handled differently + if (col instanceof yy.Column && col.columnid === '*') { + return; + } + // Check if this column is part of GROUP BY + // For columns with renamed nicks (e.g., 'x:1'), we need to check the original columnid + var groupCol = null; + if (col instanceof yy.Column && self.group) { + var key = (col.tableid || '') + '\t' + col.columnid; + groupCol = groupColMap[key]; + } + var isInGroup = + (groupCol !== null && groupCol !== undefined) || query.ingroup.indexOf(col.nick) > -1; + if (isInGroup) { + // For columns in GROUP BY, use the GROUP BY column's nick if available + var groupNick = (groupCol && groupCol.nick) || col.nick; + s += "r['" + (col.as || col.nick) + "']=g['" + groupNick + "'];"; } }); - } - self.columns.forEach(function (col) { - // console.log(col); - // Skip SELECT * columns as they are handled differently - if (col instanceof yy.Column && col.columnid === '*') { - return; - } - // Check if this column is part of GROUP BY - // For columns with renamed nicks (e.g., 'x:1'), we need to check the original columnid - var groupCol = null; - if (col instanceof yy.Column && self.group) { - var key = (col.tableid || '') + '\t' + col.columnid; - groupCol = groupColMap[key]; - } - var isInGroup = - (groupCol !== null && groupCol !== undefined) || query.ingroup.indexOf(col.nick) > -1; - if (isInGroup) { - // For columns in GROUP BY, use the GROUP BY column's nick if available - var groupNick = (groupCol && groupCol.nick) || col.nick; - s += "r['" + (col.as || col.nick) + "']=g['" + groupNick + "'];"; + // Only add order keys if there's no union operation (otherwise they'll be added later) + if (hasOrderColumns) { + this.orderColumns.forEach(function (v, idx) { + // console.log(411,v); + var key = '$$$' + idx; + var groupKey = (v.tableid || '') + '\t' + v.columnid; + // console.log(427,v,query.groupColumns,query.xgroupColumns); + // Handle positional column reference (for SELECT * with ORDER BY numeric) + if (v._useColumnIndex !== undefined) { + // Use Object.keys to get column names and access by index + s += "var keys=Object.keys(r);r['" + key + "']=r[keys[" + v.columnIndex + ']];'; + } else if (v instanceof yy.Column && groupProjectedColumnMap[groupKey]) { + s += "r['" + key + "']=r['" + groupProjectedColumnMap[groupKey] + "'];"; + } else if (v instanceof yy.Column && projectedSelectColumnMap[v.columnid]) { + s += "r['" + key + "']=r['" + v.columnid + "'];"; + } else if (v instanceof yy.Column && groupColMap[groupKey]) { + s += "r['" + key + "']=g['" + groupColMap[groupKey].nick + "'];"; + } else { + s += "r['" + key + "']=" + v.toJS('g', '') + ';'; + } + query.removeKeys.push(key); + }); } - }); - - // Only add order keys if there's no union operation (otherwise they'll be added later) - if (hasOrderColumns) { - this.orderColumns.forEach(function (v, idx) { - // console.log(411,v); - var key = '$$$' + idx; - var groupKey = (v.tableid || '') + '\t' + v.columnid; - // console.log(427,v,query.groupColumns,query.xgroupColumns); - // Handle positional column reference (for SELECT * with ORDER BY numeric) - if (v._useColumnIndex !== undefined) { - // Use Object.keys to get column names and access by index - s += "var keys=Object.keys(r);r['" + key + "']=r[keys[" + v.columnIndex + ']];'; - } else if (v instanceof yy.Column && groupProjectedColumnMap[groupKey]) { - s += "r['" + key + "']=r['" + groupProjectedColumnMap[groupKey] + "'];"; - } else if (v instanceof yy.Column && projectedSelectColumnMap[v.columnid]) { - s += "r['" + key + "']=r['" + v.columnid + "'];"; - } else if (v instanceof yy.Column && groupColMap[groupKey]) { - s += "r['" + key + "']=g['" + groupColMap[groupKey].nick + "'];"; - } else { - s += "r['" + key + "']=" + v.toJS('g', '') + ';'; - } - query.removeKeys.push(key); - }); - } - //console.log(425,s); - // console.log('selectg:',s); - return new Function('g,params,alasql', 'var y;' + s + 'return r'); -}; - -// SELECY * REMOVE [COLUMNS] col-list, LIKE '' -yy.Select.prototype.compileRemoveColumns = function (query) { - var self = this; - if (typeof this.removecolumns !== 'undefined') { - query.removeKeys = query.removeKeys.concat( - this.removecolumns + //console.log(425,s); + // console.log('selectg:',s); + return new Function('g,params,alasql', 'var y;' + s + 'return r'); + }; + + // SELECY * REMOVE [COLUMNS] col-list, LIKE '' + yy.Select.prototype.compileRemoveColumns = function (query) { + var self = this; + if (typeof this.removecolumns !== 'undefined') { + query.removeKeys = query.removeKeys.concat( + this.removecolumns + .filter(function (column) { + return typeof column.like === 'undefined'; + }) + .map(function (column) { + return column.columnid; + }) + ); + + //console.log(query.removeKeys,this.removecolumns); + query.removeLikeKeys = this.removecolumns .filter(function (column) { - return typeof column.like === 'undefined'; + return typeof column.like !== 'undefined'; }) .map(function (column) { - return column.columnid; - }) - ); - - //console.log(query.removeKeys,this.removecolumns); - query.removeLikeKeys = this.removecolumns - .filter(function (column) { - return typeof column.like !== 'undefined'; - }) - .map(function (column) { - // return new RegExp((column.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g'); - return column.like.value; - }); - } -}; + // return new RegExp((column.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g'); + return column.like.value; + }); + } + }; } diff --git a/src/select/defcols.js b/src/select/defcols.js index e743afb00b..119a1b1e27 100755 --- a/src/select/defcols.js +++ b/src/select/defcols.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // Select run-time part for Alasql.js // Date: 03.11.2014 @@ -8,91 +8,91 @@ export default function install(alasql) { // */ -yy.Select.prototype.compileDefCols = function (query, databaseid) { - // console.log('defcols'); - var defcols = {'.': {}}; - if (this.from) { - this.from.forEach(function (fr) { - defcols['.'][fr.as || fr.tableid] = true; - if (fr instanceof yy.Table) { - var alias = fr.as || fr.tableid; - // console.log(alasql.databases[fr.databaseid || databaseid]); - // console.log(alasql.databases[fr.databaseid || databaseid].tables, fr.tableid); - //console.log(alasql.databases[fr.databaseid || databaseid].tables, fr.tableid); - //console.log(alasql.databases); - var table = alasql.databases[fr.databaseid || databaseid].tables[fr.tableid]; - //console.log(table); + yy.Select.prototype.compileDefCols = function (query, databaseid) { + // console.log('defcols'); + var defcols = {'.': {}}; + if (this.from) { + this.from.forEach(function (fr) { + defcols['.'][fr.as || fr.tableid] = true; + if (fr instanceof yy.Table) { + var alias = fr.as || fr.tableid; + // console.log(alasql.databases[fr.databaseid || databaseid]); + // console.log(alasql.databases[fr.databaseid || databaseid].tables, fr.tableid); + //console.log(alasql.databases[fr.databaseid || databaseid].tables, fr.tableid); + //console.log(alasql.databases); + var table = alasql.databases[fr.databaseid || databaseid].tables[fr.tableid]; + //console.log(table); - if (undefined === table) { - throw new Error('Table does not exist: ' + fr.tableid); - } + if (undefined === table) { + throw new Error('Table does not exist: ' + fr.tableid); + } - if (table.columns) { - table.columns.forEach(function (col) { - if (defcols[col.columnid]) { - defcols[col.columnid] = '-'; // Ambigous - } else { - defcols[col.columnid] = alias; - } - }); + if (table.columns) { + table.columns.forEach(function (col) { + if (defcols[col.columnid]) { + defcols[col.columnid] = '-'; // Ambigous + } else { + defcols[col.columnid] = alias; + } + }); + } + } else if (fr instanceof yy.Select) { + } else if (fr instanceof yy.Search) { + } else if (fr instanceof yy.ParamValue) { + } else if (fr instanceof yy.VarValue) { + } else if (fr instanceof yy.FuncValue) { + } else if (fr instanceof yy.FromData) { + } else if (fr instanceof yy.Json) { + } else if (fr.inserted) { + } else { + // console.log(fr); + throw new Error('Unknown type of FROM clause'); } - } else if (fr instanceof yy.Select) { - } else if (fr instanceof yy.Search) { - } else if (fr instanceof yy.ParamValue) { - } else if (fr instanceof yy.VarValue) { - } else if (fr instanceof yy.FuncValue) { - } else if (fr instanceof yy.FromData) { - } else if (fr instanceof yy.Json) { - } else if (fr.inserted) { - } else { - // console.log(fr); - throw new Error('Unknown type of FROM clause'); - } - }); - } + }); + } - if (this.joins) { - this.joins.forEach(function (jn) { - defcols['.'][jn.as || jn.table.tableid] = true; + if (this.joins) { + this.joins.forEach(function (jn) { + defcols['.'][jn.as || jn.table.tableid] = true; - // console.log(jn); - if (jn.table) { - var alias = jn.as || jn.table.tableid; - var databaseId = jn.table.databaseid || databaseid; - var database = alasql.databases[databaseId]; + // console.log(jn); + if (jn.table) { + var alias = jn.as || jn.table.tableid; + var databaseId = jn.table.databaseid || databaseid; + var database = alasql.databases[databaseId]; - if (database === undefined) { - throw new Error('Database does not exist: ' + databaseId); - } + if (database === undefined) { + throw new Error('Database does not exist: ' + databaseId); + } - var table = database.tables[jn.table.tableid]; - // console.log(jn.table.tableid, jn.table.databaseid); + var table = database.tables[jn.table.tableid]; + // console.log(jn.table.tableid, jn.table.databaseid); - if (table === undefined) { - throw new Error('Table does not exist: ' + jn.table.tableid); - } + if (table === undefined) { + throw new Error('Table does not exist: ' + jn.table.tableid); + } - if (table.columns) { - table.columns.forEach(function (col) { - if (defcols[col.columnid]) { - defcols[col.columnid] = '-'; // Ambigous - } else { - defcols[col.columnid] = alias; - } - }); + if (table.columns) { + table.columns.forEach(function (col) { + if (defcols[col.columnid]) { + defcols[col.columnid] = '-'; // Ambigous + } else { + defcols[col.columnid] = alias; + } + }); + } + } else if (jn.select) { + } else if (jn.param) { + } else if (jn.func) { + } else { + throw new Error('Unknown type of FROM clause'); } - } else if (jn.select) { - } else if (jn.param) { - } else if (jn.func) { - } else { - throw new Error('Unknown type of FROM clause'); - } - }); - } - // for(var k in defcols) { - // if(defcols[k] == '-') defcols[k] = undefined; - // } - // console.log(89,defcols); - return defcols; -}; + }); + } + // for(var k in defcols) { + // if(defcols[k] == '-') defcols[k] = undefined; + // } + // console.log(89,defcols); + return defcols; + }; } diff --git a/src/select/dojoin.js b/src/select/dojoin.js index 71272c89bd..b8f41fb452 100755 --- a/src/select/dojoin.js +++ b/src/select/dojoin.js @@ -1,187 +1,187 @@ export default function install(alasql, internal) { -// -// Join all lines over sources -// - -function doJoin(query, scope, h) { - // Check, if this is a last join? - if (h >= query.sources.length) { - // Todo: check if this runs once too many - // Then apply where and select - if (query.wherefn(scope, query.params, alasql)) { - // If there is a GROUP BY then pipe to grouping function - if (query.groupfn) { - query.groupfn(scope, query.params, alasql); - } else { - query.data.push(query.selectfn(scope, query.params, alasql)); - } - } - } else if (query.sources[h].applyselect) { - var source = query.sources[h]; - source.applyselect( - query.params, - function (data) { - if (data.length > 0) { - for (var i = 0; i < data.length; i++) { - scope[source.alias] = data[i]; - doJoin(query, scope, h + 1); - } + // + // Join all lines over sources + // + + function doJoin(query, scope, h) { + // Check, if this is a last join? + if (h >= query.sources.length) { + // Todo: check if this runs once too many + // Then apply where and select + if (query.wherefn(scope, query.params, alasql)) { + // If there is a GROUP BY then pipe to grouping function + if (query.groupfn) { + query.groupfn(scope, query.params, alasql); } else { - if (source.applymode == 'OUTER') { - scope[source.alias] = {}; - doJoin(query, scope, h + 1); + query.data.push(query.selectfn(scope, query.params, alasql)); + } + } + } else if (query.sources[h].applyselect) { + var source = query.sources[h]; + source.applyselect( + query.params, + function (data) { + if (data.length > 0) { + for (var i = 0; i < data.length; i++) { + scope[source.alias] = data[i]; + doJoin(query, scope, h + 1); + } + } else { + if (source.applymode == 'OUTER') { + scope[source.alias] = {}; + doJoin(query, scope, h + 1); + } } + }, + scope + ); + } else { + // STEP 1 + + let source = query.sources[h]; + let nextsource = query.sources[h + 1]; + + let tableid = source.alias || source.tableid; + let pass = false; // For LEFT JOIN + let data = source.data; + let opt = false; + + // Reduce data for looping if there is optimization hint + if (!source.getfn || (source.getfn && !source.dontcache)) { + if ( + source.joinmode != 'RIGHT' && + source.joinmode != 'OUTER' && + source.joinmode != 'ANTI' && + source.optimization == 'ix' + ) { + data = source.ix[source.onleftfn(scope, query.params, alasql)] || []; + opt = true; } - }, - scope - ); - } else { - // STEP 1 - - let source = query.sources[h]; - let nextsource = query.sources[h + 1]; - - let tableid = source.alias || source.tableid; - let pass = false; // For LEFT JOIN - let data = source.data; - let opt = false; - - // Reduce data for looping if there is optimization hint - if (!source.getfn || (source.getfn && !source.dontcache)) { - if ( - source.joinmode != 'RIGHT' && - source.joinmode != 'OUTER' && - source.joinmode != 'ANTI' && - source.optimization == 'ix' - ) { - data = source.ix[source.onleftfn(scope, query.params, alasql)] || []; - opt = true; } - } - // Main cycle - let i = 0; - if (typeof data == 'undefined') { - throw new Error('Data source number ' + h + ' in undefined'); - } - let ilen = data.length; - let dataw; - // console.log(h,opt,source.data,i,source.dontcache); - while ((dataw = data[i]) || (!opt && source.getfn && (dataw = source.getfn(i))) || i < ilen) { - if (!opt && source.getfn && !source.dontcache) data[i] = dataw; - scope[tableid] = dataw; - - // Reduce with ON and USING clause - var usingPassed = !source.onleftfn; - if (!usingPassed) { - var left = source.onleftfn(scope, query.params, alasql); - var right = source.onrightfn(scope, query.params, alasql); - if (left instanceof String || left instanceof Number) left = left.valueOf(); - if (right instanceof String || right instanceof Number) right = left.valueOf(); - usingPassed = left == right; + // Main cycle + let i = 0; + if (typeof data == 'undefined') { + throw new Error('Data source number ' + h + ' in undefined'); } + let ilen = data.length; + let dataw; + // console.log(h,opt,source.data,i,source.dontcache); + while ((dataw = data[i]) || (!opt && source.getfn && (dataw = source.getfn(i))) || i < ilen) { + if (!opt && source.getfn && !source.dontcache) data[i] = dataw; + scope[tableid] = dataw; + + // Reduce with ON and USING clause + var usingPassed = !source.onleftfn; + if (!usingPassed) { + var left = source.onleftfn(scope, query.params, alasql); + var right = source.onrightfn(scope, query.params, alasql); + if (left instanceof String || left instanceof Number) left = left.valueOf(); + if (right instanceof String || right instanceof Number) right = left.valueOf(); + usingPassed = left == right; + } - if (usingPassed) { - // For all non-standard JOINs like a-b=0 - if (source.onmiddlefn(scope, query.params, alasql)) { - // Recursively call new join - if (source.joinmode != 'SEMI' && source.joinmode != 'ANTI') { - doJoin(query, scope, h + 1); - } - if (source.joinmode != 'LEFT' && source.joinmode != 'INNER') { - dataw._rightjoin = true; - } + if (usingPassed) { + // For all non-standard JOINs like a-b=0 + if (source.onmiddlefn(scope, query.params, alasql)) { + // Recursively call new join + if (source.joinmode != 'SEMI' && source.joinmode != 'ANTI') { + doJoin(query, scope, h + 1); + } + if (source.joinmode != 'LEFT' && source.joinmode != 'INNER') { + dataw._rightjoin = true; + } - // for LEFT JOIN - pass = true; + // for LEFT JOIN + pass = true; + } } + i++; } - i++; - } - - // Additional join for LEFT JOINS - if ( - (source.joinmode == 'LEFT' || source.joinmode == 'OUTER' || source.joinmode == 'SEMI') && - !pass - ) { - // Clear the scope after the loop - scope[tableid] = {}; - doJoin(query, scope, h + 1); - } - - // STEP 2 - if (h == 0) { - for (var nh = h + 1; nh < query.sources.length; nh++) { - if ( - nextsource.joinmode == 'OUTER' || - nextsource.joinmode == 'RIGHT' || - nextsource.joinmode == 'ANTI' - ) { - scope[source.alias] = {}; + // Additional join for LEFT JOINS + if ( + (source.joinmode == 'LEFT' || source.joinmode == 'OUTER' || source.joinmode == 'SEMI') && + !pass + ) { + // Clear the scope after the loop + scope[tableid] = {}; + doJoin(query, scope, h + 1); + } - let j = 0; - let jlen = nextsource.data.length; - let dataw; + // STEP 2 - while ( - (dataw = nextsource.data[j]) || - (nextsource.getfn && (dataw = nextsource.getfn(j))) || - j < jlen + if (h == 0) { + for (var nh = h + 1; nh < query.sources.length; nh++) { + if ( + nextsource.joinmode == 'OUTER' || + nextsource.joinmode == 'RIGHT' || + nextsource.joinmode == 'ANTI' ) { - if (nextsource.getfn && !nextsource.dontcache) { - nextsource.data[j] = dataw; - } + scope[source.alias] = {}; - // console.log(169,dataw._rightjoin,scope); - if (dataw._rightjoin) { - delete dataw._rightjoin; - } else { - // delete dataw._rightjoin; - // console.log(163,h,scope); - scope[nextsource.alias] = dataw; - doJoin(query, scope, nh + 1); + let j = 0; + let jlen = nextsource.data.length; + let dataw; + + while ( + (dataw = nextsource.data[j]) || + (nextsource.getfn && (dataw = nextsource.getfn(j))) || + j < jlen + ) { + if (nextsource.getfn && !nextsource.dontcache) { + nextsource.data[j] = dataw; + } + + // console.log(169,dataw._rightjoin,scope); + if (dataw._rightjoin) { + delete dataw._rightjoin; + } else { + // delete dataw._rightjoin; + // console.log(163,h,scope); + scope[nextsource.alias] = dataw; + doJoin(query, scope, nh + 1); + } + j++; } - j++; + // debugger; + } else { + //console.log(180,scope); } - // debugger; - } else { - //console.log(180,scope); + source = query.sources[nh]; + nextsource = query.sources[nh + 1]; } - source = query.sources[nh]; - nextsource = query.sources[nh + 1]; } - } - scope[tableid] = undefined; + scope[tableid] = undefined; + } } -} -function swapSources(query, h) { - var source = query.sources[h]; - var nextsource = query.sources[h + 1]; - - let onleftfn = source.onleftfn; - let onleftfns = source.onleftfns; - let onrightfn = source.onrightfn; - let onrightfns = source.onrightfns; - let optimization = source.optimization; - - source.onleftfn = nextsource.onrightfn; - source.onleftfns = nextsource.onrightfns; - source.onrightfn = nextsource.onleftfn; - source.onrightfns = nextsource.onleftfns; - source.optimization = nextsource.optimization; - - nextsource.onleftfn = onleftfn; - nextsource.onleftfns = onleftfns; - nextsource.onrightfn = onrightfn; - nextsource.onrightfns = onrightfns; - nextsource.optimization = optimization; - - query.sources[h] = nextsource; - query.sources[h + 1] = source; -} + function swapSources(query, h) { + var source = query.sources[h]; + var nextsource = query.sources[h + 1]; + + let onleftfn = source.onleftfn; + let onleftfns = source.onleftfns; + let onrightfn = source.onrightfn; + let onrightfns = source.onrightfns; + let optimization = source.optimization; + + source.onleftfn = nextsource.onrightfn; + source.onleftfns = nextsource.onrightfns; + source.onrightfn = nextsource.onleftfn; + source.onrightfns = nextsource.onleftfns; + source.optimization = nextsource.optimization; + + nextsource.onleftfn = onleftfn; + nextsource.onleftfns = onleftfns; + nextsource.onrightfn = onrightfn; + nextsource.onrightfns = onrightfns; + nextsource.optimization = optimization; + + query.sources[h] = nextsource; + query.sources[h + 1] = source; + } internal.doJoin = doJoin; } diff --git a/src/select/exists.js b/src/select/exists.js index efea0fee2c..11c0850076 100755 --- a/src/select/exists.js +++ b/src/select/exists.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // EXISTS and other subqueries functions for AlaSQL.js // Date: 03.11.2014 @@ -9,50 +9,50 @@ export default function install(alasql) { // */ -yy.ExistsValue = class ExistsValue { - constructor(params) { - Object.assign(this, params); - } + yy.ExistsValue = class ExistsValue { + constructor(params) { + Object.assign(this, params); + } - toString() { - return 'EXISTS(' + this.value.toString() + ')'; - } + toString() { + return 'EXISTS(' + this.value.toString() + ')'; + } - toType() { - return 'boolean'; - } + toType() { + return 'boolean'; + } - toJS(context, tableid, defcols) { - // Updated to return a boolean value correctly - return `!!this.existsfn[${this.existsidx}](params, null, ${context}).data.length`; - } -}; + toJS(context, tableid, defcols) { + // Updated to return a boolean value correctly + return `!!this.existsfn[${this.existsidx}](params, null, ${context}).data.length`; + } + }; -// -// Prepare subqueries and EXISTS -// -alasql.precompile = function (statement, databaseid, params) { - if (!statement) return; - statement.params = params; - // For CREATE VIEW statements, move queries to the select object - // so they can be compiled later when the view is first used - if (statement.view && statement.select && statement.queries) { - statement.select.queries = statement.queries; - return; - } - if (statement.queries) { - statement.queriesfn = statement.queries.map(function (q) { - var nq = q.compile(databaseid || statement.database.databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } - if (statement.exists) { - statement.existsfn = statement.exists.map(function (ex) { - var nq = ex.compile(databaseid || statement.database.databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } -}; + // + // Prepare subqueries and EXISTS + // + alasql.precompile = function (statement, databaseid, params) { + if (!statement) return; + statement.params = params; + // For CREATE VIEW statements, move queries to the select object + // so they can be compiled later when the view is first used + if (statement.view && statement.select && statement.queries) { + statement.select.queries = statement.queries; + return; + } + if (statement.queries) { + statement.queriesfn = statement.queries.map(function (q) { + var nq = q.compile(databaseid || statement.database.databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); + } + if (statement.exists) { + statement.existsfn = statement.exists.map(function (ex) { + var nq = ex.compile(databaseid || statement.database.databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); + } + }; } diff --git a/src/select/from.js b/src/select/from.js index c2971e42c7..f3a1fe01d3 100755 --- a/src/select/from.js +++ b/src/select/from.js @@ -2,7 +2,7 @@ import {returnTrue} from '../utils/helpers.js'; export default function install(alasql, internal) { var yy = alasql.yy; -/* + /* // // Select compiler part for Alasql.js // Date: 03.11.2014 @@ -10,210 +10,210 @@ export default function install(alasql, internal) { // */ -/* global yy, alasql, Mongo, returnTrue */ + /* global yy, alasql, Mongo, returnTrue */ -// Rows of a subquery in FROM or JOIN, also when the subquery reads a file -function subqueryDatafn(source) { - return (query, params, cb, idx, alasql) => { - // Subqueries parsed inside this subselect (e.g. a scalar subquery in its - // column list) are hoisted to the statement-level queries list, so share - // the compiled list with the subselect - if (!source.subquery.query.queriesfn && query.queriesfn) { - source.subquery.query.queriesfn = query.queriesfn; - } - let res; - source.subquery(query.params, data => { - res = data.data; - if (cb) res = cb(res, idx, query); - }); - return res; - }; -} - -yy.Select.prototype.compileFrom = function (query) { - const self = this; - query.sources = []; - query.aliases = {}; + // Rows of a subquery in FROM or JOIN, also when the subquery reads a file + function subqueryDatafn(source) { + return (query, params, cb, idx, alasql) => { + // Subqueries parsed inside this subselect (e.g. a scalar subquery in its + // column list) are hoisted to the statement-level queries list, so share + // the compiled list with the subselect + if (!source.subquery.query.queriesfn && query.queriesfn) { + source.subquery.query.queriesfn = query.queriesfn; + } + let res; + source.subquery(query.params, data => { + res = data.data; + if (cb) res = cb(res, idx, query); + }); + return res; + }; + } - if (!self.from) return; + yy.Select.prototype.compileFrom = function (query) { + const self = this; + query.sources = []; + query.aliases = {}; - self.from.forEach(tq => { - const alias = tq.as || tq.tableid; - if (tq instanceof yy.Table) { - query.aliases[alias] = { - tableid: tq.tableid, - databaseid: tq.databaseid || query.database.databaseid, - type: 'table', - }; - } else if (tq instanceof yy.Select) { - query.aliases[alias] = {type: 'subquery'}; - } else if (tq instanceof yy.Search) { - query.aliases[alias] = {type: 'subsearch'}; - } else if (tq instanceof yy.ParamValue) { - query.aliases[alias] = {type: 'paramvalue'}; - } else if (tq instanceof yy.FuncValue) { - query.aliases[alias] = {type: 'funcvalue'}; - } else if (tq instanceof yy.VarValue) { - query.aliases[alias] = {type: 'varvalue'}; - } else if (tq instanceof yy.FromData) { - query.aliases[alias] = {type: 'fromdata'}; - } else if (tq instanceof yy.Json) { - query.aliases[alias] = {type: 'json'}; - } else if (tq.inserted) { - query.aliases[alias] = {type: 'inserted'}; - } else { - throw new Error('Wrong table at FROM'); - } + if (!self.from) return; - const source = { - alias: alias, - databaseid: tq.databaseid || query.database.databaseid, - tableid: tq.tableid, - joinmode: 'INNER', - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - }; - - if (tq instanceof yy.Table) { - source.columns = alasql.databases[source.databaseid].tables[source.tableid].columns; - if ( - alasql.options.autocommit && - alasql.databases[source.databaseid].engineid && - !alasql.databases[source.databaseid].tables[source.tableid].view - ) { - source.datafn = (query, params, cb, idx, alasql) => { - return alasql.engines[alasql.databases[source.databaseid].engineid].fromTable( - source.databaseid, - source.tableid, - cb, - idx, - query - ); - }; - } else if (alasql.databases[source.databaseid].tables[source.tableid].view) { - source.datafn = (query, params, cb, idx, alasql) => { - let table = alasql.databases[source.databaseid].tables[source.tableid]; - // Lazily compile the view's select on first use, and again after a schema change - if ( - table.viewSelect && - (!table.select || table.selectSchemaVersion !== alasql.schemaVersion) - ) { - table.select = table.viewSelect.compile(table.viewDatabaseid); - table.selectSchemaVersion = alasql.schemaVersion; - } - let res = table.select(params); - if (cb) res = cb(res, idx, query); - return res; + self.from.forEach(tq => { + const alias = tq.as || tq.tableid; + if (tq instanceof yy.Table) { + query.aliases[alias] = { + tableid: tq.tableid, + databaseid: tq.databaseid || query.database.databaseid, + type: 'table', }; + } else if (tq instanceof yy.Select) { + query.aliases[alias] = {type: 'subquery'}; + } else if (tq instanceof yy.Search) { + query.aliases[alias] = {type: 'subsearch'}; + } else if (tq instanceof yy.ParamValue) { + query.aliases[alias] = {type: 'paramvalue'}; + } else if (tq instanceof yy.FuncValue) { + query.aliases[alias] = {type: 'funcvalue'}; + } else if (tq instanceof yy.VarValue) { + query.aliases[alias] = {type: 'varvalue'}; + } else if (tq instanceof yy.FromData) { + query.aliases[alias] = {type: 'fromdata'}; + } else if (tq instanceof yy.Json) { + query.aliases[alias] = {type: 'json'}; + } else if (tq.inserted) { + query.aliases[alias] = {type: 'inserted'}; } else { - source.datafn = (query, params, cb, idx, alasql) => { - let res = alasql.databases[source.databaseid].tables[source.tableid].data; - if (cb) res = cb(res, idx, query); - return res; - }; - } - } else if (tq instanceof yy.Select) { - source.subquery = tq.compile(query.database.databaseid); - if (typeof source.subquery.query.modifier === 'undefined') { - source.subquery.query.modifier = 'RECORDSET'; + throw new Error('Wrong table at FROM'); } - // If the subquery could not resolve all its columns at compile time - // (e.g. SELECT * over parameter data), its columns list is incomplete, - // so leave the columns unknown and let SELECT * expand from the data - source.columns = source.subquery.query.dirtyColumns ? [] : source.subquery.query.columns; - source.datafn = subqueryDatafn(source); - } else if (tq instanceof yy.Search) { - source.subsearch = tq; - source.columns = []; - source.datafn = (query, params, cb, idx, alasql) => { - let res; - source.subsearch.execute(query.database.databaseid, query.params, data => { - res = data; - if (cb) res = cb(res, idx, query); - }); - return res; + const source = { + alias: alias, + databaseid: tq.databaseid || query.database.databaseid, + tableid: tq.tableid, + joinmode: 'INNER', + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, }; - } else if (tq instanceof yy.ParamValue) { - let ps = `var res = alasql.prepareFromData(params['${tq.param}']`; - if (tq.array) ps += ',true'; - ps += ');if(cb)res=cb(res,idx,query);return res'; - source.datafn = new Function('query,params,cb,idx,alasql', ps); - } else if (tq.inserted) { - let ps = 'var res = alasql.prepareFromData(alasql.inserted'; - if (tq.array) ps += ',true'; - ps += ');if(cb)res=cb(res,idx,query);return res'; - source.datafn = new Function('query,params,cb,idx,alasql', ps); - } else if (tq instanceof yy.Json) { - let ps = 'var res = alasql.prepareFromData(' + tq.toJS(); - if (tq.array) ps += ',true'; - ps += ');if(cb)res=cb(res,idx,query);return res'; - source.datafn = new Function('query,params,cb,idx,alasql', ps); - } else if (tq instanceof yy.VarValue) { - let ps = `var res = alasql.prepareFromData(alasql.vars['${tq.variable}']`; - if (tq.array) ps += ',true'; - ps += ');if(cb)res=cb(res,idx,query);return res'; - source.datafn = new Function('query,params,cb,idx,alasql', ps); - } else if (tq instanceof yy.FuncValue) { - let ps = 'var res=alasql.from[' + JSON.stringify(tq.funcid.toUpperCase()) + ']('; - if (tq.args && tq.args.length > 0) { - if (tq.args[0]) { - ps += tq.args[0].toJS('query.oldscope') + ','; + if (tq instanceof yy.Table) { + source.columns = alasql.databases[source.databaseid].tables[source.tableid].columns; + if ( + alasql.options.autocommit && + alasql.databases[source.databaseid].engineid && + !alasql.databases[source.databaseid].tables[source.tableid].view + ) { + source.datafn = (query, params, cb, idx, alasql) => { + return alasql.engines[alasql.databases[source.databaseid].engineid].fromTable( + source.databaseid, + source.tableid, + cb, + idx, + query + ); + }; + } else if (alasql.databases[source.databaseid].tables[source.tableid].view) { + source.datafn = (query, params, cb, idx, alasql) => { + let table = alasql.databases[source.databaseid].tables[source.tableid]; + // Lazily compile the view's select on first use, and again after a schema change + if ( + table.viewSelect && + (!table.select || table.selectSchemaVersion !== alasql.schemaVersion) + ) { + table.select = table.viewSelect.compile(table.viewDatabaseid); + table.selectSchemaVersion = alasql.schemaVersion; + } + let res = table.select(params); + if (cb) res = cb(res, idx, query); + return res; + }; } else { - ps += 'null,'; + source.datafn = (query, params, cb, idx, alasql) => { + let res = alasql.databases[source.databaseid].tables[source.tableid].data; + if (cb) res = cb(res, idx, query); + return res; + }; + } + } else if (tq instanceof yy.Select) { + source.subquery = tq.compile(query.database.databaseid); + if (typeof source.subquery.query.modifier === 'undefined') { + source.subquery.query.modifier = 'RECORDSET'; } + // If the subquery could not resolve all its columns at compile time + // (e.g. SELECT * over parameter data), its columns list is incomplete, + // so leave the columns unknown and let SELECT * expand from the data + source.columns = source.subquery.query.dirtyColumns ? [] : source.subquery.query.columns; + + source.datafn = subqueryDatafn(source); + } else if (tq instanceof yy.Search) { + source.subsearch = tq; + source.columns = []; + source.datafn = (query, params, cb, idx, alasql) => { + let res; + source.subsearch.execute(query.database.databaseid, query.params, data => { + res = data; + if (cb) res = cb(res, idx, query); + }); + return res; + }; + } else if (tq instanceof yy.ParamValue) { + let ps = `var res = alasql.prepareFromData(params['${tq.param}']`; + if (tq.array) ps += ',true'; + ps += ');if(cb)res=cb(res,idx,query);return res'; + source.datafn = new Function('query,params,cb,idx,alasql', ps); + } else if (tq.inserted) { + let ps = 'var res = alasql.prepareFromData(alasql.inserted'; + if (tq.array) ps += ',true'; + ps += ');if(cb)res=cb(res,idx,query);return res'; + source.datafn = new Function('query,params,cb,idx,alasql', ps); + } else if (tq instanceof yy.Json) { + let ps = 'var res = alasql.prepareFromData(' + tq.toJS(); + if (tq.array) ps += ',true'; + ps += ');if(cb)res=cb(res,idx,query);return res'; + source.datafn = new Function('query,params,cb,idx,alasql', ps); + } else if (tq instanceof yy.VarValue) { + let ps = `var res = alasql.prepareFromData(alasql.vars['${tq.variable}']`; + if (tq.array) ps += ',true'; + ps += ');if(cb)res=cb(res,idx,query);return res'; + source.datafn = new Function('query,params,cb,idx,alasql', ps); + } else if (tq instanceof yy.FuncValue) { + let ps = 'var res=alasql.from[' + JSON.stringify(tq.funcid.toUpperCase()) + ']('; - if (tq.args[1]) { - ps += tq.args[1].toJS('query.oldscope') + ','; + if (tq.args && tq.args.length > 0) { + if (tq.args[0]) { + ps += tq.args[0].toJS('query.oldscope') + ','; + } else { + ps += 'null,'; + } + + if (tq.args[1]) { + ps += tq.args[1].toJS('query.oldscope') + ','; + } else { + ps += 'null,'; + } } else { - ps += 'null,'; + ps += 'null,null,'; } + ps += 'cb,idx,query); return res'; + source.datafn = new Function('query,params,cb,idx,alasql', ps); + } else if (tq instanceof yy.FromData) { + source.datafn = (query, params, cb, idx, alasql) => { + let res = tq.data; + if (cb) res = cb(res, idx, query); + return res; + }; } else { - ps += 'null,null,'; + throw new Error('Wrong table at FROM'); } - ps += 'cb,idx,query); return res'; - source.datafn = new Function('query,params,cb,idx,alasql', ps); - } else if (tq instanceof yy.FromData) { - source.datafn = (query, params, cb, idx, alasql) => { - let res = tq.data; - if (cb) res = cb(res, idx, query); - return res; - }; - } else { - throw new Error('Wrong table at FROM'); - } - query.sources.push(source); - }); - query.defaultTableid = query.sources[0].alias; -}; + query.sources.push(source); + }); + query.defaultTableid = query.sources[0].alias; + }; -alasql.prepareFromData = function (data, array) { - let res = data; - if (typeof data === 'string') { - res = data.split(/\r?\n/); - if (array) { - res = res.map(item => [item]); - } - } else if (array) { - res = data.map(item => [item]); - } else if (typeof data === 'object' && !Array.isArray(data)) { - if ( - typeof Mongo !== 'undefined' && - typeof Mongo.Collection !== 'undefined' && - data instanceof Mongo.Collection - ) { - res = data.find().fetch(); - } else { - res = []; - for (const key in data) { - if (data.hasOwnProperty(key)) res.push([key, data[key]]); + alasql.prepareFromData = function (data, array) { + let res = data; + if (typeof data === 'string') { + res = data.split(/\r?\n/); + if (array) { + res = res.map(item => [item]); + } + } else if (array) { + res = data.map(item => [item]); + } else if (typeof data === 'object' && !Array.isArray(data)) { + if ( + typeof Mongo !== 'undefined' && + typeof Mongo.Collection !== 'undefined' && + data instanceof Mongo.Collection + ) { + res = data.find().fetch(); + } else { + res = []; + for (const key in data) { + if (data.hasOwnProperty(key)) res.push([key, data[key]]); + } } } - } - return res; -}; + return res; + }; internal.subqueryDatafn = subqueryDatafn; } diff --git a/src/select/groupby.js b/src/select/groupby.js index 5f8c800c70..d7a4bee4d9 100755 --- a/src/select/groupby.js +++ b/src/select/groupby.js @@ -3,7 +3,7 @@ import {arrayDiff, arrayUnion} from '../utils/helpers.js'; export default function install(alasql, internal) { var yy = alasql.yy; var decartes = internal.decartes; -/* + /* // // Select compiler part for Alasql.js // Date: 03.11.2014 @@ -11,212 +11,219 @@ export default function install(alasql, internal) { // */ -/** - * Helper to get GROUP_CONCAT parameters for compilation - * @param {Object} col - Column with aggregatorid and funcid - * @returns {string} - Comma-separated parameter string for aggregator call - */ -function getGroupConcatParams(col) { - if (!col.funcid || col.funcid.toUpperCase() !== 'GROUP_CONCAT') { - return ''; + /** + * Helper to get GROUP_CONCAT parameters for compilation + * @param {Object} col - Column with aggregatorid and funcid + * @returns {string} - Comma-separated parameter string for aggregator call + */ + function getGroupConcatParams(col) { + if (!col.funcid || col.funcid.toUpperCase() !== 'GROUP_CONCAT') { + return ''; + } + const separator = col.separator !== undefined ? JSON.stringify(col.separator) : 'undefined'; + // Extract direction from order expressions (MySQL GROUP_CONCAT orders by the value itself) + const orderDir = + col.order && col.order.length > 0 && col.order[0].direction + ? JSON.stringify(col.order[0].direction) + : 'undefined'; + return `,${separator},${orderDir}`; } - const separator = col.separator !== undefined ? JSON.stringify(col.separator) : 'undefined'; - // Extract direction from order expressions (MySQL GROUP_CONCAT orders by the value itself) - const orderDir = - col.order && col.order.length > 0 && col.order[0].direction - ? JSON.stringify(col.order[0].direction) - : 'undefined'; - return `,${separator},${orderDir}`; -} -/** + /** Compile group of statements */ -yy.Select.prototype.compileGroup = function (query) { - // console.log(this.group); - if (query.sources.length > 0) { - var tableid = query.sources[0].alias; - } else { - // If SELECT contains group aggregators without source tables - var tableid = ''; - } - var defcols = query.defcols; - query.groupSources = new WeakMap(); - var allgroup = [[]]; - if (this.group) { - allgroup = decartes(this.group, query); - } - // Union all arrays to get a maximum - var allgroups = []; - allgroup.forEach(function (a) { - allgroups = arrayUnion(allgroups, a); - }); - - query.allgroups = allgroups; - - query.ingroup = []; - var s = ''; - allgroup.forEach(function (agroup, setIdx) { - // Start of group function - s += 'var g=this.xgroups['; - - // Array with group columns from record - var rg = agroup.map(function (col2) { - var columnid = col2.split('\t')[0]; - var coljs = col2.split('\t')[1]; - // Check, if aggregator exists but GROUP BY is not exists - if (columnid === '') { - return '1'; // Create fictive grouping column for fictive GROUP BY - } - query.ingroup.push(columnid); - return coljs; + yy.Select.prototype.compileGroup = function (query) { + // console.log(this.group); + if (query.sources.length > 0) { + var tableid = query.sources[0].alias; + } else { + // If SELECT contains group aggregators without source tables + var tableid = ''; + } + var defcols = query.defcols; + query.groupSources = new WeakMap(); + var allgroup = [[]]; + if (this.group) { + allgroup = decartes(this.group, query); + } + // Union all arrays to get a maximum + var allgroups = []; + allgroup.forEach(function (a) { + allgroups = arrayUnion(allgroups, a); }); - if (rg.length === 0) { - rg = ["''"]; - } + query.allgroups = allgroups; - // With several grouping sets (CUBE, ROLLUP, GROUPING SETS) the same values can - // appear in different sets, e.g. (a=2) and (b=2), so the key must include the set - var groupKey = rg.join('+"`"+'); - if (allgroup.length > 1) { - groupKey = `'${setIdx}\`'+` + groupKey; - } + query.ingroup = []; + var s = ''; + allgroup.forEach(function (agroup, setIdx) { + // Start of group function + s += 'var g=this.xgroups['; - s += groupKey; - s += '];if(!g) {this.groups.push((g=this.xgroups['; - s += groupKey; - s += '] = {'; - s += agroup - .map(function (col2) { + // Array with group columns from record + var rg = agroup.map(function (col2) { var columnid = col2.split('\t')[0]; var coljs = col2.split('\t')[1]; - + // Check, if aggregator exists but GROUP BY is not exists if (columnid === '') { - return ''; + return '1'; // Create fictive grouping column for fictive GROUP BY } - return "'" + columnid + "':" + coljs + ','; - }) - .join(''); - var neggroup = arrayDiff(allgroups, agroup); + query.ingroup.push(columnid); + return coljs; + }); - s += neggroup - .map(function (col2) { - var columnid = col2.split('\t')[0]; - return "'" + columnid + "':null,"; - }) - .join(''); - var aft = '', - aft2 = ''; + if (rg.length === 0) { + rg = ["''"]; + } - if (typeof query.groupStar !== 'undefined') { - aft2 += "for(var f in p['" + query.groupStar + "']) {g[f]=p['" + query.groupStar + "'][f];};"; - } + // With several grouping sets (CUBE, ROLLUP, GROUPING SETS) the same values can + // appear in different sets, e.g. (a=2) and (b=2), so the key must include the set + var groupKey = rg.join('+"`"+'); + if (allgroup.length > 1) { + groupKey = `'${setIdx}\`'+` + groupKey; + } + + s += groupKey; + s += '];if(!g) {this.groups.push((g=this.xgroups['; + s += groupKey; + s += '] = {'; + s += agroup + .map(function (col2) { + var columnid = col2.split('\t')[0]; + var coljs = col2.split('\t')[1]; - s += query.selectGroup - .map(function (col) { - var colexp = col.expression.toJS('p', tableid, defcols); - var colas = col.nick; - let colExpIfFunIdExists = expression => { - let colexpression = expression.args[0]; - return colexpression.toJS('p', tableid, defcols); - }; - if (col instanceof yy.AggrValue) { - if (col.distinct) { - aft += - ",g['$$_VALUES_" + colas + "']={},g['$$_VALUES_" + colas + "'][" + colexp + ']=true'; + if (columnid === '') { + return ''; } - if (col.aggregatorid === 'SUM') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return `'${colas}':(y = ${colexp}, (y instanceof Date) ? undefined : ((y || typeof y == 'number') ? y : undefined)),`; + return "'" + columnid + "':" + coljs + ','; + }) + .join(''); + var neggroup = arrayDiff(allgroups, agroup); + + s += neggroup + .map(function (col2) { + var columnid = col2.split('\t')[0]; + return "'" + columnid + "':null,"; + }) + .join(''); + var aft = '', + aft2 = ''; + + if (typeof query.groupStar !== 'undefined') { + aft2 += + "for(var f in p['" + query.groupStar + "']) {g[f]=p['" + query.groupStar + "'][f];};"; + } + + s += query.selectGroup + .map(function (col) { + var colexp = col.expression.toJS('p', tableid, defcols); + var colas = col.nick; + let colExpIfFunIdExists = expression => { + let colexpression = expression.args[0]; + return colexpression.toJS('p', tableid, defcols); + }; + if (col instanceof yy.AggrValue) { + if (col.distinct) { + aft += + ",g['$$_VALUES_" + + colas + + "']={},g['$$_VALUES_" + + colas + + "'][" + + colexp + + ']=true'; } - return `'${colas}':(y = ${colexp}, (y instanceof Date) ? undefined : ((y || typeof y == 'number') ? y : undefined)),`; - } else if (col.aggregatorid === 'TOTAL') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return `'${colas}':(${colexp1}) || typeof ${colexp1} == 'number' ? + if (col.aggregatorid === 'SUM') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return `'${colas}':(y = ${colexp}, (y instanceof Date) ? undefined : ((y || typeof y == 'number') ? y : undefined)),`; + } + return `'${colas}':(y = ${colexp}, (y instanceof Date) ? undefined : ((y || typeof y == 'number') ? y : undefined)),`; + } else if (col.aggregatorid === 'TOTAL') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return `'${colas}':(${colexp1}) || typeof ${colexp1} == 'number' ? ${colexp1} : ${colexp1} == 'string' && typeof Number(${colexp1}) == 'number' ? Number(${colexp1}) : typeof ${colexp1} == 'boolean' ? Number(${colexp1}) : 0,`; - } - return `'${colas}':(${colexp})|| typeof ${colexp} == 'number' ? + } + return `'${colas}':(${colexp})|| typeof ${colexp} == 'number' ? ${colexp} : ${colexp} == 'string' && typeof Number(${colexp}) == 'number' ? Number(${colexp}) : typeof ${colexp} === 'boolean' ? Number(${colexp}) : 0,`; - } else if (col.aggregatorid === 'FIRST' || col.aggregatorid === 'LAST') { - return "'" + colas + "':" + colexp + ','; //f.field.arguments[0].toJS(); - } else if (col.aggregatorid === 'MIN') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); + } else if (col.aggregatorid === 'FIRST' || col.aggregatorid === 'LAST') { + return "'" + colas + "':" + colexp + ','; //f.field.arguments[0].toJS(); + } else if (col.aggregatorid === 'MIN') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return `'${colas}': (y = ${colexp}, y !== null && (typeof y == 'number' || typeof y == 'bigint' || (typeof y == 'object' && (typeof Number(y) == 'number' || y instanceof Date))) ? y : undefined),`; + } return `'${colas}': (y = ${colexp}, y !== null && (typeof y == 'number' || typeof y == 'bigint' || (typeof y == 'object' && (typeof Number(y) == 'number' || y instanceof Date))) ? y : undefined),`; - } - return `'${colas}': (y = ${colexp}, y !== null && (typeof y == 'number' || typeof y == 'bigint' || (typeof y == 'object' && (typeof Number(y) == 'number' || y instanceof Date))) ? y : undefined),`; - } else if (col.aggregatorid === 'MAX') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return `'${colas}': (y = ${colexp}, y !== null && (typeof y == 'number' || typeof y == 'bigint' || (typeof y == 'object' && (typeof Number(y) == 'number' || y instanceof Date))) ? y : undefined),`; - } - return `'${colas}' : (${colexp} !== null && (typeof ${colexp} == 'number' || typeof ${colexp} == 'bigint') ? ${colexp} : ${colexp} !== null && typeof ${colexp} == 'object' ? + } else if (col.aggregatorid === 'MAX') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return `'${colas}': (y = ${colexp}, y !== null && (typeof y == 'number' || typeof y == 'bigint' || (typeof y == 'object' && (typeof Number(y) == 'number' || y instanceof Date))) ? y : undefined),`; + } + return `'${colas}' : (${colexp} !== null && (typeof ${colexp} == 'number' || typeof ${colexp} == 'bigint') ? ${colexp} : ${colexp} !== null && typeof ${colexp} == 'object' ? typeof Number(${colexp}) == 'number' ? ${colexp} : undefined : undefined),`; - } else if (col.aggregatorid === 'ARRAY') { - return `'${colas}':[${colexp}],`; - } else if (col.aggregatorid === 'COUNT') { - if (col.expression.columnid === '*') { - return `'${colas}':1,`; - } else { - return `'${colas}':(typeof ${colexp} == "undefined" || ${colexp} === null) ? 0 : 1,`; - } - } else if (col.aggregatorid === 'AVG') { - query.removeKeys.push(`_SUM_${colas}`); - query.removeKeys.push(`_COUNT_${colas}`); + } else if (col.aggregatorid === 'ARRAY') { + return `'${colas}':[${colexp}],`; + } else if (col.aggregatorid === 'COUNT') { + if (col.expression.columnid === '*') { + return `'${colas}':1,`; + } else { + return `'${colas}':(typeof ${colexp} == "undefined" || ${colexp} === null) ? 0 : 1,`; + } + } else if (col.aggregatorid === 'AVG') { + query.removeKeys.push(`_SUM_${colas}`); + query.removeKeys.push(`_COUNT_${colas}`); - return `'${colas}':(function() { var t = ${colexp}; return (t instanceof Date) ? undefined : t; })(),'_SUM_${colas}':(function() { var t = ${colexp}; return (t instanceof Date) ? undefined : (t || 0); })(),'_COUNT_${colas}':(typeof ${colexp} == "undefined" || ${colexp} === null) ? 0 : 1,`; - } else if (col.aggregatorid === 'AGGR') { - aft += `,g['${colas}']=${col.expression.toJS('g', -1)}`; - return ''; - } else if (col.aggregatorid === 'REDUCE') { - query.aggrKeys.push(col); - const extraParams = getGroupConcatParams(col); - // Support multiple arguments for user-defined aggregates - if (col.args && col.args.length > 1) { - // Multiple arguments - pass all of them - let argExpressions = col.args.map(arg => arg.toJS('p', tableid, defcols)).join(','); - return `'${colas}':alasql.aggr['${col.funcid}'](${argExpressions},undefined,1${extraParams}),`; - } else { - // Single argument - backward compatibility - return `'${colas}':alasql.aggr['${col.funcid}'](${colexp},undefined,1${extraParams}),`; + return `'${colas}':(function() { var t = ${colexp}; return (t instanceof Date) ? undefined : t; })(),'_SUM_${colas}':(function() { var t = ${colexp}; return (t instanceof Date) ? undefined : (t || 0); })(),'_COUNT_${colas}':(typeof ${colexp} == "undefined" || ${colexp} === null) ? 0 : 1,`; + } else if (col.aggregatorid === 'AGGR') { + aft += `,g['${colas}']=${col.expression.toJS('g', -1)}`; + return ''; + } else if (col.aggregatorid === 'REDUCE') { + query.aggrKeys.push(col); + const extraParams = getGroupConcatParams(col); + // Support multiple arguments for user-defined aggregates + if (col.args && col.args.length > 1) { + // Multiple arguments - pass all of them + let argExpressions = col.args.map(arg => arg.toJS('p', tableid, defcols)).join(','); + return `'${colas}':alasql.aggr['${col.funcid}'](${argExpressions},undefined,1${extraParams}),`; + } else { + // Single argument - backward compatibility + return `'${colas}':alasql.aggr['${col.funcid}'](${colexp},undefined,1${extraParams}),`; + } } + return ''; } - return ''; - } - return ''; - }) - .join(''); + return ''; + }) + .join(''); - s += '}' + aft + ',g));this.groupSources.set(g,Object.assign({},p));' + aft2 + '} else {'; - s += query.selectGroup - .map(function (col) { - var colas = col.nick; - var colexp = col.expression.toJS('p', tableid, defcols); - let colExpIfFunIdExists = expression => { - let colexpression = expression.args[0]; - return colexpression.toJS('p', tableid, defcols); - }; - if (col instanceof yy.AggrValue) { - var pre = '', - post = ''; - if (col.distinct) { - pre = `if(typeof ${colexp}!="undefined" && (!g['$$_VALUES_${colas}'][${colexp}])) {`; - post = `g['$$_VALUES_${colas}'][${colexp}]=true;}`; - } + s += '}' + aft + ',g));this.groupSources.set(g,Object.assign({},p));' + aft2 + '} else {'; + s += query.selectGroup + .map(function (col) { + var colas = col.nick; + var colexp = col.expression.toJS('p', tableid, defcols); + let colExpIfFunIdExists = expression => { + let colexpression = expression.args[0]; + return colexpression.toJS('p', tableid, defcols); + }; + if (col instanceof yy.AggrValue) { + var pre = '', + post = ''; + if (col.distinct) { + pre = `if(typeof ${colexp}!="undefined" && (!g['$$_VALUES_${colas}'][${colexp}])) {`; + post = `g['$$_VALUES_${colas}'][${colexp}]=true;}`; + } - if (col.aggregatorid === 'SUM') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return ( - pre + - ` + if (col.aggregatorid === 'SUM') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return ( + pre + + ` { const __g_colas = g['${colas}']; const __typeof_colexp1 = typeof ${colexp1}; @@ -242,12 +249,12 @@ yy.Select.prototype.compileGroup = function (query) { } } ` + - post - ); - } - return ( - pre + - ` + post + ); + } + return ( + pre + + ` { const __g_colas = g['${colas}']; const __typeof_colexp = typeof ${colexp}; @@ -274,14 +281,14 @@ yy.Select.prototype.compileGroup = function (query) { } } ` + - post - ); - } else if (col.aggregatorid === 'TOTAL') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return ( - pre + - `{ + post + ); + } else if (col.aggregatorid === 'TOTAL') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return ( + pre + + `{ const __g_colas = g['${colas}']; const __colexp1 = ${colexp1}; const __typeof_g_colas = typeof __g_colas; @@ -302,12 +309,12 @@ yy.Select.prototype.compileGroup = function (query) { g['${colas}'] += __colexp1 || 0; } }` + - post - ); - } - return ( - pre + - `{ + post + ); + } + return ( + pre + + `{ const __g_colas = g['${colas}']; const __colexp = ${colexp}; const __typeof_g_colas = typeof __g_colas; @@ -330,26 +337,26 @@ yy.Select.prototype.compileGroup = function (query) { } ` + - post - ); - } else if (col.aggregatorid === 'COUNT') { - if (col.expression.columnid === '*') { - return `${pre} + post + ); + } else if (col.aggregatorid === 'COUNT') { + if (col.expression.columnid === '*') { + return `${pre} g['${colas}']++; ${post}`; - } else { - return `${pre} + } else { + return `${pre} if(typeof ${colexp}!="undefined" && ${colexp} !== null) g['${colas}']++; ${post}`; - } - } else if (col.aggregatorid === 'ARRAY') { - return pre + "g['" + colas + "'].push(" + colexp + ');' + post; - } else if (col.aggregatorid === 'MIN') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - return ( - pre + - `if ((g['${colas}'] == null && ${colexp1} !== null) ? y = ${colexp} : + } + } else if (col.aggregatorid === 'ARRAY') { + return pre + "g['" + colas + "'].push(" + colexp + ');' + post; + } else if (col.aggregatorid === 'MIN') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + return ( + pre + + `if ((g['${colas}'] == null && ${colexp1} !== null) ? y = ${colexp} : (g['${colas}'] !== null && ${colexp1} == null) ? y = g['${colas}'] : ((y = ${colexp}) < g['${colas}'])) { if (typeof y == 'number' || typeof y == 'bigint') { @@ -364,12 +371,12 @@ yy.Select.prototype.compileGroup = function (query) { } else if (g['${colas}'] !== null && typeof g['${colas}'] == 'object') { g['${colas}'] = Number(g['${colas}']); }` + - post - ); - } - return ( - pre + - `if((g['${colas}'] == null && ${colexp}!== null) ? y = ${colexp} : + post + ); + } + return ( + pre + + `if((g['${colas}'] == null && ${colexp}!== null) ? y = ${colexp} : (g['${colas}']!== null && ${colexp} == null) ? y = g['${colas}'] : ((y=${colexp}) < g['${colas}'])) { if(typeof y == 'number' || typeof y == 'bigint') { @@ -384,15 +391,15 @@ yy.Select.prototype.compileGroup = function (query) { } else if(g['${colas}']!== null && typeof g['${colas}'] == 'object') { g['${colas}'] = Number(g['${colas}']); }` + - post - ); - } else if (col.aggregatorid === 'MAX') { - if ('funcid' in col.expression) { - let colexp1 = colExpIfFunIdExists(col.expression); - //console.log(pre + 'if ((y=' + colexp + ") > g['" + colas + "']) g['" + colas + "']) - return ( - pre + - `if ((g['${colas}'] == null && ${colexp1} !== null) ? y = ${colexp} : + post + ); + } else if (col.aggregatorid === 'MAX') { + if ('funcid' in col.expression) { + let colexp1 = colExpIfFunIdExists(col.expression); + //console.log(pre + 'if ((y=' + colexp + ") > g['" + colas + "']) g['" + colas + "']) + return ( + pre + + `if ((g['${colas}'] == null && ${colexp1} !== null) ? y = ${colexp} : (g['${colas}'] !== null && ${colexp1} == null) ? y = g['${colas}'] : ((y = ${colexp}) > g['${colas}'])) { if (typeof y == 'number' || typeof y == 'bigint') { @@ -407,12 +414,12 @@ yy.Select.prototype.compileGroup = function (query) { } else if (g['${colas}'] !== null && typeof g['${colas}'] == 'object') { g['${colas}'] = Number(g['${colas}']); }` + - post - ); - } - return ( - pre + - `if ((g['${colas}'] == null && ${colexp} !== null) ? y = ${colexp} : + post + ); + } + return ( + pre + + `if ((g['${colas}'] == null && ${colexp} !== null) ? y = ${colexp} : (g['${colas}'] !== null && ${colexp} == null) ? y = g['${colas}'] : ((y = ${colexp}) > g['${colas}'])) { if (typeof y == 'number' || typeof y == 'bigint') { @@ -427,14 +434,14 @@ yy.Select.prototype.compileGroup = function (query) { } else if (g['${colas}'] !== null && typeof g['${colas}'] == 'object') { g['${colas}'] = Number(g['${colas}']); }` + - post - ); - } else if (col.aggregatorid === 'FIRST') { - return ''; - } else if (col.aggregatorid === 'LAST') { - return `${pre}g['${colas}']=${colexp};${post}`; - } else if (col.aggregatorid === 'AVG') { - return `${pre} + post + ); + } else if (col.aggregatorid === 'FIRST') { + return ''; + } else if (col.aggregatorid === 'LAST') { + return `${pre}g['${colas}']=${colexp};${post}`; + } else if (col.aggregatorid === 'AVG') { + return `${pre} y= (${colexp}); g['_COUNT_${colas}'] += (typeof y == "undefined" || y === null) ? 0 : 1; if (y instanceof Date || (g['_SUM_${colas}'] && g['_SUM_${colas}'] instanceof Date)) { @@ -450,35 +457,35 @@ yy.Select.prototype.compileGroup = function (query) { g['${colas}'] = g['_SUM_${colas}'] / g['_COUNT_${colas}']; } ${post}`; - } else if (col.aggregatorid === 'AGGR') { - return `${pre} + } else if (col.aggregatorid === 'AGGR') { + return `${pre} g['${colas}']=${col.expression.toJS('g', -1)}; ${post}`; - } else if (col.aggregatorid === 'REDUCE') { - const extraParams = getGroupConcatParams(col); - // Support multiple arguments for user-defined aggregates - if (col.args && col.args.length > 1) { - // Multiple arguments - pass all of them - let argExpressions = col.args.map(arg => arg.toJS('p', tableid, defcols)).join(','); - return `${pre} + } else if (col.aggregatorid === 'REDUCE') { + const extraParams = getGroupConcatParams(col); + // Support multiple arguments for user-defined aggregates + if (col.args && col.args.length > 1) { + // Multiple arguments - pass all of them + let argExpressions = col.args.map(arg => arg.toJS('p', tableid, defcols)).join(','); + return `${pre} g['${colas}'] = alasql.aggr.${col.funcid}(${argExpressions},g['${colas}'],2${extraParams}); ${post}`; - } else { - // Single argument - backward compatibility - return `${pre} + } else { + // Single argument - backward compatibility + return `${pre} g['${colas}'] = alasql.aggr.${col.funcid}(${colexp},g['${colas}'],2${extraParams}); ${post}`; + } } + return ''; } - return ''; - } - return ''; - }) - .join(''); + return ''; + }) + .join(''); - s += '}'; - }); - return new Function('p,params,alasql', 'var y;' + s); -}; + s += '}'; + }); + return new Function('p,params,alasql', 'var y;' + s); + }; } diff --git a/src/select/having.js b/src/select/having.js index aa463377bd..6377e73eae 100755 --- a/src/select/having.js +++ b/src/select/having.js @@ -1,40 +1,40 @@ export default function install(alasql) { var yy = alasql.yy; -/* global yy */ + /* global yy */ -// A name in HAVING that is not a column of a source table reads the SELECT alias -// of that name, as in SQLite (#887). Aggregates and subqueries keep their own names. -function resolveHavingAliases(node, query) { - if (!node || typeof node !== 'object') return; - if (node instanceof yy.Select || node instanceof yy.AggrValue) return; - if (node instanceof yy.Column) { - var nick = query.groupColumns[node.columnid]; - if ( - !node.tableid && - nick !== undefined && - nick !== node.columnid && - !(query.defcols && query.defcols[node.columnid]) - ) { - node.nick = nick; + // A name in HAVING that is not a column of a source table reads the SELECT alias + // of that name, as in SQLite (#887). Aggregates and subqueries keep their own names. + function resolveHavingAliases(node, query) { + if (!node || typeof node !== 'object') return; + if (node instanceof yy.Select || node instanceof yy.AggrValue) return; + if (node instanceof yy.Column) { + var nick = query.groupColumns[node.columnid]; + if ( + !node.tableid && + nick !== undefined && + nick !== node.columnid && + !(query.defcols && query.defcols[node.columnid]) + ) { + node.nick = nick; + } + return; + } + for (var key in node) { + resolveHavingAliases(node[key], query); } - return; - } - for (var key in node) { - resolveHavingAliases(node[key], query); } -} -yy.Select.prototype.compileHaving = function (query) { - if (this.having) { - resolveHavingAliases(this.having, query); - var s = this.having.toJS('g', -1); - query.havingfns = s; - // console.log(s); - return new Function('g,params,alasql', 'var y;return ' + s); - } + yy.Select.prototype.compileHaving = function (query) { + if (this.having) { + resolveHavingAliases(this.having, query); + var s = this.having.toJS('g', -1); + query.havingfns = s; + // console.log(s); + return new Function('g,params,alasql', 'var y;return ' + s); + } - return function () { - return true; + return function () { + return true; + }; }; -}; } diff --git a/src/select/join.js b/src/select/join.js index 50725d3301..26b067ada3 100755 --- a/src/select/join.js +++ b/src/select/join.js @@ -3,7 +3,7 @@ import {returnTrue, arrayIntersect} from '../utils/helpers.js'; export default function install(alasql, internal) { var yy = alasql.yy; var subqueryDatafn = internal.subqueryDatafn; -/* + /* // // Select compiler part for Alasql.js // Date: 03.11.2014 @@ -11,291 +11,293 @@ export default function install(alasql, internal) { // */ -// SELECT Compile functions - -/* global yy, alasql, returnTrue, arrayIntersect */ - -yy.Select.prototype.compileJoins = function (query) { - const self = this; - - this.joins.forEach(jn => { - let tq, ps, source; - // Handle CROSS JOIN (SQLite-compatible behavior) - // SQLite allows CROSS JOIN with ON/USING clauses, treating them as INNER JOIN - if (jn.joinmode === 'CROSS') { - // Convert all CROSS JOINs to INNER JOIN for consistent processing - // Result behavior: - // - Without ON/USING: Produces cartesian product (standard CROSS JOIN) - // - With ON/USING: Filters results like INNER JOIN (SQLite extension) - jn.joinmode = 'INNER'; - } - - if (jn instanceof yy.Apply) { - source = { - alias: jn.as, - applymode: jn.applymode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - columns: [], // TODO check this - }; - source.applyselect = jn.select.compile(query.database.databaseid); - source.columns = source.applyselect.query.columns; - - source.datafn = function (query, params, cb, idx, alasql) { - let res; - if (cb) res = cb(res, idx, query); - return res; - }; + // SELECT Compile functions - query.sources.push(source); + /* global yy, alasql, returnTrue, arrayIntersect */ - return; - } - - if (jn.table) { - tq = jn.table; - source = { - alias: jn.as || tq.tableid, - databaseid: tq.databaseid || query.database.databaseid, - tableid: tq.tableid, - joinmode: jn.joinmode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - columns: [], - }; - - if (!alasql.databases[source.databaseid].tables[source.tableid]) { - throw new Error( - "Table '" + source.tableid + "' is not exists in database '" + source.databaseid + "'" - ); - } + yy.Select.prototype.compileJoins = function (query) { + const self = this; - source.columns = alasql.databases[source.databaseid].tables[source.tableid].columns; + this.joins.forEach(jn => { + let tq, ps, source; + // Handle CROSS JOIN (SQLite-compatible behavior) + // SQLite allows CROSS JOIN with ON/USING clauses, treating them as INNER JOIN + if (jn.joinmode === 'CROSS') { + // Convert all CROSS JOINs to INNER JOIN for consistent processing + // Result behavior: + // - Without ON/USING: Produces cartesian product (standard CROSS JOIN) + // - With ON/USING: Filters results like INNER JOIN (SQLite extension) + jn.joinmode = 'INNER'; + } - if (alasql.options.autocommit && alasql.databases[source.databaseid].engineid) { - source.datafn = function (query, params, cb, idx, alasql) { - return alasql.engines[alasql.databases[source.databaseid].engineid].fromTable( - source.databaseid, - source.tableid, - cb, - idx, - query - ); - }; - } else if (alasql.databases[source.databaseid].tables[source.tableid].view) { - source.datafn = function (query, params, cb, idx, alasql) { - let res = alasql.databases[source.databaseid].tables[source.tableid].select(params); - if (cb) res = cb(res, idx, query); - return res; + if (jn instanceof yy.Apply) { + source = { + alias: jn.as, + applymode: jn.applymode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + columns: [], // TODO check this }; - } else { + source.applyselect = jn.select.compile(query.database.databaseid); + source.columns = source.applyselect.query.columns; + source.datafn = function (query, params, cb, idx, alasql) { - let res = alasql.databases[source.databaseid].tables[source.tableid].data; + let res; if (cb) res = cb(res, idx, query); return res; }; - } - query.aliases[source.alias] = { - tableid: tq.tableid, - databaseid: tq.databaseid || query.database.databaseid, - }; - } else if (jn.select) { - tq = jn.select; - source = { - alias: jn.as, - joinmode: jn.joinmode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - columns: [], - }; - - source.subquery = tq.compile(query.database.databaseid); - if (typeof source.subquery.query.modifier === 'undefined') { - source.subquery.query.modifier = 'RECORDSET'; // Subqueries always return recordsets + query.sources.push(source); + + return; } - source.columns = source.subquery.query.columns; - - source.datafn = subqueryDatafn(source); - query.aliases[source.alias] = {type: 'subquery'}; - } else if (jn.param) { - source = { - alias: jn.as, - joinmode: jn.joinmode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - }; - const jnparam = jn.param.param; - ps = "let res=alasql.prepareFromData(params['" + jnparam + "']"; - if (jn.array) ps += ',true'; - ps += '); if(cb) res=cb(res, idx, query); return res'; - - source.datafn = new Function('query,params,cb,idx, alasql', ps); - query.aliases[source.alias] = {type: 'paramvalue'}; - } else if (jn.variable) { - source = { - alias: jn.as, - joinmode: jn.joinmode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - }; - ps = "let res=alasql.prepareFromData(alasql.vars['" + jn.variable + "']"; - if (jn.array) ps += ', true'; - ps += '); if(cb)res=cb(res, idx, query);return res'; - - source.datafn = new Function('query,params,cb,idx, alasql', ps); - query.aliases[source.alias] = {type: 'varvalue'}; - } else if (jn.func) { - source = { - alias: jn.as, - joinmode: jn.joinmode, - onmiddlefn: returnTrue, - srcwherefns: '', // for optimization - srcwherefn: returnTrue, - }; - - let s = 'let res=alasql.from[' + JSON.stringify(jn.func.funcid.toUpperCase()) + ']('; - - const args = jn.func.args; - if (args && args.length > 0) { - if (args[0]) { - s += args[0].toJS('query.oldscope') + ', '; - } else { - s += 'null, '; + + if (jn.table) { + tq = jn.table; + source = { + alias: jn.as || tq.tableid, + databaseid: tq.databaseid || query.database.databaseid, + tableid: tq.tableid, + joinmode: jn.joinmode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + columns: [], + }; + + if (!alasql.databases[source.databaseid].tables[source.tableid]) { + throw new Error( + "Table '" + source.tableid + "' is not exists in database '" + source.databaseid + "'" + ); } - if (args[1]) { - s += args[1].toJS('query.oldscope') + ', '; + + source.columns = alasql.databases[source.databaseid].tables[source.tableid].columns; + + if (alasql.options.autocommit && alasql.databases[source.databaseid].engineid) { + source.datafn = function (query, params, cb, idx, alasql) { + return alasql.engines[alasql.databases[source.databaseid].engineid].fromTable( + source.databaseid, + source.tableid, + cb, + idx, + query + ); + }; + } else if (alasql.databases[source.databaseid].tables[source.tableid].view) { + source.datafn = function (query, params, cb, idx, alasql) { + let res = alasql.databases[source.databaseid].tables[source.tableid].select(params); + if (cb) res = cb(res, idx, query); + return res; + }; } else { - s += 'null, '; + source.datafn = function (query, params, cb, idx, alasql) { + let res = alasql.databases[source.databaseid].tables[source.tableid].data; + if (cb) res = cb(res, idx, query); + return res; + }; } - } else { - s += 'null, null, '; - } - s += 'cb, idx, query); return res'; - source.datafn = new Function('query, params, cb, idx, alasql', s); - - query.aliases[source.alias] = {type: 'funcvalue'}; - } - - const alias = source.alias; - - // Test NATURAL-JOIN - if (jn.natural) { - if (jn.using || jn.on) { - throw new Error('NATURAL JOIN cannot have USING or ON clauses'); - } else { - // source.joinmode == "INNER"; - if (query.sources.length > 0) { - const prevSource = query.sources[query.sources.length - 1]; - const prevTable = alasql.databases[prevSource.databaseid].tables[prevSource.tableid]; - const table = alasql.databases[source.databaseid].tables[source.tableid]; - - if (prevTable && table) { - const c1 = prevTable.columns.map(col => col.columnid); - const c2 = table.columns.map(col => col.columnid); - jn.using = arrayIntersect(c1, c2).map(colid => ({columnid: colid})); - } else { - throw new Error( - 'In this version of Alasql NATURAL JOIN ' + - 'works for tables with predefined columns only' - ); - } + + query.aliases[source.alias] = { + tableid: tq.tableid, + databaseid: tq.databaseid || query.database.databaseid, + }; + } else if (jn.select) { + tq = jn.select; + source = { + alias: jn.as, + joinmode: jn.joinmode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + columns: [], + }; + + source.subquery = tq.compile(query.database.databaseid); + if (typeof source.subquery.query.modifier === 'undefined') { + source.subquery.query.modifier = 'RECORDSET'; // Subqueries always return recordsets } - } - } - - if (jn.using) { - const prevSource = query.sources[query.sources.length - 1]; - source.onleftfns = jn.using - .map(col => "p['" + (prevSource.alias || prevSource.tableid) + "']['" + col.columnid + "']") - .join('+"`"+'); - - source.onleftfn = new Function('p,params,alasql', 'let y;return ' + source.onleftfns); - - source.onrightfns = jn.using - .map(col => "p['" + (source.alias || source.tableid) + "']['" + col.columnid + "']") - .join('+"`"+'); - source.onrightfn = new Function('p,params,alasql', 'let y;return ' + source.onrightfns); - source.optimization = 'ix'; - } else if (jn.on) { - if (jn.on instanceof yy.Op && jn.on.op === '=' && !jn.on.allsome) { - source.optimization = 'ix'; - let lefts = ''; - let rights = ''; - let middles = ''; - let middlef = false; - // Test right and left sides - const ls = jn.on.left.toJS('p', query.defaultTableid, query.defcols); - const rs = jn.on.right.toJS('p', query.defaultTableid, query.defcols); - - if (ls.indexOf("p['" + alias + "']") > -1 && !(rs.indexOf("p['" + alias + "']") > -1)) { - if ((ls.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { - rights = ls; + source.columns = source.subquery.query.columns; + + source.datafn = subqueryDatafn(source); + query.aliases[source.alias] = {type: 'subquery'}; + } else if (jn.param) { + source = { + alias: jn.as, + joinmode: jn.joinmode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + }; + const jnparam = jn.param.param; + ps = "let res=alasql.prepareFromData(params['" + jnparam + "']"; + if (jn.array) ps += ',true'; + ps += '); if(cb) res=cb(res, idx, query); return res'; + + source.datafn = new Function('query,params,cb,idx, alasql', ps); + query.aliases[source.alias] = {type: 'paramvalue'}; + } else if (jn.variable) { + source = { + alias: jn.as, + joinmode: jn.joinmode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + }; + ps = "let res=alasql.prepareFromData(alasql.vars['" + jn.variable + "']"; + if (jn.array) ps += ', true'; + ps += '); if(cb)res=cb(res, idx, query);return res'; + + source.datafn = new Function('query,params,cb,idx, alasql', ps); + query.aliases[source.alias] = {type: 'varvalue'}; + } else if (jn.func) { + source = { + alias: jn.as, + joinmode: jn.joinmode, + onmiddlefn: returnTrue, + srcwherefns: '', // for optimization + srcwherefn: returnTrue, + }; + + let s = 'let res=alasql.from[' + JSON.stringify(jn.func.funcid.toUpperCase()) + ']('; + + const args = jn.func.args; + if (args && args.length > 0) { + if (args[0]) { + s += args[0].toJS('query.oldscope') + ', '; } else { - middlef = true; + s += 'null, '; } - } else if ( - !(ls.indexOf("p['" + alias + "']") > -1) && - rs.indexOf("p['" + alias + "']") > -1 - ) { - if ((rs.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { - lefts = ls; + if (args[1]) { + s += args[1].toJS('query.oldscope') + ', '; } else { - middlef = true; + s += 'null, '; } } else { - middlef = true; + s += 'null, null, '; + } + s += 'cb, idx, query); return res'; + source.datafn = new Function('query, params, cb, idx, alasql', s); + + query.aliases[source.alias] = {type: 'funcvalue'}; + } + + const alias = source.alias; + + // Test NATURAL-JOIN + if (jn.natural) { + if (jn.using || jn.on) { + throw new Error('NATURAL JOIN cannot have USING or ON clauses'); + } else { + // source.joinmode == "INNER"; + if (query.sources.length > 0) { + const prevSource = query.sources[query.sources.length - 1]; + const prevTable = alasql.databases[prevSource.databaseid].tables[prevSource.tableid]; + const table = alasql.databases[source.databaseid].tables[source.tableid]; + + if (prevTable && table) { + const c1 = prevTable.columns.map(col => col.columnid); + const c2 = table.columns.map(col => col.columnid); + jn.using = arrayIntersect(c1, c2).map(colid => ({columnid: colid})); + } else { + throw new Error( + 'In this version of Alasql NATURAL JOIN ' + + 'works for tables with predefined columns only' + ); + } + } } + } + + if (jn.using) { + const prevSource = query.sources[query.sources.length - 1]; + source.onleftfns = jn.using + .map( + col => "p['" + (prevSource.alias || prevSource.tableid) + "']['" + col.columnid + "']" + ) + .join('+"`"+'); + + source.onleftfn = new Function('p,params,alasql', 'let y;return ' + source.onleftfns); - if (rs.indexOf("p['" + alias + "']") > -1 && !(ls.indexOf("p['" + alias + "']") > -1)) { - if ((rs.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { - rights = rs; + source.onrightfns = jn.using + .map(col => "p['" + (source.alias || source.tableid) + "']['" + col.columnid + "']") + .join('+"`"+'); + source.onrightfn = new Function('p,params,alasql', 'let y;return ' + source.onrightfns); + source.optimization = 'ix'; + } else if (jn.on) { + if (jn.on instanceof yy.Op && jn.on.op === '=' && !jn.on.allsome) { + source.optimization = 'ix'; + let lefts = ''; + let rights = ''; + let middles = ''; + let middlef = false; + // Test right and left sides + const ls = jn.on.left.toJS('p', query.defaultTableid, query.defcols); + const rs = jn.on.right.toJS('p', query.defaultTableid, query.defcols); + + if (ls.indexOf("p['" + alias + "']") > -1 && !(rs.indexOf("p['" + alias + "']") > -1)) { + if ((ls.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { + rights = ls; + } else { + middlef = true; + } + } else if ( + !(ls.indexOf("p['" + alias + "']") > -1) && + rs.indexOf("p['" + alias + "']") > -1 + ) { + if ((rs.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { + lefts = ls; + } else { + middlef = true; + } } else { middlef = true; } - } else if ( - !(rs.indexOf("p['" + alias + "']") > -1) && - ls.indexOf("p['" + alias + "']") > -1 - ) { - if ((ls.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { - lefts = rs; + + if (rs.indexOf("p['" + alias + "']") > -1 && !(ls.indexOf("p['" + alias + "']") > -1)) { + if ((rs.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { + rights = rs; + } else { + middlef = true; + } + } else if ( + !(rs.indexOf("p['" + alias + "']") > -1) && + ls.indexOf("p['" + alias + "']") > -1 + ) { + if ((ls.match(/p\['.*?'\]/g) || []).every(s => s === "p['" + alias + "']")) { + lefts = rs; + } else { + middlef = true; + } } else { middlef = true; } - } else { - middlef = true; - } - if (middlef) { - rights = ''; - lefts = ''; - middles = jn.on.toJS('p', query.defaultTableid, query.defcols); - source.optimization = 'no'; - } + if (middlef) { + rights = ''; + lefts = ''; + middles = jn.on.toJS('p', query.defaultTableid, query.defcols); + source.optimization = 'no'; + } - source.onleftfns = lefts; - source.onrightfns = rights; - source.onmiddlefns = middles || 'true'; + source.onleftfns = lefts; + source.onrightfns = rights; + source.onmiddlefns = middles || 'true'; - source.onleftfn = new Function('p,params,alasql', 'let y;return ' + source.onleftfns); - source.onrightfn = new Function('p,params,alasql', 'let y;return ' + source.onrightfns); - source.onmiddlefn = new Function('p,params,alasql', 'let y;return ' + source.onmiddlefns); - } else { - source.optimization = 'no'; - source.onmiddlefns = jn.on.toJS('p', query.defaultTableid, query.defcols); - source.onmiddlefn = new Function( - 'p,params,alasql', - 'let y;return ' + jn.on.toJS('p', query.defaultTableid, query.defcols) - ); + source.onleftfn = new Function('p,params,alasql', 'let y;return ' + source.onleftfns); + source.onrightfn = new Function('p,params,alasql', 'let y;return ' + source.onrightfns); + source.onmiddlefn = new Function('p,params,alasql', 'let y;return ' + source.onmiddlefns); + } else { + source.optimization = 'no'; + source.onmiddlefns = jn.on.toJS('p', query.defaultTableid, query.defcols); + source.onmiddlefn = new Function( + 'p,params,alasql', + 'let y;return ' + jn.on.toJS('p', query.defaultTableid, query.defcols) + ); + } } - } - query.sources.push(source); - }); -}; + query.sources.push(source); + }); + }; } diff --git a/src/select/linq.js b/src/select/linq.js index 4d67c5feb3..8c98ae27ac 100755 --- a/src/select/linq.js +++ b/src/select/linq.js @@ -2,173 +2,173 @@ import {hash} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -// Alasql Linq library - -yy.FromData = function (params) { - return yy.extend(this, params); -}; -yy.FromData.prototype.toString = function () { - if (this.data) return 'DATA(' + ((Math.random() * 10e15) | 0) + ')'; - else return '?'; -}; -yy.FromData.prototype.toJS = function () { - // console.log('yy.FromData.prototype.toJS'); -}; - -yy.Select.prototype.exec = function (params, cb) { - if (this.preparams) params = this.preparams.concat(params); - // console.log(15,this.preparams); - - var databaseid = alasql.useid; - var db = alasql.databases[databaseid]; - var sql = this.toString(); - var hh = hash(sql); - // console.log(sql); - - var statement = this.compile(databaseid); - if (!statement) return; - statement.sql = sql; - statement.dbversion = db.dbversion; - statement.schemaVersion = alasql.schemaVersion; - - // Secure sqlCache size - if (db.sqlCacheSize > alasql.MAXSQLCACHESIZE) { - db.resetSqlCache(); - } - db.sqlCacheSize++; - db.sqlCache[hh] = statement; - var res = (alasql.res = statement(params, cb)); - return res; -}; - -yy.Select.prototype.Select = function () { - var self = this; - var args = []; - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } else if (arguments.length == 1) { - if (Array.isArray(arguments[0])) { - args = arguments[0]; + // Alasql Linq library + + yy.FromData = function (params) { + return yy.extend(this, params); + }; + yy.FromData.prototype.toString = function () { + if (this.data) return 'DATA(' + ((Math.random() * 10e15) | 0) + ')'; + else return '?'; + }; + yy.FromData.prototype.toJS = function () { + // console.log('yy.FromData.prototype.toJS'); + }; + + yy.Select.prototype.exec = function (params, cb) { + if (this.preparams) params = this.preparams.concat(params); + // console.log(15,this.preparams); + + var databaseid = alasql.useid; + var db = alasql.databases[databaseid]; + var sql = this.toString(); + var hh = hash(sql); + // console.log(sql); + + var statement = this.compile(databaseid); + if (!statement) return; + statement.sql = sql; + statement.dbversion = db.dbversion; + statement.schemaVersion = alasql.schemaVersion; + + // Secure sqlCache size + if (db.sqlCacheSize > alasql.MAXSQLCACHESIZE) { + db.resetSqlCache(); + } + db.sqlCacheSize++; + db.sqlCache[hh] = statement; + var res = (alasql.res = statement(params, cb)); + return res; + }; + + yy.Select.prototype.Select = function () { + var self = this; + var args = []; + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } else if (arguments.length == 1) { + if (Array.isArray(arguments[0])) { + args = arguments[0]; + } else { + args = [arguments[0]]; + } } else { - args = [arguments[0]]; + throw new Error('Wrong number of arguments of Select() function'); } - } else { - throw new Error('Wrong number of arguments of Select() function'); - } - self.columns = []; + self.columns = []; + + args.forEach(function (arg) { + if (typeof arg == 'string') { + self.columns.push(new yy.Column({columnid: arg})); + } else if (typeof arg == 'function') { + var pari = 0; + if (self.preparams) { + pari = self.preparams.length; + } else { + self.preparams = []; + } + self.preparams.push(arg); + self.columns.push(new yy.Column({columnid: '*', func: arg, param: pari})); + } else { + // Unknown type + } + }); + + // console.log(self instanceof yy.Select); + return self; + }; - args.forEach(function (arg) { - if (typeof arg == 'string') { - self.columns.push(new yy.Column({columnid: arg})); - } else if (typeof arg == 'function') { + yy.Select.prototype.From = function (tableid) { + var self = this; + if (!self.from) self.from = []; + if (Array.isArray(tableid)) { var pari = 0; if (self.preparams) { pari = self.preparams.length; } else { self.preparams = []; } - self.preparams.push(arg); - self.columns.push(new yy.Column({columnid: '*', func: arg, param: pari})); + self.preparams.push(tableid); + self.from.push(new yy.ParamValue({param: pari})); + } else if (typeof tableid == 'string') { + self.from.push(new yy.Table({tableid: tableid})); } else { - // Unknown type + throw new Error('Unknown arguments in From() function'); } - }); - - // console.log(self instanceof yy.Select); - return self; -}; - -yy.Select.prototype.From = function (tableid) { - var self = this; - if (!self.from) self.from = []; - if (Array.isArray(tableid)) { - var pari = 0; - if (self.preparams) { - pari = self.preparams.length; + return self; + }; + + yy.Select.prototype.OrderBy = function () { + var self = this; + var args = []; + + self.order = []; + + if (arguments.length == 0) { + // self.order.push(new yy.OrderExpression({expression: new yy.Column({columnid:"_"}), direction:'ASC'})); + args = ['_']; + } else if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } else if (arguments.length == 1) { + if (Array.isArray(arguments[0])) { + args = arguments[0]; + } else { + args = [arguments[0]]; + } } else { - self.preparams = []; + throw new Error('Wrong number of arguments of Select() function'); + } + + if (args.length > 0) { + args.forEach(function (arg) { + var expr = new yy.Column({columnid: arg}); + if (typeof arg == 'function') { + expr = arg; + } + self.order.push(new yy.OrderExpression({expression: expr, direction: 'ASC'})); + }); } - self.preparams.push(tableid); - self.from.push(new yy.ParamValue({param: pari})); - } else if (typeof tableid == 'string') { - self.from.push(new yy.Table({tableid: tableid})); - } else { - throw new Error('Unknown arguments in From() function'); - } - return self; -}; - -yy.Select.prototype.OrderBy = function () { - var self = this; - var args = []; - - self.order = []; - - if (arguments.length == 0) { - // self.order.push(new yy.OrderExpression({expression: new yy.Column({columnid:"_"}), direction:'ASC'})); - args = ['_']; - } else if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } else if (arguments.length == 1) { - if (Array.isArray(arguments[0])) { - args = arguments[0]; + return self; + }; + + yy.Select.prototype.Top = function (topnum) { + var self = this; + self.top = new yy.NumValue({value: topnum}); + return self; + }; + + yy.Select.prototype.GroupBy = function () { + var self = this; + var args = []; + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } else if (arguments.length == 1) { + if (Array.isArray(arguments[0])) { + args = arguments[0]; + } else { + args = [arguments[0]]; + } } else { - args = [arguments[0]]; + throw new Error('Wrong number of arguments of Select() function'); } - } else { - throw new Error('Wrong number of arguments of Select() function'); - } - if (args.length > 0) { + self.group = []; + args.forEach(function (arg) { var expr = new yy.Column({columnid: arg}); - if (typeof arg == 'function') { - expr = arg; - } - self.order.push(new yy.OrderExpression({expression: expr, direction: 'ASC'})); + self.group.push(expr); }); - } - return self; -}; - -yy.Select.prototype.Top = function (topnum) { - var self = this; - self.top = new yy.NumValue({value: topnum}); - return self; -}; - -yy.Select.prototype.GroupBy = function () { - var self = this; - var args = []; - - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } else if (arguments.length == 1) { - if (Array.isArray(arguments[0])) { - args = arguments[0]; - } else { - args = [arguments[0]]; + + return self; + }; + + yy.Select.prototype.Where = function (expr) { + var self = this; + if (typeof expr == 'function') { + self.where = expr; } - } else { - throw new Error('Wrong number of arguments of Select() function'); - } - - self.group = []; - - args.forEach(function (arg) { - var expr = new yy.Column({columnid: arg}); - self.group.push(expr); - }); - - return self; -}; - -yy.Select.prototype.Where = function (expr) { - var self = this; - if (typeof expr == 'function') { - self.where = expr; - } - return self; -}; + return self; + }; } diff --git a/src/select/orderby.js b/src/select/orderby.js index 7431d8f9ab..17c81dc77a 100755 --- a/src/select/orderby.js +++ b/src/select/orderby.js @@ -1,130 +1,130 @@ export default function install(alasql) { var yy = alasql.yy; -yy.Select.prototype.compileOrder = function (query, params) { - var self = this; - self.orderColumns = []; - if (this.order) { - // console.log(990, this.order); - if ( - this.order && - this.order.length == 1 && - this.order[0].expression && - typeof this.order[0].expression == 'function' - ) { - // console.log(991, this.order[0]); - var func = this.order[0].expression; - // console.log(994, func); - var nullsOrder = - this.order[0].nullsOrder == 'FIRST' ? -1 : this.order[0].nullsOrder == 'LAST' ? +1 : 0; - return function (a, b) { - var ra = func(a), - rb = func(b); - if (nullsOrder) { - if (ra == null) return rb == null ? 0 : nullsOrder; - if (rb == null) return -nullsOrder; - } - if (ra > rb) return 1; - if (ra == rb) return 0; - return -1; - }; - } - - var s = ''; - var sk = ''; - this.order.forEach(function (ord, idx) { - // console.log(ord instanceof yy.Expression); - // console.log(ord.toJS('a','')); - // console.log(ord.expression instanceof yy.Column); - - if (ord.expression instanceof yy.NumValue) { - // Validate that column number is at least 1 - if (ord.expression.value < 1) { - throw new Error( - `Invalid column number ${ord.expression.value}. Column numbers must be at least 1.` - ); - } + yy.Select.prototype.compileOrder = function (query, params) { + var self = this; + self.orderColumns = []; + if (this.order) { + // console.log(990, this.order); + if ( + this.order && + this.order.length == 1 && + this.order[0].expression && + typeof this.order[0].expression == 'function' + ) { + // console.log(991, this.order[0]); + var func = this.order[0].expression; + // console.log(994, func); + var nullsOrder = + this.order[0].nullsOrder == 'FIRST' ? -1 : this.order[0].nullsOrder == 'LAST' ? +1 : 0; + return function (a, b) { + var ra = func(a), + rb = func(b); + if (nullsOrder) { + if (ra == null) return rb == null ? 0 : nullsOrder; + if (rb == null) return -nullsOrder; + } + if (ra > rb) return 1; + if (ra == rb) return 0; + return -1; + }; + } - var v = self.columns[ord.expression.value - 1]; - // Check if we're dealing with SELECT * case - var hasWildcard = - self.columns.length === 1 && - self.columns[0] instanceof yy.Column && - self.columns[0].columnid === '*'; + var s = ''; + var sk = ''; + this.order.forEach(function (ord, idx) { + // console.log(ord instanceof yy.Expression); + // console.log(ord.toJS('a','')); + // console.log(ord.expression instanceof yy.Column); - if (hasWildcard) { - // With SELECT *, use positional ordering (resolved at runtime) - // Skip validation as we don't know the column count at compile time - v = {_useColumnIndex: true, columnIndex: ord.expression.value - 1}; - } else { - // With explicit columns, validate the column number - if (ord.expression.value > self.columns.length) { + if (ord.expression instanceof yy.NumValue) { + // Validate that column number is at least 1 + if (ord.expression.value < 1) { throw new Error( - `You are trying to order by column number ${ord.expression.value} but you have only selected ${self.columns.length} columns.` + `Invalid column number ${ord.expression.value}. Column numbers must be at least 1.` ); } - // Also check if the resolved column is a wildcard (shouldn't happen but be safe) - if (v instanceof yy.Column && v.columnid === '*') { + + var v = self.columns[ord.expression.value - 1]; + // Check if we're dealing with SELECT * case + var hasWildcard = + self.columns.length === 1 && + self.columns[0] instanceof yy.Column && + self.columns[0].columnid === '*'; + + if (hasWildcard) { + // With SELECT *, use positional ordering (resolved at runtime) + // Skip validation as we don't know the column count at compile time v = {_useColumnIndex: true, columnIndex: ord.expression.value - 1}; + } else { + // With explicit columns, validate the column number + if (ord.expression.value > self.columns.length) { + throw new Error( + `You are trying to order by column number ${ord.expression.value} but you have only selected ${self.columns.length} columns.` + ); + } + // Also check if the resolved column is a wildcard (shouldn't happen but be safe) + if (v instanceof yy.Column && v.columnid === '*') { + v = {_useColumnIndex: true, columnIndex: ord.expression.value - 1}; + } } + } else if (ord.expression instanceof yy.StringValue) { + // Treat quoted strings in ORDER BY as column references + var v = new yy.Column({columnid: ord.expression.value}); + } else { + var v = ord.expression; } - } else if (ord.expression instanceof yy.StringValue) { - // Treat quoted strings in ORDER BY as column references - var v = new yy.Column({columnid: ord.expression.value}); - } else { - var v = ord.expression; - } - self.orderColumns.push(v); + self.orderColumns.push(v); - var key = '$$$' + idx; + var key = '$$$' + idx; - // Date conversion - get columnid based on expression type - var dg = ''; - var columnid; - if (ord.expression instanceof yy.Column) { - columnid = ord.expression.columnid; - } else if (ord.expression instanceof yy.ParamValue) { - columnid = params[ord.expression.param]; - } else if (ord.expression instanceof yy.StringValue) { - columnid = ord.expression.value; - } - if (columnid) { - if (alasql.options.valueof) { - dg = '.valueOf()'; - } else if (query.xcolumns[columnid]) { - var dbtypeid = query.xcolumns[columnid].dbtypeid; - if ( - dbtypeid == 'DATE' || - dbtypeid == 'DATETIME' || - dbtypeid == 'DATETIME2' || - dbtypeid == 'STRING' || - dbtypeid == 'NUMBER' - ) + // Date conversion - get columnid based on expression type + var dg = ''; + var columnid; + if (ord.expression instanceof yy.Column) { + columnid = ord.expression.columnid; + } else if (ord.expression instanceof yy.ParamValue) { + columnid = params[ord.expression.param]; + } else if (ord.expression instanceof yy.StringValue) { + columnid = ord.expression.value; + } + if (columnid) { + if (alasql.options.valueof) { dg = '.valueOf()'; + } else if (query.xcolumns[columnid]) { + var dbtypeid = query.xcolumns[columnid].dbtypeid; + if ( + dbtypeid == 'DATE' || + dbtypeid == 'DATETIME' || + dbtypeid == 'DATETIME2' || + dbtypeid == 'STRING' || + dbtypeid == 'NUMBER' + ) + dg = '.valueOf()'; + } } - } - if (ord.nullsOrder) { - if (ord.nullsOrder == 'FIRST') { - s += "if((a['" + key + "'] != null) && (b['" + key + "'] == null)) return 1;"; - } else if (ord.nullsOrder == 'LAST') { - s += "if((a['" + key + "'] == null) && (b['" + key + "'] != null)) return 1;"; + if (ord.nullsOrder) { + if (ord.nullsOrder == 'FIRST') { + s += "if((a['" + key + "'] != null) && (b['" + key + "'] == null)) return 1;"; + } else if (ord.nullsOrder == 'LAST') { + s += "if((a['" + key + "'] == null) && (b['" + key + "'] != null)) return 1;"; + } + s += "if((a['" + key + "'] == null) == (b['" + key + "'] == null)) {"; + sk += '}'; } - s += "if((a['" + key + "'] == null) == (b['" + key + "'] == null)) {"; - sk += '}'; - } - var va = "(a['" + key + "']||'')" + dg; - var vb = "(b['" + key + "']||'')" + dg; - // COLLATE NOCASE ignores case in text; numbers and other values compare as they are - if (ord.nocase) { - va = '(typeof (y=' + va + ")=='string'?y.toUpperCase():y)"; - vb = '(typeof (y=' + vb + ")=='string'?y.toUpperCase():y)"; - } - s += 'if(' + va + (ord.direction == 'ASC' ? '>' : '<') + vb + ')return 1;'; - s += 'if(' + va + '==' + vb + '){'; - //console.log(37,s); + var va = "(a['" + key + "']||'')" + dg; + var vb = "(b['" + key + "']||'')" + dg; + // COLLATE NOCASE ignores case in text; numbers and other values compare as they are + if (ord.nocase) { + va = '(typeof (y=' + va + ")=='string'?y.toUpperCase():y)"; + vb = '(typeof (y=' + vb + ")=='string'?y.toUpperCase():y)"; + } + s += 'if(' + va + (ord.direction == 'ASC' ? '>' : '<') + vb + ')return 1;'; + s += 'if(' + va + '==' + vb + '){'; + //console.log(37,s); - /* + /* if(false) { //console.log(ord.expression, ord.expression instanceof yy.NumValue); if(ord.expression instanceof yy.NumValue) { @@ -167,13 +167,13 @@ if(false) { } */ - sk += '}'; - }); - s += 'return 0;'; - s += sk + 'return -1'; - query.orderfns = s; - //console.log('ORDERBY',s); - return new Function('a,b', 'var y;' + s); - } -}; + sk += '}'; + }); + s += 'return 0;'; + s += sk + 'return -1'; + query.orderfns = s; + //console.log('ORDERBY',s); + return new Function('a,b', 'var y;' + s); + } + }; } diff --git a/src/select/pivot.js b/src/select/pivot.js index fcf546a329..61097bf933 100755 --- a/src/select/pivot.js +++ b/src/select/pivot.js @@ -2,369 +2,369 @@ import {cloneDeep} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -// Pivot functions -/** + // Pivot functions + /** Compile Pivot functions @param {object} query Source query @return {function} Pivoting functions */ -yy.Select.prototype.compilePivot = function (query) { - var self = this; - /** @type {string} Main pivoting column */ + yy.Select.prototype.compilePivot = function (query) { + var self = this; + /** @type {string} Main pivoting column */ - var columnid = self.pivot.columnid; - var aggr = self.pivot.expr.aggregatorid; - var inlist = self.pivot.inlist; + var columnid = self.pivot.columnid; + var aggr = self.pivot.expr.aggregatorid; + var inlist = self.pivot.inlist; - var exprcolid = null; + var exprcolid = null; - if (self.pivot.expr.expression.hasOwnProperty('columnid')) { - exprcolid = self.pivot.expr.expression.columnid; - } else { - exprcolid = self.pivot.expr.expression.expression.columnid; - } - - if (null == exprcolid) { - throw 'columnid not found'; - } - - if (inlist) { - inlist = inlist.map(function (l) { - return l.expr.columnid; - }); - } - - // Function for PIVOT post production (returned by compilePivot) - return function () { - var query = this; // Keep reference to the query object + if (self.pivot.expr.expression.hasOwnProperty('columnid')) { + exprcolid = self.pivot.expr.expression.columnid; + } else { + exprcolid = self.pivot.expr.expression.expression.columnid; + } - // Exit if no data to pivot - if (!query.data || query.data.length === 0) { - query.columns = []; // Reset columns if no data - return; + if (null == exprcolid) { + throw 'columnid not found'; } - // --- Determine grouping columns robustly --- - var firstRowKeys = Object.keys(query.data[0]); - var cols = firstRowKeys.filter(function (key) { - return key !== columnid && key !== exprcolid; - }); - // ----------------------------------------- + if (inlist) { + inlist = inlist.map(function (l) { + return l.expr.columnid; + }); + } - var newcols = []; // Stores the new column headers (e.g., 'MON', 'TUE') - var gnewcols = {}; // Tracks unique new columns encountered - var gr = {}; // Stores the grouped & pivoted results (key: gx, value: g) - var ga = {}; // Stores counts for AVG calculation (key: gx, value: {day: count}) - var data = []; // The final pivoted data array + // Function for PIVOT post production (returned by compilePivot) + return function () { + var query = this; // Keep reference to the query object - query.data.forEach(function (d) { - // Skip row if the pivot column value is not in the IN list (if provided) - if (inlist && inlist.indexOf(d[columnid]) === -1) { + // Exit if no data to pivot + if (!query.data || query.data.length === 0) { + query.columns = []; // Reset columns if no data return; } - var gx = cols - .map(function (colid) { - return d[colid] === undefined || d[colid] === null ? '' : d[colid]; - }) - .join('`'); - - var g = gr[gx]; - if (!g) { - g = {}; - gr[gx] = g; - data.push(g); - cols.forEach(function (colid) { - g[colid] = d[colid]; - }); - } - - if (!ga[gx]) { - ga[gx] = {}; - } + // --- Determine grouping columns robustly --- + var firstRowKeys = Object.keys(query.data[0]); + var cols = firstRowKeys.filter(function (key) { + return key !== columnid && key !== exprcolid; + }); + // ----------------------------------------- + + var newcols = []; // Stores the new column headers (e.g., 'MON', 'TUE') + var gnewcols = {}; // Tracks unique new columns encountered + var gr = {}; // Stores the grouped & pivoted results (key: gx, value: g) + var ga = {}; // Stores counts for AVG calculation (key: gx, value: {day: count}) + var data = []; // The final pivoted data array + + query.data.forEach(function (d) { + // Skip row if the pivot column value is not in the IN list (if provided) + if (inlist && inlist.indexOf(d[columnid]) === -1) { + return; + } - var pivotColValue = d[columnid]; - var aggValue = d[exprcolid]; + var gx = cols + .map(function (colid) { + return d[colid] === undefined || d[colid] === null ? '' : d[colid]; + }) + .join('`'); + + var g = gr[gx]; + if (!g) { + g = {}; + gr[gx] = g; + data.push(g); + cols.forEach(function (colid) { + g[colid] = d[colid]; + }); + } - // Increment count only for non-null/undefined values when calculating AVG - if (ga[gx][pivotColValue]) { - if (aggValue !== null && typeof aggValue !== 'undefined') { - ga[gx][pivotColValue]++; + if (!ga[gx]) { + ga[gx] = {}; } - } else { - ga[gx][pivotColValue] = aggValue !== null && typeof aggValue !== 'undefined' ? 1 : 0; - } - if (!gnewcols[pivotColValue]) { - gnewcols[pivotColValue] = true; - newcols.push(pivotColValue); - } + var pivotColValue = d[columnid]; + var aggValue = d[exprcolid]; - // Apply the specified aggregation - if (aggr == 'SUM' || aggr == 'AVG' || aggr == 'TOTAL') { - if (aggValue !== null && typeof aggValue !== 'undefined') { - g[pivotColValue] = - typeof g[pivotColValue] === 'undefined' || g[pivotColValue] === null - ? Number(aggValue) - : g[pivotColValue] + Number(aggValue); - } else if (typeof g[pivotColValue] === 'undefined') { - g[pivotColValue] = null; + // Increment count only for non-null/undefined values when calculating AVG + if (ga[gx][pivotColValue]) { + if (aggValue !== null && typeof aggValue !== 'undefined') { + ga[gx][pivotColValue]++; + } + } else { + ga[gx][pivotColValue] = aggValue !== null && typeof aggValue !== 'undefined' ? 1 : 0; } - } else if (aggr == 'COUNT') { - if (exprcolid === '*' || (aggValue !== null && typeof aggValue !== 'undefined')) { - g[pivotColValue] = (g[pivotColValue] || 0) + 1; - } else if (typeof g[pivotColValue] === 'undefined') { - g[pivotColValue] = 0; + + if (!gnewcols[pivotColValue]) { + gnewcols[pivotColValue] = true; + newcols.push(pivotColValue); } - } else if (aggr == 'MIN') { - if (aggValue !== null && typeof aggValue !== 'undefined') { - if ( - typeof g[pivotColValue] === 'undefined' || - g[pivotColValue] === null || - aggValue < g[pivotColValue] - ) { - g[pivotColValue] = aggValue; + + // Apply the specified aggregation + if (aggr == 'SUM' || aggr == 'AVG' || aggr == 'TOTAL') { + if (aggValue !== null && typeof aggValue !== 'undefined') { + g[pivotColValue] = + typeof g[pivotColValue] === 'undefined' || g[pivotColValue] === null + ? Number(aggValue) + : g[pivotColValue] + Number(aggValue); + } else if (typeof g[pivotColValue] === 'undefined') { + g[pivotColValue] = null; } - } else if (typeof g[pivotColValue] === 'undefined') { - g[pivotColValue] = null; - } - } else if (aggr == 'MAX') { - if (aggValue !== null && typeof aggValue !== 'undefined') { - if ( - typeof g[pivotColValue] === 'undefined' || - g[pivotColValue] === null || - aggValue > g[pivotColValue] - ) { + } else if (aggr == 'COUNT') { + if (exprcolid === '*' || (aggValue !== null && typeof aggValue !== 'undefined')) { + g[pivotColValue] = (g[pivotColValue] || 0) + 1; + } else if (typeof g[pivotColValue] === 'undefined') { + g[pivotColValue] = 0; + } + } else if (aggr == 'MIN') { + if (aggValue !== null && typeof aggValue !== 'undefined') { + if ( + typeof g[pivotColValue] === 'undefined' || + g[pivotColValue] === null || + aggValue < g[pivotColValue] + ) { + g[pivotColValue] = aggValue; + } + } else if (typeof g[pivotColValue] === 'undefined') { + g[pivotColValue] = null; + } + } else if (aggr == 'MAX') { + if (aggValue !== null && typeof aggValue !== 'undefined') { + if ( + typeof g[pivotColValue] === 'undefined' || + g[pivotColValue] === null || + aggValue > g[pivotColValue] + ) { + g[pivotColValue] = aggValue; + } + } else if (typeof g[pivotColValue] === 'undefined') { + g[pivotColValue] = null; + } + } else if (aggr == 'FIRST') { + if (typeof g[pivotColValue] === 'undefined') { g[pivotColValue] = aggValue; } - } else if (typeof g[pivotColValue] === 'undefined') { - g[pivotColValue] = null; - } - } else if (aggr == 'FIRST') { - if (typeof g[pivotColValue] === 'undefined') { + } else if (aggr == 'LAST') { g[pivotColValue] = aggValue; - } - } else if (aggr == 'LAST') { - g[pivotColValue] = aggValue; - } else if (alasql.aggr[aggr]) { - if (typeof g[pivotColValue] === 'undefined') { - g[pivotColValue] = alasql.aggr[aggr](aggValue, undefined, 1); + } else if (alasql.aggr[aggr]) { + if (typeof g[pivotColValue] === 'undefined') { + g[pivotColValue] = alasql.aggr[aggr](aggValue, undefined, 1); + } else { + g[pivotColValue] = alasql.aggr[aggr](aggValue, g[pivotColValue], 2); + } } else { - g[pivotColValue] = alasql.aggr[aggr](aggValue, g[pivotColValue], 2); + throw new Error('Unknown aggregator in PIVOT clause: ' + aggr); } - } else { - throw new Error('Unknown aggregator in PIVOT clause: ' + aggr); - } - }); + }); - // Finalize AVG calculation - if (aggr == 'AVG') { - for (var gx in gr) { - var d = gr[gx]; - for (var pivotColValue in ga[gx]) { - if (d.hasOwnProperty(pivotColValue) && d[pivotColValue] !== null) { - var count = ga[gx][pivotColValue]; - if (count > 0) { - d[pivotColValue] = d[pivotColValue] / count; - } else { - d[pivotColValue] = null; + // Finalize AVG calculation + if (aggr == 'AVG') { + for (var gx in gr) { + var d = gr[gx]; + for (var pivotColValue in ga[gx]) { + if (d.hasOwnProperty(pivotColValue) && d[pivotColValue] !== null) { + var count = ga[gx][pivotColValue]; + if (count > 0) { + d[pivotColValue] = d[pivotColValue] / count; + } else { + d[pivotColValue] = null; + } } } } } - } - // --- Rebuild query.columns --- - query.data = data; + // --- Rebuild query.columns --- + query.data = data; - if (inlist) { - newcols = inlist; - } else { - newcols.sort(); - } + if (inlist) { + newcols = inlist; + } else { + newcols.sort(); + } - // Find original column definition - might be basic if SELECT * was used - let aggColDef = query.columns.find(col => col.columnid === exprcolid); - // If SELECT * was used, aggColDef might be missing, find it from the table definition if possible - if (!aggColDef && query.sources && query.sources.length > 0) { - let sourceTableId = query.sources[0].tableid; - let sourceDbId = query.sources[0].databaseid; - if ( - sourceTableId && - sourceDbId && - alasql.databases[sourceDbId]?.tables?.[sourceTableId]?.xcolumns - ) { - aggColDef = alasql.databases[sourceDbId].tables[sourceTableId].xcolumns[exprcolid]; + // Find original column definition - might be basic if SELECT * was used + let aggColDef = query.columns.find(col => col.columnid === exprcolid); + // If SELECT * was used, aggColDef might be missing, find it from the table definition if possible + if (!aggColDef && query.sources && query.sources.length > 0) { + let sourceTableId = query.sources[0].tableid; + let sourceDbId = query.sources[0].databaseid; + if ( + sourceTableId && + sourceDbId && + alasql.databases[sourceDbId]?.tables?.[sourceTableId]?.xcolumns + ) { + aggColDef = alasql.databases[sourceDbId].tables[sourceTableId].xcolumns[exprcolid]; + } } - } - // Provide a fallback if still not found - aggColDef = aggColDef || {columnid: exprcolid, dbtypeid: 'OBJECT'}; + // Provide a fallback if still not found + aggColDef = aggColDef || {columnid: exprcolid, dbtypeid: 'OBJECT'}; - // Keep only the grouping columns initially - query.columns = query.columns.filter(function (col) { - return cols.includes(col.columnid); - }); + // Keep only the grouping columns initially + query.columns = query.columns.filter(function (col) { + return cols.includes(col.columnid); + }); - // Add the new pivoted columns - newcols.forEach(function (newColId) { - var nc = cloneDeep(aggColDef); - nc.columnid = newColId; - - // ---- Final Refined Type Logic ---- - const originalType = (aggColDef.dbtypeid || 'OBJECT').toUpperCase(); - const integerTypes = [ - 'INT', - 'INTEGER', - 'SMALLINT', - 'BIGINT', - 'SERIAL', - 'SMALLSERIAL', - 'BIGSERIAL', - ]; - const numericTypes = [...integerTypes, 'NUMBER', 'FLOAT', 'DECIMAL', 'NUMERIC', 'MONEY']; - - if (aggr === 'COUNT') { - nc.dbtypeid = 'INT'; - } else if (aggr === 'AVG') { - // Keep INT type if original was INT-like, otherwise use FLOAT - if (integerTypes.includes(originalType)) { - nc.dbtypeid = aggColDef.dbtypeid; // Preserve original INT-like type - } else { - nc.dbtypeid = 'FLOAT'; - } - } else if (aggr === 'SUM' || aggr === 'TOTAL') { - // Preserve numeric types, default to FLOAT otherwise - if (numericTypes.includes(originalType)) { + // Add the new pivoted columns + newcols.forEach(function (newColId) { + var nc = cloneDeep(aggColDef); + nc.columnid = newColId; + + // ---- Final Refined Type Logic ---- + const originalType = (aggColDef.dbtypeid || 'OBJECT').toUpperCase(); + const integerTypes = [ + 'INT', + 'INTEGER', + 'SMALLINT', + 'BIGINT', + 'SERIAL', + 'SMALLSERIAL', + 'BIGSERIAL', + ]; + const numericTypes = [...integerTypes, 'NUMBER', 'FLOAT', 'DECIMAL', 'NUMERIC', 'MONEY']; + + if (aggr === 'COUNT') { + nc.dbtypeid = 'INT'; + } else if (aggr === 'AVG') { + // Keep INT type if original was INT-like, otherwise use FLOAT + if (integerTypes.includes(originalType)) { + nc.dbtypeid = aggColDef.dbtypeid; // Preserve original INT-like type + } else { + nc.dbtypeid = 'FLOAT'; + } + } else if (aggr === 'SUM' || aggr === 'TOTAL') { + // Preserve numeric types, default to FLOAT otherwise + if (numericTypes.includes(originalType)) { + nc.dbtypeid = aggColDef.dbtypeid; + } else { + nc.dbtypeid = 'FLOAT'; // Default for non-numeric or unknown sums + } + } else if (aggr === 'MIN' || aggr === 'MAX' || aggr === 'FIRST' || aggr === 'LAST') { + // Preserve original type as comparisons work across types nc.dbtypeid = aggColDef.dbtypeid; - } else { - nc.dbtypeid = 'FLOAT'; // Default for non-numeric or unknown sums } - } else if (aggr === 'MIN' || aggr === 'MAX' || aggr === 'FIRST' || aggr === 'LAST') { - // Preserve original type as comparisons work across types - nc.dbtypeid = aggColDef.dbtypeid; - } - // For custom aggregators (AGGR, REDUCE), inherit type from clone, ensure fallback - else if (!nc.dbtypeid) { - nc.dbtypeid = 'OBJECT'; - } + // For custom aggregators (AGGR, REDUCE), inherit type from clone, ensure fallback + else if (!nc.dbtypeid) { + nc.dbtypeid = 'OBJECT'; + } - query.columns.push(nc); - }); + query.columns.push(nc); + }); + }; }; -}; - -// var columnid = this.pivot.columnid; - -// return function(data){ -// * @type {object} Collection of grouped records -// var gx = {}; -// /** @type {array} Array of grouped records */ -// var gr = []; - -// if(false) { -// for(var i=0,ilen=data.length;i-1){z=r[\''+columnid+'\'];'; -// s += 'g[z] = (g[z]||0)+1;'; -// s += '}'; -// console.log(this.pivot.expr.toJS()); -// console.log(this.pivot); -// console.log(s); -// var gfn = new Function('g,r,params,alasql','var y;'+s); - -// return function(data){ -// var g = {}, gr = []; -// for(var i=0,ilen=data.length;i-1){z=r[\''+columnid+'\'];'; + // s += 'g[z] = (g[z]||0)+1;'; + // s += '}'; + // console.log(this.pivot.expr.toJS()); + // console.log(this.pivot); + // console.log(s); + // var gfn = new Function('g,r,params,alasql','var y;'+s); + + // return function(data){ + // var g = {}, gr = []; + // for(var i=0,ilen=data.length;i= 0) { - let source = query.sources[idx]; - source.data = data; - if (typeof source.data === 'function') { - source.getfn = source.data; - source.dontcache = source.getfn.dontcache; - if (['OUTER', 'RIGHT', 'ANTI'].includes(source.joinmode)) { - source.dontcache = false; + function queryfn2(data, idx, query) { + if (idx >= 0) { + let source = query.sources[idx]; + source.data = data; + if (typeof source.data === 'function') { + source.getfn = source.data; + source.dontcache = source.getfn.dontcache; + if (['OUTER', 'RIGHT', 'ANTI'].includes(source.joinmode)) { + source.dontcache = false; + } + source.data = {}; } - source.data = {}; + } else { + query.queriesdata[-idx - 1] = flatArray(data); } - } else { - query.queriesdata[-idx - 1] = flatArray(data); - } - - query.sourceslen--; - if (query.sourceslen > 0) return; - - return queryfn3(query); -} - -function queryfn3(query) { - var scope = query.scope, - jlen; - // Preindexation of data sources - preIndex(query); + query.sourceslen--; + if (query.sourceslen > 0) return; - // Prepare variables - query.data = []; - query.xgroups = {}; - query.groups = []; + return queryfn3(query); + } - // Level of Joins - var h = 0; + function queryfn3(query) { + var scope = query.scope, + jlen; - // Start walking over data - doJoin(query, scope, h); + // Preindexation of data sources + preIndex(query); - // If grouping, then filter groups with HAVING function - if (query.groupfn) { + // Prepare variables query.data = []; - if (query.groups.length === 0 && query.allgroups.length === 0) { - var g = {}; - if (query.selectGroup.length > 0) { - query.selectGroup.forEach(function (sg) { - if ( - sg.aggregatorid == 'COUNT' || - sg.aggregatorid == 'SUM' || - sg.aggregatorid == 'TOTAL' - ) { - g[sg.nick] = 0; + query.xgroups = {}; + query.groups = []; + + // Level of Joins + var h = 0; + + // Start walking over data + doJoin(query, scope, h); + + // If grouping, then filter groups with HAVING function + if (query.groupfn) { + query.data = []; + if (query.groups.length === 0 && query.allgroups.length === 0) { + var g = {}; + if (query.selectGroup.length > 0) { + query.selectGroup.forEach(function (sg) { + if ( + sg.aggregatorid == 'COUNT' || + sg.aggregatorid == 'SUM' || + sg.aggregatorid == 'TOTAL' + ) { + g[sg.nick] = 0; + } else { + g[sg.nick] = undefined; + } + }); + } + query.groups = [g]; + } + + if (query.aggrKeys.length > 0) { + var gfns = ''; + query.aggrKeys.forEach(function (col) { + // For multi-column aggregates, pass undefined for each column parameter + var undefinedArgs = ''; + if (col.args && col.args.length > 1) { + // Multi-column: pass undefined for each argument, then accumulator, then stage + undefinedArgs = Array(col.args.length).fill('undefined').join(',') + ','; } else { - g[sg.nick] = undefined; + // Single column: pass undefined, accumulator, stage + undefinedArgs = 'undefined,'; } + gfns += ` + g[${JSON.stringify(col.nick)}] = alasql.aggr[${JSON.stringify( + col.funcid + )}](${undefinedArgs}g[${JSON.stringify(col.nick)}],3); `; }); + var gfn = new Function('g,params,alasql', 'var y;' + gfns); } - query.groups = [g]; - } - if (query.aggrKeys.length > 0) { - var gfns = ''; - query.aggrKeys.forEach(function (col) { - // For multi-column aggregates, pass undefined for each column parameter - var undefinedArgs = ''; - if (col.args && col.args.length > 1) { - // Multi-column: pass undefined for each argument, then accumulator, then stage - undefinedArgs = Array(col.args.length).fill('undefined').join(',') + ','; - } else { - // Single column: pass undefined, accumulator, stage - undefinedArgs = 'undefined,'; + for (var i = 0, ilen = query.groups.length; i < ilen; i++) { + var g = query.groups[i]; + + if (gfn) gfn(g, query.params, alasql); + + if (!query.havingfn || query.havingfn(g, query.params, alasql)) { + var d = query.selectgfn(g, query.params, alasql); + + for (const key in query.groupColumns) { + // only remove columns where the alias is also not a column in the result + // and no other alias in the result also points to the same nick + if ( + query.groupColumns[key] !== key && + d[query.groupColumns[key]] && + !query.groupColumns[query.groupColumns[key]] + ) { + // Check if any other key in the result also maps to this nick + var nick = query.groupColumns[key]; + var otherAliasExists = false; + for (const otherKey in query.groupColumns) { + if (otherKey !== key && query.groupColumns[otherKey] === nick && d[otherKey]) { + otherAliasExists = true; + break; + } + } + if (!otherAliasExists) { + delete d[query.groupColumns[key]]; + } + } + } + query.data.push(d); } - gfns += ` - g[${JSON.stringify(col.nick)}] = alasql.aggr[${JSON.stringify( - col.funcid - )}](${undefinedArgs}g[${JSON.stringify(col.nick)}],3); `; - }); - var gfn = new Function('g,params,alasql', 'var y;' + gfns); + } } - - for (var i = 0, ilen = query.groups.length; i < ilen; i++) { - var g = query.groups[i]; - - if (gfn) gfn(g, query.params, alasql); - - if (!query.havingfn || query.havingfn(g, query.params, alasql)) { - var d = query.selectgfn(g, query.params, alasql); - - for (const key in query.groupColumns) { - // only remove columns where the alias is also not a column in the result - // and no other alias in the result also points to the same nick - if ( - query.groupColumns[key] !== key && - d[query.groupColumns[key]] && - !query.groupColumns[query.groupColumns[key]] - ) { - // Check if any other key in the result also maps to this nick - var nick = query.groupColumns[key]; - var otherAliasExists = false; - for (const otherKey in query.groupColumns) { - if (otherKey !== key && query.groupColumns[otherKey] === nick && d[otherKey]) { - otherAliasExists = true; - break; + // Remove distinct values + doDistinct(query); + + // If we have UNION/UNION ALL/EXCEPT/INTERSECT with ORDER BY/LIMIT before it, + // apply ORDER BY and LIMIT to the first SELECT before combining. + // This handles the pattern: SELECT ... ORDER BY ... LIMIT ... UNION ALL SELECT ... ORDER BY ... LIMIT ... + // We only do this if the UNION branch also has ORDER BY/LIMIT (pattern 2), not if ORDER BY is at the end (pattern 1). + // The compiled query is reused by the statement cache and alasql.compile(), so record + // what was already applied in this run instead of clearing it on the query. + var orderApplied = false; + var limitApplied = false; + var unionBranchHasOrder = ['unionallfn', 'unionfn', 'exceptfn', 'intersectfn'].some( + function (fnName) { + var fn = query[fnName]; + return fn && fn.query && (fn.query.orderfn || fn.query.limit); + } + ); + + if (unionBranchHasOrder && (query.orderfn || query.limit)) { + // Apply ordering to first SELECT's data + if (query.orderfn) { + // Populate order keys before sorting (needed for UNION queries) + if (query.orderColumns) { + for (var i = 0, ilen = query.data.length; i < ilen; i++) { + for (var idx = 0; idx < query.orderColumns.length; idx++) { + var v = query.orderColumns[idx]; + var key = '$$$' + idx; + var r = query.data[i]; + if (v instanceof yy.Column && r[v.columnid] !== undefined) { + r[key] = r[v.columnid]; + } else if (v instanceof yy.Column) { + r[key] = undefined; + } else { + r[key] = undefined; + } + if (i === 0 && query.removeKeys.indexOf(key) === -1) { + query.removeKeys.push(key); } } - if (!otherAliasExists) { - delete d[query.groupColumns[key]]; + } + } + query.data = query.data.sort(query.orderfn); + // The order keys were only needed for this sort. Remove them, so these rows + // compare equal to the rows of the other SELECT in UNION, EXCEPT and INTERSECT + if (query.orderColumns) { + for (var i = 0, ilen = query.data.length; i < ilen; i++) { + for (var idx = 0; idx < query.orderColumns.length; idx++) { + delete query.data[i]['$$$' + idx]; } } } - query.data.push(d); + // Don't apply orderfn again after UNION + orderApplied = true; + } + // Apply limit to first SELECT's data + if (query.limit) { + doLimit(query); + // Don't apply limit again after UNION + limitApplied = true; } } - } - // Remove distinct values - doDistinct(query); - - // If we have UNION/UNION ALL/EXCEPT/INTERSECT with ORDER BY/LIMIT before it, - // apply ORDER BY and LIMIT to the first SELECT before combining. - // This handles the pattern: SELECT ... ORDER BY ... LIMIT ... UNION ALL SELECT ... ORDER BY ... LIMIT ... - // We only do this if the UNION branch also has ORDER BY/LIMIT (pattern 2), not if ORDER BY is at the end (pattern 1). - // The compiled query is reused by the statement cache and alasql.compile(), so record - // what was already applied in this run instead of clearing it on the query. - var orderApplied = false; - var limitApplied = false; - var unionBranchHasOrder = ['unionallfn', 'unionfn', 'exceptfn', 'intersectfn'].some( - function (fnName) { - var fn = query[fnName]; - return fn && fn.query && (fn.query.orderfn || fn.query.limit); - } - ); - - if (unionBranchHasOrder && (query.orderfn || query.limit)) { - // Apply ordering to first SELECT's data - if (query.orderfn) { - // Populate order keys before sorting (needed for UNION queries) - if (query.orderColumns) { - for (var i = 0, ilen = query.data.length; i < ilen; i++) { - for (var idx = 0; idx < query.orderColumns.length; idx++) { - var v = query.orderColumns[idx]; - var key = '$$$' + idx; - var r = query.data[i]; - if (v instanceof yy.Column && r[v.columnid] !== undefined) { - r[key] = r[v.columnid]; - } else if (v instanceof yy.Column) { - r[key] = undefined; - } else { - r[key] = undefined; + + // UNION / UNION ALL + if (query.unionallfn) { + // TODO Simplify this part of program + var ud, nd; + if (query.corresponding) { + if (query.unionallfn.query && !query.unionallfn.query.modifier) + query.unionallfn.query.modifier = undefined; + ud = query.unionallfn(query.params); + } else { + if (query.unionallfn.query && !query.unionallfn.query.modifier) + query.unionallfn.query.modifier = 'RECORDSET'; + nd = query.unionallfn(query.params); + ud = []; + ilen = nd.data.length; + for (var i = 0; i < ilen; i++) { + var r = {}; + if (query.columns.length) { + jlen = Math.min(query.columns.length, nd.columns.length); + for (var j = 0; j < jlen; j++) { + r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; } - if (i === 0 && query.removeKeys.indexOf(key) === -1) { - query.removeKeys.push(key); + } else { + jlen = nd.columns.length; + for (var j = 0; j < jlen; j++) { + r[nd.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; } } + ud.push(r); } } - query.data = query.data.sort(query.orderfn); - // The order keys were only needed for this sort. Remove them, so these rows - // compare equal to the rows of the other SELECT in UNION, EXCEPT and INTERSECT - if (query.orderColumns) { - for (var i = 0, ilen = query.data.length; i < ilen; i++) { - for (var idx = 0; idx < query.orderColumns.length; idx++) { - delete query.data[i]['$$$' + idx]; + query.data = query.data.concat(ud); + } else if (query.unionfn) { + if (query.corresponding) { + if (query.unionfn.query && !query.unionfn.query.modifier) + query.unionfn.query.modifier = 'ARRAY'; + ud = query.unionfn(query.params); + } else { + if (query.unionfn.query && !query.unionfn.query.modifier) + query.unionfn.query.modifier = 'RECORDSET'; + nd = query.unionfn(query.params); + ud = []; + ilen = nd.data.length; + for (var i = 0; i < ilen; i++) { + r = {}; + if (query.columns.length) { + jlen = Math.min(query.columns.length, nd.columns.length); + for (var j = 0; j < jlen; j++) { + r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; + } + } else { + jlen = nd.columns.length; + for (var j = 0; j < jlen; j++) { + r[nd.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; + } } + ud.push(r); } } - // Don't apply orderfn again after UNION - orderApplied = true; - } - // Apply limit to first SELECT's data - if (query.limit) { - doLimit(query); - // Don't apply limit again after UNION - limitApplied = true; - } - } - // UNION / UNION ALL - if (query.unionallfn) { - // TODO Simplify this part of program - var ud, nd; - if (query.corresponding) { - if (query.unionallfn.query && !query.unionallfn.query.modifier) - query.unionallfn.query.modifier = undefined; - ud = query.unionallfn(query.params); - } else { - if (query.unionallfn.query && !query.unionallfn.query.modifier) - query.unionallfn.query.modifier = 'RECORDSET'; - nd = query.unionallfn(query.params); - ud = []; - ilen = nd.data.length; - for (var i = 0; i < ilen; i++) { - var r = {}; - if (query.columns.length) { - jlen = Math.min(query.columns.length, nd.columns.length); - for (var j = 0; j < jlen; j++) { + query.data = arrayUnionDeep(query.data, ud); + } else if (query.exceptfn) { + if (query.corresponding) { + if (query.exceptfn.query && !query.exceptfn.query.modifier) + query.exceptfn.query.modifier = 'ARRAY'; + var ud = query.exceptfn(query.params); + } else { + if (query.exceptfn.query && !query.exceptfn.query.modifier) + query.exceptfn.query.modifier = 'RECORDSET'; + var nd = query.exceptfn(query.params); + var ud = []; + for (var i = 0, ilen = nd.data.length; i < ilen; i++) { + var r = {}; + for (var j = Math.min(query.columns.length, nd.columns.length) - 1; 0 <= j; j--) { r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; } - } else { - jlen = nd.columns.length; - for (var j = 0; j < jlen; j++) { - r[nd.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; - } + ud.push(r); } - ud.push(r); } - } - query.data = query.data.concat(ud); - } else if (query.unionfn) { - if (query.corresponding) { - if (query.unionfn.query && !query.unionfn.query.modifier) - query.unionfn.query.modifier = 'ARRAY'; - ud = query.unionfn(query.params); - } else { - if (query.unionfn.query && !query.unionfn.query.modifier) - query.unionfn.query.modifier = 'RECORDSET'; - nd = query.unionfn(query.params); - ud = []; - ilen = nd.data.length; - for (var i = 0; i < ilen; i++) { - r = {}; - if (query.columns.length) { + + query.data = arrayExceptDeep(query.data, ud); + } else if (query.intersectfn) { + if (query.corresponding) { + if (query.intersectfn.query && !query.intersectfn.query.modifier) + query.intersectfn.query.modifier = undefined; + ud = query.intersectfn(query.params); + } else { + if (query.intersectfn.query && !query.intersectfn.query.modifier) + query.intersectfn.query.modifier = 'RECORDSET'; + nd = query.intersectfn(query.params); + ud = []; + ilen = nd.data.length; + for (i = 0; i < ilen; i++) { + r = {}; jlen = Math.min(query.columns.length, nd.columns.length); - for (var j = 0; j < jlen; j++) { + for (j = 0; j < jlen; j++) { r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; } - } else { - jlen = nd.columns.length; - for (var j = 0; j < jlen; j++) { - r[nd.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; - } + ud.push(r); } - ud.push(r); } - } - query.data = arrayUnionDeep(query.data, ud); - } else if (query.exceptfn) { - if (query.corresponding) { - if (query.exceptfn.query && !query.exceptfn.query.modifier) - query.exceptfn.query.modifier = 'ARRAY'; - var ud = query.exceptfn(query.params); - } else { - if (query.exceptfn.query && !query.exceptfn.query.modifier) - query.exceptfn.query.modifier = 'RECORDSET'; - var nd = query.exceptfn(query.params); - var ud = []; - for (var i = 0, ilen = nd.data.length; i < ilen; i++) { - var r = {}; - for (var j = Math.min(query.columns.length, nd.columns.length) - 1; 0 <= j; j--) { - r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; - } - ud.push(r); - } + query.data = arrayIntersectDeep(query.data, ud); } - query.data = arrayExceptDeep(query.data, ud); - } else if (query.intersectfn) { - if (query.corresponding) { - if (query.intersectfn.query && !query.intersectfn.query.modifier) - query.intersectfn.query.modifier = undefined; - ud = query.intersectfn(query.params); - } else { - if (query.intersectfn.query && !query.intersectfn.query.modifier) - query.intersectfn.query.modifier = 'RECORDSET'; - nd = query.intersectfn(query.params); - ud = []; - ilen = nd.data.length; - for (i = 0; i < ilen; i++) { - r = {}; - jlen = Math.min(query.columns.length, nd.columns.length); - for (j = 0; j < jlen; j++) { - r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid]; + // Populate order keys for UNION/INTERSECT/EXCEPT before ordering + if ( + query.orderfn && + !orderApplied && + query.orderColumns && + (query.unionallfn || query.unionfn || query.exceptfn || query.intersectfn) + ) { + for (i = 0, ilen = query.data.length; i < ilen; i++) { + for (var idx = 0; idx < query.orderColumns.length; idx++) { + var v = query.orderColumns[idx]; + var key = '$$$' + idx; + var r = query.data[i]; + if (v instanceof yy.Column && r[v.columnid] !== undefined) { + r[key] = r[v.columnid]; + } else if (v instanceof yy.Column) { + // Column not found in row, set to null or undefined + r[key] = undefined; + } else { + // For expressions, we'd need to evaluate them, but for now just skip + r[key] = undefined; + } + // Add to removeKeys if not already there + if (i === 0 && query.removeKeys.indexOf(key) === -1) { + query.removeKeys.push(key); + } } - ud.push(r); } } - query.data = arrayIntersectDeep(query.data, ud); - } - - // Populate order keys for UNION/INTERSECT/EXCEPT before ordering - if ( - query.orderfn && - !orderApplied && - query.orderColumns && - (query.unionallfn || query.unionfn || query.exceptfn || query.intersectfn) - ) { - for (i = 0, ilen = query.data.length; i < ilen; i++) { - for (var idx = 0; idx < query.orderColumns.length; idx++) { - var v = query.orderColumns[idx]; - var key = '$$$' + idx; - var r = query.data[i]; - if (v instanceof yy.Column && r[v.columnid] !== undefined) { - r[key] = r[v.columnid]; - } else if (v instanceof yy.Column) { - // Column not found in row, set to null or undefined - r[key] = undefined; - } else { - // For expressions, we'd need to evaluate them, but for now just skip - r[key] = undefined; - } - // Add to removeKeys if not already there - if (i === 0 && query.removeKeys.indexOf(key) === -1) { - query.removeKeys.push(key); - } + // Ordering + if (query.orderfn && !orderApplied) { + if (query.explain) var ms = Date.now(); + query.data = query.data.sort(query.orderfn); + if (query.explain) { + query.explaination.push({ + explid: query.explid++, + description: 'QUERY BY', + ms: Date.now() - ms, + }); } } - } - - // Ordering - if (query.orderfn && !orderApplied) { - if (query.explain) var ms = Date.now(); - query.data = query.data.sort(query.orderfn); - if (query.explain) { - query.explaination.push({ - explid: query.explid++, - description: 'QUERY BY', - ms: Date.now() - ms, - }); - } - } - // Reduce to limit and offset - if (!limitApplied) doLimit(query); + // Reduce to limit and offset + if (!limitApplied) doLimit(query); - // TODO: Check what artefacts rest from Angular.js - if (typeof angular != 'undefined') { - query.removeKeys.push('$$hashKey'); - } + // TODO: Check what artefacts rest from Angular.js + if (typeof angular != 'undefined') { + query.removeKeys.push('$$hashKey'); + } - if (query.removeKeys.length > 0) { - var removeKeys = query.removeKeys; + if (query.removeKeys.length > 0) { + var removeKeys = query.removeKeys; - // Remove from data - jlen = removeKeys.length; - if (jlen > 0) { - ilen = query.data.length; - for (i = 0; i < ilen; i++) { - for (j = 0; j < jlen; j++) { - delete query.data[i][removeKeys[j]]; + // Remove from data + jlen = removeKeys.length; + if (jlen > 0) { + ilen = query.data.length; + for (i = 0; i < ilen; i++) { + for (j = 0; j < jlen; j++) { + delete query.data[i][removeKeys[j]]; + } } } - } - // Remove from columns list - if (query.columns.length > 0) { - query.columns = query.columns.filter(function (column) { - var found = false; - removeKeys.forEach(function (key) { - if (column.columnid == key) found = true; + // Remove from columns list + if (query.columns.length > 0) { + query.columns = query.columns.filter(function (column) { + var found = false; + removeKeys.forEach(function (key) { + if (column.columnid == key) found = true; + }); + return !found; }); - return !found; - }); + } } - } - if (typeof query.removeLikeKeys != 'undefined' && query.removeLikeKeys.length > 0) { - var removeLikeKeys = query.removeLikeKeys; - - // Remove unused columns - // SELECT * REMOVE COLUMNS LIKE "%b" - for (var i = 0, ilen = query.data.length; i < ilen; i++) { - r = query.data[i]; - for (var k in r) { - for (j = 0; j < query.removeLikeKeys.length; j++) { - if (alasql.utils.like(query.removeLikeKeys[j], k)) { - delete r[k]; + if (typeof query.removeLikeKeys != 'undefined' && query.removeLikeKeys.length > 0) { + var removeLikeKeys = query.removeLikeKeys; + + // Remove unused columns + // SELECT * REMOVE COLUMNS LIKE "%b" + for (var i = 0, ilen = query.data.length; i < ilen; i++) { + r = query.data[i]; + for (var k in r) { + for (j = 0; j < query.removeLikeKeys.length; j++) { + if (alasql.utils.like(query.removeLikeKeys[j], k)) { + delete r[k]; + } } } } - } - if (query.columns.length > 0) { - query.columns = query.columns.filter(function (column) { - var found = false; - removeLikeKeys.forEach(function (key) { - if (alasql.utils.like(key, column.columnid)) { - found = true; - } + if (query.columns.length > 0) { + query.columns = query.columns.filter(function (column) { + var found = false; + removeLikeKeys.forEach(function (key) { + if (alasql.utils.like(key, column.columnid)) { + found = true; + } + }); + return !found; }); - return !found; - }); + } } - } - if (query.pivotfn) query.pivotfn(); + if (query.pivotfn) query.pivotfn(); - if (query.unpivotfn) query.unpivotfn(); + if (query.unpivotfn) query.unpivotfn(); - if (query.intoallfn) { - var res = query.intoallfn(query.columns, query.cb, query.params, query.alasql); - return res; - } + if (query.intoallfn) { + var res = query.intoallfn(query.columns, query.cb, query.params, query.alasql); + return res; + } - if (query.intofn) { - ilen = query.data.length; - for (i = 0; i < ilen; i++) { - query.intofn(query.data[i], i, query.params, query.alasql); + if (query.intofn) { + ilen = query.data.length; + for (i = 0; i < ilen; i++) { + query.intofn(query.data[i], i, query.params, query.alasql); + } + if (query.cb) query.cb(query.data.length, query.A, query.B); + return query.data.length; } - if (query.cb) query.cb(query.data.length, query.A, query.B); - return query.data.length; + res = query.data; + if (query.cb) res = query.cb(query.data, query.A, query.B); + return res; } - res = query.data; - if (query.cb) res = query.cb(query.data, query.A, query.B); - return res; -} -// Limiting -function doLimit(query) { - // console.log(query.limit, query.offset) - if (query.limit) { - var offset = 0; - if (query.offset) { - offset = query.offset | 0 || 0; - offset = offset < 0 ? 0 : offset; - } - var limit; - if (query.percent) { - limit = (((query.data.length * query.limit) / 100) | 0) + offset; - } else { - limit = (query.limit | 0) + offset; + // Limiting + function doLimit(query) { + // console.log(query.limit, query.offset) + if (query.limit) { + var offset = 0; + if (query.offset) { + offset = query.offset | 0 || 0; + offset = offset < 0 ? 0 : offset; + } + var limit; + if (query.percent) { + limit = (((query.data.length * query.limit) / 100) | 0) + offset; + } else { + limit = (query.limit | 0) + offset; + } + query.data = query.data.slice(offset, limit); } - query.data = query.data.slice(offset, limit); } -} -// Distinct -function doDistinct(query) { - if (query.distinct) { - var uniq = {}; - // TODO: Speedup, because Object.keys is slow** - // TODO: Problem with DISTINCT on objects - var keys = Object.keys(query.data[0] || []); - for (var i = 0, ilen = query.data.length; i < ilen; i++) { - var uix = keys - .map(function (k) { - var val = query.data[i][k]; - // Properly serialize objects for comparison - return typeof val === 'object' ? JSON.stringify(val) : val; - }) - .join('`'); - uniq[uix] = query.data[i]; - } - query.data = []; - for (var key in uniq) { - query.data.push(uniq[key]); + // Distinct + function doDistinct(query) { + if (query.distinct) { + var uniq = {}; + // TODO: Speedup, because Object.keys is slow** + // TODO: Problem with DISTINCT on objects + var keys = Object.keys(query.data[0] || []); + for (var i = 0, ilen = query.data.length; i < ilen; i++) { + var uix = keys + .map(function (k) { + var val = query.data[i][k]; + // Properly serialize objects for comparison + return typeof val === 'object' ? JSON.stringify(val) : val; + }) + .join('`'); + uniq[uix] = query.data[i]; + } + query.data = []; + for (var key in uniq) { + query.data.push(uniq[key]); + } } } -} -// Optimization: preliminary indexation of joins -var preIndex = function (query) { - // console.log(query); - // Loop over all sources - // Todo: make this loop smaller and more graspable - for (var k = 0, klen = query.sources.length; k < klen; k++) { - var source = query.sources[k]; - delete source.ix; - // Declare variables at function scope for use across if/else branches - var scope, i, ilen, dataw, ixx, addr, group, res; - // If there is indexation rule - if (k > 0 && source.optimization == 'ix' && source.onleftfn && source.onrightfn) { - // If there is no table.indices - create it - if (source.databaseid && alasql.databases[source.databaseid].tables[source.tableid]) { - if (!alasql.databases[source.databaseid].tables[source.tableid].indices) - query.database.tables[source.tableid].indices = {}; - // Check if index already exists - ixx = - alasql.databases[source.databaseid].tables[source.tableid].indices[ - hash(source.onrightfns + '`' + source.srcwherefns) - ]; - if (!alasql.databases[source.databaseid].tables[source.tableid].dirty && ixx) { - source.ix = ixx; + // Optimization: preliminary indexation of joins + var preIndex = function (query) { + // console.log(query); + // Loop over all sources + // Todo: make this loop smaller and more graspable + for (var k = 0, klen = query.sources.length; k < klen; k++) { + var source = query.sources[k]; + delete source.ix; + // Declare variables at function scope for use across if/else branches + var scope, i, ilen, dataw, ixx, addr, group, res; + // If there is indexation rule + if (k > 0 && source.optimization == 'ix' && source.onleftfn && source.onrightfn) { + // If there is no table.indices - create it + if (source.databaseid && alasql.databases[source.databaseid].tables[source.tableid]) { + if (!alasql.databases[source.databaseid].tables[source.tableid].indices) + query.database.tables[source.tableid].indices = {}; + // Check if index already exists + ixx = + alasql.databases[source.databaseid].tables[source.tableid].indices[ + hash(source.onrightfns + '`' + source.srcwherefns) + ]; + if (!alasql.databases[source.databaseid].tables[source.tableid].dirty && ixx) { + source.ix = ixx; + } } - } - if (!source.ix) { - source.ix = {}; - // Walking over source data - scope = {}; - i = 0; - ilen = source.data.length; - // while(source.getfn i { - const rr = []; - let mask = 0; - const glen = a.length; + const rollup = (a, query) => { + const rr = []; + let mask = 0; + const glen = a.length; - for (let g = 0; g < glen + 1; g++) { - const ss = []; - for (let i = 0; i < glen; i++) { - let aaa; - if (a[i] instanceof yy.Column) { - a[i].nick = escapeq(a[i].columnid); - query.groupColumns[escapeq(a[i].columnid)] = a[i].nick; - aaa = `${a[i].nick}\t${a[i].toJS('p', query.sources[0].alias, query.defcols)}`; - } else { - query.groupColumns[escapeq(a[i].toString())] = escapeq(a[i].toString()); - aaa = `${escapeq(a[i].toString())}\t${a[i].toJS('p', query.sources[0].alias, query.defcols)}`; - } + for (let g = 0; g < glen + 1; g++) { + const ss = []; + for (let i = 0; i < glen; i++) { + let aaa; + if (a[i] instanceof yy.Column) { + a[i].nick = escapeq(a[i].columnid); + query.groupColumns[escapeq(a[i].columnid)] = a[i].nick; + aaa = `${a[i].nick}\t${a[i].toJS('p', query.sources[0].alias, query.defcols)}`; + } else { + query.groupColumns[escapeq(a[i].toString())] = escapeq(a[i].toString()); + aaa = `${escapeq(a[i].toString())}\t${a[i].toJS('p', query.sources[0].alias, query.defcols)}`; + } - if (mask & (1 << i)) ss.push(aaa); + if (mask & (1 << i)) ss.push(aaa); + } + rr.push(ss); + mask = (mask << 1) + 1; } - rr.push(ss); - mask = (mask << 1) + 1; - } - return rr; -}; + return rr; + }; -/** + /** Calculate CUBE() */ -const cube = (a, query) => { - const rr = []; - const glen = a.length; - const glenCube = 1 << glen; + const cube = (a, query) => { + const rr = []; + const glen = a.length; + const glenCube = 1 << glen; - for (let g = 0; g < glenCube; g++) { - let ss = []; - for (let i = 0; i < glen; i++) { - if (g & (1 << i)) { - ss = ss.concat(decartes(a[i], query)); + for (let g = 0; g < glenCube; g++) { + let ss = []; + for (let i = 0; i < glen; i++) { + if (g & (1 << i)) { + ss = ss.concat(decartes(a[i], query)); + } } + rr.push(ss); } - rr.push(ss); - } - return rr; -}; + return rr; + }; -/** - * GROUPING SETS() - */ -const groupingsets = (a, query) => - a.reduce((acc, d) => { - acc = acc.concat(decartes(d, query)); - return acc; - }, []); + /** + * GROUPING SETS() + */ + const groupingsets = (a, query) => + a.reduce((acc, d) => { + acc = acc.concat(decartes(d, query)); + return acc; + }, []); -/** - * Cartesian production - */ -const cartes = (a1, a2) => { - const rrr = []; - for (let i1 = 0; i1 < a1.length; i1++) { - for (let i2 = 0; i2 < a2.length; i2++) { - rrr.push(a1[i1].concat(a2[i2])); + /** + * Cartesian production + */ + const cartes = (a1, a2) => { + const rrr = []; + for (let i1 = 0; i1 < a1.length; i1++) { + for (let i2 = 0; i2 < a2.length; i2++) { + rrr.push(a1[i1].concat(a2[i2])); + } } - } - return rrr; -}; + return rrr; + }; -/** + /** Prepare groups function */ function decartes(gv, query) { - if (Array.isArray(gv)) { - let res = [[]]; - for (let t = 0; t < gv.length; t++) { - if (gv[t] instanceof yy.Column) { - gv[t].nick = gv[t].nick ? escapeq(gv[t].nick) : escapeq(gv[t].columnid); - query.groupColumns[gv[t].nick] = gv[t].nick; - res = res.map(r => - r.concat(`${gv[t].nick}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}`) - ); - } else if (gv[t] instanceof yy.FuncValue) { - query.groupColumns[escapeq(gv[t].toString())] = escapeq(gv[t].toString()); - res = res.map(r => - r.concat( - `${escapeq(gv[t].toString())}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}` - ) - ); - } else if (gv[t] instanceof yy.GroupExpression) { - if (gv[t].type == 'ROLLUP') res = cartes(res, rollup(gv[t].group, query)); - else if (gv[t].type == 'CUBE') res = cartes(res, cube(gv[t].group, query)); - else if (gv[t].type == 'GROUPING SETS') res = cartes(res, groupingsets(gv[t].group, query)); - else throw new Error('Unknown grouping function'); - } else if (gv[t] === '') { - res = [['1\t1']]; - } else { - res = res.map(r => - r.concat( - `${escapeq(gv[t].toString())}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}` - ) - ); + if (Array.isArray(gv)) { + let res = [[]]; + for (let t = 0; t < gv.length; t++) { + if (gv[t] instanceof yy.Column) { + gv[t].nick = gv[t].nick ? escapeq(gv[t].nick) : escapeq(gv[t].columnid); + query.groupColumns[gv[t].nick] = gv[t].nick; + res = res.map(r => + r.concat(`${gv[t].nick}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}`) + ); + } else if (gv[t] instanceof yy.FuncValue) { + query.groupColumns[escapeq(gv[t].toString())] = escapeq(gv[t].toString()); + res = res.map(r => + r.concat( + `${escapeq(gv[t].toString())}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}` + ) + ); + } else if (gv[t] instanceof yy.GroupExpression) { + if (gv[t].type == 'ROLLUP') res = cartes(res, rollup(gv[t].group, query)); + else if (gv[t].type == 'CUBE') res = cartes(res, cube(gv[t].group, query)); + else if (gv[t].type == 'GROUPING SETS') + res = cartes(res, groupingsets(gv[t].group, query)); + else throw new Error('Unknown grouping function'); + } else if (gv[t] === '') { + res = [['1\t1']]; + } else { + res = res.map(r => + r.concat( + `${escapeq(gv[t].toString())}\t${gv[t].toJS('p', query.sources[0].alias, query.defcols)}` + ) + ); + } } + return res; } - return res; - } - if (gv instanceof yy.FuncValue) { - query.groupColumns[escapeq(gv.toString())] = escapeq(gv.toString()); - return [`${gv.toString()}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; - } + if (gv instanceof yy.FuncValue) { + query.groupColumns[escapeq(gv.toString())] = escapeq(gv.toString()); + return [`${gv.toString()}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; + } - if (gv instanceof yy.Column) { - gv.nick = escapeq(gv.columnid); - query.groupColumns[gv.nick] = gv.nick; - return [`${gv.nick}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; - } + if (gv instanceof yy.Column) { + gv.nick = escapeq(gv.columnid); + query.groupColumns[gv.nick] = gv.nick; + return [`${gv.nick}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; + } - query.groupColumns[escapeq(gv.toString())] = escapeq(gv.toString()); - return [`${escapeq(gv.toString())}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; -} + query.groupColumns[escapeq(gv.toString())] = escapeq(gv.toString()); + return [`${escapeq(gv.toString())}\t${gv.toJS('p', query.sources[0].alias, query.defcols)}`]; + } internal.decartes = decartes; } diff --git a/src/select/select.js b/src/select/select.js index bb3da1c386..e3051fef7d 100755 --- a/src/select/select.js +++ b/src/select/select.js @@ -2,7 +2,7 @@ export default function install(alasql, internal) { var yy = alasql.yy; var Query = alasql.Query; var queryfn = internal.queryfn; -/* + /* // // Select run-time part for Alasql.js // Date: 03.11.2014 @@ -10,305 +10,307 @@ export default function install(alasql, internal) { // */ -// -// Main part of SELECT procedure -// + // + // Main part of SELECT procedure + // -/* global yy */ - -yy.Select = class Select { - constructor(params) { - Object.assign(this, params); - } + /* global yy */ - toString() { - var s; - s = ''; - if (this.explain) { - s += 'EXPLAIN '; + yy.Select = class Select { + constructor(params) { + Object.assign(this, params); } - s += 'SELECT '; - if (this.modifier) { - s += this.modifier + ' '; - } - if (this.distinct) { - s += 'DISTINCT '; - } - if (this.top) { - s += 'TOP ' + this.top.toString() + ' '; - if (this.percent) { - s += 'PERCENT '; + + toString() { + var s; + s = ''; + if (this.explain) { + s += 'EXPLAIN '; } - } - s += this.columns - .map(function (col) { - var s; - s = col.toString(); - if (typeof col.as !== 'undefined') { - s += ' AS ' + col.as; + s += 'SELECT '; + if (this.modifier) { + s += this.modifier + ' '; + } + if (this.distinct) { + s += 'DISTINCT '; + } + if (this.top) { + s += 'TOP ' + this.top.toString() + ' '; + if (this.percent) { + s += 'PERCENT '; } - return s; - }) - .join(', '); - if (this.from) { - s += - ' FROM ' + - this.from - .map(function (f) { + } + s += this.columns + .map(function (col) { + var s; + s = col.toString(); + if (typeof col.as !== 'undefined') { + s += ' AS ' + col.as; + } + return s; + }) + .join(', '); + if (this.from) { + s += + ' FROM ' + + this.from + .map(function (f) { + var ss; + ss = f.toString(); + if (f.as) { + ss += ' AS ' + f.as; + } + return ss; + }) + .join(','); + } + if (this.joins) { + s += this.joins + .map(function (jn) { var ss; - ss = f.toString(); - if (f.as) { - ss += ' AS ' + f.as; + ss = ' '; + if (jn.joinmode) { + ss += jn.joinmode + ' '; + } + if (jn.table) { + ss += 'JOIN ' + jn.table.toString(); + } else if (jn.select) { + ss += 'JOIN (' + jn.select.toString() + ')'; + } else if (jn instanceof alasql.yy.Apply) { + ss += jn.toString(); + } else { + throw new Error('Wrong type in JOIN mode'); + } + if (jn.as) { + ss += ' AS ' + jn.as; + } + if (jn.using) { + ss += ' USING ' + jn.using.toString(); + } + if (jn.on) { + ss += ' ON ' + jn.on.toString(); } return ss; }) - .join(','); - } - if (this.joins) { - s += this.joins - .map(function (jn) { - var ss; - ss = ' '; - if (jn.joinmode) { - ss += jn.joinmode + ' '; - } - if (jn.table) { - ss += 'JOIN ' + jn.table.toString(); - } else if (jn.select) { - ss += 'JOIN (' + jn.select.toString() + ')'; - } else if (jn instanceof alasql.yy.Apply) { - ss += jn.toString(); - } else { - throw new Error('Wrong type in JOIN mode'); - } - if (jn.as) { - ss += ' AS ' + jn.as; - } - if (jn.using) { - ss += ' USING ' + jn.using.toString(); - } - if (jn.on) { - ss += ' ON ' + jn.on.toString(); - } - return ss; - }) - .join(''); - } - if (this.where) { - s += ' WHERE ' + this.where.toString(); - } - if (this.group && this.group.length > 0) { - s += - ' GROUP BY ' + - this.group - .map(function (grp) { - return grp.toString(); - }) - .join(', '); - } - if (this.having) { - s += ' HAVING ' + this.having.toString(); - } - if (this.order && this.order.length > 0) { - s += - ' ORDER BY ' + - this.order - .map(function (ord) { - return ord.toString(); - }) - .join(', '); - } - if (this.limit) { - s += ' LIMIT ' + this.limit.toString(); - } - if (this.offset) { - s += ' OFFSET ' + this.offset.toString(); - } - if (this.union) { - s += ' UNION ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.union.toString(); - } - if (this.unionall) { - s += ' UNION ALL ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.unionall.toString(); - } - if (this.except) { - s += ' EXCEPT ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.except.toString(); - } - if (this.intersect) { - s += ' INTERSECT ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.intersect.toString(); + .join(''); + } + if (this.where) { + s += ' WHERE ' + this.where.toString(); + } + if (this.group && this.group.length > 0) { + s += + ' GROUP BY ' + + this.group + .map(function (grp) { + return grp.toString(); + }) + .join(', '); + } + if (this.having) { + s += ' HAVING ' + this.having.toString(); + } + if (this.order && this.order.length > 0) { + s += + ' ORDER BY ' + + this.order + .map(function (ord) { + return ord.toString(); + }) + .join(', '); + } + if (this.limit) { + s += ' LIMIT ' + this.limit.toString(); + } + if (this.offset) { + s += ' OFFSET ' + this.offset.toString(); + } + if (this.union) { + s += ' UNION ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.union.toString(); + } + if (this.unionall) { + s += + ' UNION ALL ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.unionall.toString(); + } + if (this.except) { + s += ' EXCEPT ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.except.toString(); + } + if (this.intersect) { + s += + ' INTERSECT ' + (this.corresponding ? 'CORRESPONDING ' : '') + this.intersect.toString(); + } + return s; } - return s; - } - /** + /** Select statement in expression */ - toJS(context) { - let outerContext = context === 'g' ? '(this.groupSources.get(g) || g)' : context; - let s = - 'alasql.utils.flatArray(this.queriesfn[' + - (this.queriesidx - 1) + - '](this.params,null,' + - outerContext + - '))[0]'; - - // let s = '(ee=alasql.utils.flatArray(this.queriesfn['+(this.queriesidx-1)+'](this.params,null,'+context+')),console.log(999,ee),ee[0])'; - return s; - } + toJS(context) { + let outerContext = context === 'g' ? '(this.groupSources.get(g) || g)' : context; + let s = + 'alasql.utils.flatArray(this.queriesfn[' + + (this.queriesidx - 1) + + '](this.params,null,' + + outerContext + + '))[0]'; + + // let s = '(ee=alasql.utils.flatArray(this.queriesfn['+(this.queriesidx-1)+'](this.params,null,'+context+')),console.log(999,ee),ee[0])'; + return s; + } - // Compile SELECT statement - compile(databaseid, params) { - var db = alasql.databases[databaseid]; - // Create variable for query - var query = new Query(); + // Compile SELECT statement + compile(databaseid, params) { + var db = alasql.databases[databaseid]; + // Create variable for query + var query = new Query(); - // Array with columns to be removed - query.removeKeys = []; - query.aggrKeys = []; + // Array with columns to be removed + query.removeKeys = []; + query.aggrKeys = []; - query.explain = this.explain; // Explain - query.explaination = []; - query.explid = 1; - query.modifier = this.modifier; + query.explain = this.explain; // Explain + query.explaination = []; + query.explid = 1; + query.modifier = this.modifier; - query.database = db; - // 0. Precompile whereexists - this.compileWhereExists(query); + query.database = db; + // 0. Precompile whereexists + this.compileWhereExists(query); - // 0. Precompile queries for IN, NOT IN, ANY and ALL operators - this.compileQueries(query); + // 0. Precompile queries for IN, NOT IN, ANY and ALL operators + this.compileQueries(query); - query.defcols = this.compileDefCols(query, databaseid); + query.defcols = this.compileDefCols(query, databaseid); - // 1. Compile FROM clause - query.fromfn = this.compileFrom(query); + // 1. Compile FROM clause + query.fromfn = this.compileFrom(query); - // 2. Compile JOIN clauses - if (this.joins) { - this.compileJoins(query); - } + // 2. Compile JOIN clauses + if (this.joins) { + this.compileJoins(query); + } - // todo?: 3. Compile SELECT clause - // For ROWNUM() - query.rownums = []; - query.grouprownums = []; - query.windowaggrs = []; // For window aggregate functions (COUNT/MAX/MIN/SUM/AVG with OVER) + // todo?: 3. Compile SELECT clause + // For ROWNUM() + query.rownums = []; + query.grouprownums = []; + query.windowaggrs = []; // For window aggregate functions (COUNT/MAX/MIN/SUM/AVG with OVER) - // Check if INTO OBJECT() is used - this affects how arrow expressions are compiled - if (this.into instanceof yy.FuncValue && this.into.funcid.toUpperCase() === 'OBJECT') { - query.intoObject = true; - } + // Check if INTO OBJECT() is used - this affects how arrow expressions are compiled + if (this.into instanceof yy.FuncValue && this.into.funcid.toUpperCase() === 'OBJECT') { + query.intoObject = true; + } - this.compileSelectGroup0(query); + this.compileSelectGroup0(query); - if (this.group || query.selectGroup.length > 0) { - query.selectgfns = this.compileSelectGroup1(query); - } else { - query.selectfns = this.compileSelect1(query, params); - } + if (this.group || query.selectGroup.length > 0) { + query.selectgfns = this.compileSelectGroup1(query); + } else { + query.selectfns = this.compileSelect1(query, params); + } - // Remove columns clause - this.compileRemoveColumns(query); + // Remove columns clause + this.compileRemoveColumns(query); - // 5. Optimize WHERE and JOINS - if (this.where) { - this.compileWhereJoins(query); - } + // 5. Optimize WHERE and JOINS + if (this.where) { + this.compileWhereJoins(query); + } - // 4. Compile WHERE clause - query.wherefn = this.compileWhere(query); + // 4. Compile WHERE clause + query.wherefn = this.compileWhere(query); - // 6. Compile GROUP BY - if (this.group || query.selectGroup.length > 0) { - query.groupfn = this.compileGroup(query); - } + // 6. Compile GROUP BY + if (this.group || query.selectGroup.length > 0) { + query.groupfn = this.compileGroup(query); + } - // 6. Compile HAVING - if (this.having) { - query.havingfn = this.compileHaving(query); - } + // 6. Compile HAVING + if (this.having) { + query.havingfn = this.compileHaving(query); + } - // 8. Compile ORDER BY clause - if (this.order) { - query.orderfn = this.compileOrder(query, params); - // Copy orderColumns to query for union handling - query.orderColumns = this.orderColumns; - } + // 8. Compile ORDER BY clause + if (this.order) { + query.orderfn = this.compileOrder(query, params); + // Copy orderColumns to query for union handling + query.orderColumns = this.orderColumns; + } - if (this.group || query.selectGroup.length > 0) { - query.selectgfn = this.compileSelectGroup2(query); - } else { - query.selectfn = this.compileSelect2(query, params); - } + if (this.group || query.selectGroup.length > 0) { + query.selectgfn = this.compileSelectGroup2(query); + } else { + query.selectfn = this.compileSelect2(query, params); + } - // 7. Compile DISTINCT, LIMIT and OFFSET - query.distinct = this.distinct; + // 7. Compile DISTINCT, LIMIT and OFFSET + query.distinct = this.distinct; - // 9. Compile PIVOT clause - if (this.pivot) query.pivotfn = this.compilePivot(query); - if (this.unpivot) query.pivotfn = this.compileUnpivot(query); + // 9. Compile PIVOT clause + if (this.pivot) query.pivotfn = this.compilePivot(query); + if (this.unpivot) query.pivotfn = this.compileUnpivot(query); - // 10. Compile TOP/LIMIT/OFFSET/FETCH clause - if (this.top) { - if (this.top instanceof yy.ParamValue) { - query.limitParam = this.top.param; - } else { - query.limit = this.top.value; - } - } else if (this.limit) { - if (this.limit instanceof yy.ParamValue) { - query.limitParam = this.limit.param; - } else { - query.limit = this.limit.value; - } - if (this.offset) { - if (this.offset instanceof yy.ParamValue) { - query.offsetParam = this.offset.param; + // 10. Compile TOP/LIMIT/OFFSET/FETCH clause + if (this.top) { + if (this.top instanceof yy.ParamValue) { + query.limitParam = this.top.param; } else { - query.offset = this.offset.value; + query.limit = this.top.value; + } + } else if (this.limit) { + if (this.limit instanceof yy.ParamValue) { + query.limitParam = this.limit.param; + } else { + query.limit = this.limit.value; + } + if (this.offset) { + if (this.offset instanceof yy.ParamValue) { + query.offsetParam = this.offset.param; + } else { + query.offset = this.offset.value; + } } } - } - query.percent = this.percent; + query.percent = this.percent; - // 9. Compile ordering function for UNION and UNIONALL - query.corresponding = this.corresponding; // If CORRESPONDING flag exists - if (this.union) { - query.unionfn = this.union.compile(databaseid); - // ORDER BY is now at the top level, not in the union clause - if (!query.orderfn && this.union.order) { - query.orderfn = this.union.compileOrder(query, params); - } - } else if (this.unionall) { - query.unionallfn = this.unionall.compile(databaseid); - // ORDER BY is now at the top level, not in the unionall clause - if (!query.orderfn && this.unionall.order) { - query.orderfn = this.unionall.compileOrder(query, params); - } - } else if (this.except) { - query.exceptfn = this.except.compile(databaseid); - // ORDER BY is now at the top level, not in the except clause - if (!query.orderfn && this.except.order) { - query.orderfn = this.except.compileOrder(query, params); - } - } else if (this.intersect) { - query.intersectfn = this.intersect.compile(databaseid); - // ORDER BY is now at the top level, not in the intersect clause - if (!query.orderfn && this.intersect.order) { - query.orderfn = this.intersect.compileOrder(query, params); + // 9. Compile ordering function for UNION and UNIONALL + query.corresponding = this.corresponding; // If CORRESPONDING flag exists + if (this.union) { + query.unionfn = this.union.compile(databaseid); + // ORDER BY is now at the top level, not in the union clause + if (!query.orderfn && this.union.order) { + query.orderfn = this.union.compileOrder(query, params); + } + } else if (this.unionall) { + query.unionallfn = this.unionall.compile(databaseid); + // ORDER BY is now at the top level, not in the unionall clause + if (!query.orderfn && this.unionall.order) { + query.orderfn = this.unionall.compileOrder(query, params); + } + } else if (this.except) { + query.exceptfn = this.except.compile(databaseid); + // ORDER BY is now at the top level, not in the except clause + if (!query.orderfn && this.except.order) { + query.orderfn = this.except.compileOrder(query, params); + } + } else if (this.intersect) { + query.intersectfn = this.intersect.compile(databaseid); + // ORDER BY is now at the top level, not in the intersect clause + if (!query.orderfn && this.intersect.order) { + query.orderfn = this.intersect.compileOrder(query, params); + } } - } - // SELECT INTO - if (this.into) { - if (this.into instanceof yy.Table) { - // Save into the table in database - if ( - alasql.options.autocommit && - alasql.databases[this.into.databaseid || databaseid].engineid - ) { - // For external database when AUTOCOMMIT is ONs - query.intoallfns = `return alasql + // SELECT INTO + if (this.into) { + if (this.into instanceof yy.Table) { + // Save into the table in database + if ( + alasql.options.autocommit && + alasql.databases[this.into.databaseid || databaseid].engineid + ) { + // For external database when AUTOCOMMIT is ONs + query.intoallfns = `return alasql .engines[${JSON.stringify(alasql.databases[this.into.databaseid || databaseid].engineid)}] .intoTable( ${JSON.stringify(this.into.databaseid || databaseid)}, @@ -317,11 +319,11 @@ yy.Select = class Select { columns, cb );`; - } else { - // Into AlaSQL tables - convert types based on column definitions - var dbid = this.into.databaseid || databaseid; - var tblid = this.into.tableid; - query.intofns = ` + } else { + // Into AlaSQL tables - convert types based on column definitions + var dbid = this.into.databaseid || databaseid; + var tblid = this.into.tableid; + query.intofns = ` var db = alasql.databases[${JSON.stringify(dbid)}]; var table = db.tables[${JSON.stringify(tblid)}]; var converted = {}; @@ -331,458 +333,461 @@ yy.Select = class Select { } table.data.push(converted); `; - } - } else if (this.into instanceof yy.VarValue) { - // - // Save into local variable - // SELECT * INTO @VAR1 FROM ? - // - query.intoallfns = ` + } + } else if (this.into instanceof yy.VarValue) { + // + // Save into local variable + // SELECT * INTO @VAR1 FROM ? + // + query.intoallfns = ` alasql.vars[${JSON.stringify(this.into.variable)}]=this.data; res=this.data.length; if(cb) res = cb(res); return res; `; - } else if (this.into instanceof yy.FuncValue) { - // - // If this is INTO() function, then call it - // with one or two parameters - // - var funcid = this.into.funcid.toUpperCase(); - var qs = 'return alasql.into[' + JSON.stringify(funcid) + ']('; - if (this.into.args && this.into.args.length > 0) { - qs += this.into.args[0].toJS() + ','; - if (this.into.args.length > 1) { - qs += this.into.args[1].toJS() + ','; + } else if (this.into instanceof yy.FuncValue) { + // + // If this is INTO() function, then call it + // with one or two parameters + // + var funcid = this.into.funcid.toUpperCase(); + var qs = 'return alasql.into[' + JSON.stringify(funcid) + ']('; + if (this.into.args && this.into.args.length > 0) { + qs += this.into.args[0].toJS() + ','; + if (this.into.args.length > 1) { + qs += this.into.args[1].toJS() + ','; + } else { + qs += 'undefined,'; + } } else { - qs += 'undefined,'; + qs += 'undefined, undefined,'; } - } else { - qs += 'undefined, undefined,'; - } - query.intoallfns = qs + 'this.data,columns,cb)'; - // Mark that OBJECT should preserve array results - if (funcid === 'OBJECT') { - query.preserveArrayResult = true; - } - } else if (this.into instanceof yy.ParamValue) { - // - // Save data into parameters array - // like alasql('SELECT * INTO ? FROM ?',[outdata,srcdata]); - // or SELECT * INTO $variable FROM ? - // - // Distinguish between ? (numeric param - push to array) and $variable (string param - replace array) - if (typeof this.into.param === 'string') { - // $variable syntax - replace the array - query.intoallfns = ` + query.intoallfns = qs + 'this.data,columns,cb)'; + // Mark that OBJECT should preserve array results + if (funcid === 'OBJECT') { + query.preserveArrayResult = true; + } + } else if (this.into instanceof yy.ParamValue) { + // + // Save data into parameters array + // like alasql('SELECT * INTO ? FROM ?',[outdata,srcdata]); + // or SELECT * INTO $variable FROM ? + // + // Distinguish between ? (numeric param - push to array) and $variable (string param - replace array) + if (typeof this.into.param === 'string') { + // $variable syntax - replace the array + query.intoallfns = ` if(!params[${JSON.stringify(this.into.param)}]) params[${JSON.stringify(this.into.param)}]=[]; params[${JSON.stringify(this.into.param)}]=this.data; res=this.data.length; if(cb) res = cb(res); return res; `; - } else { - // ? syntax - push to existing array - query.intofns = `params[${JSON.stringify(this.into.param)}].push(r)`; + } else { + // ? syntax - push to existing array + query.intofns = `params[${JSON.stringify(this.into.param)}].push(r)`; + } } - } - if (query.intofns) { - // Create intofn function - query.intofn = new Function('r,i,params,alasql', 'var y;' + query.intofns); - } else if (query.intoallfns) { - // Create intoallfn function - query.intoallfn = new Function('columns,cb,params,alasql', 'var y;' + query.intoallfns); - } - } - // Now, compile all togeather into one function with query object in scope - var statement = function (params, cb, oldscope) { - query.params = params; - if (typeof query.limitParam !== 'undefined') { - query.limit = params ? params[query.limitParam] : undefined; - } - if (typeof query.offsetParam !== 'undefined') { - query.offset = params ? params[query.offsetParam] : undefined; + if (query.intofns) { + // Create intofn function + query.intofn = new Function('r,i,params,alasql', 'var y;' + query.intofns); + } else if (query.intoallfns) { + // Create intoallfn function + query.intoallfn = new Function('columns,cb,params,alasql', 'var y;' + query.intoallfns); + } } - // Note the callback function has the data and error reversed due to existing code in promiseExec which has the - // err and data swapped. This trickles down into alasql.exec and further. Rather than risk breaking the whole thing, - // the (data, err) standard is maintained here. - var res1 = queryfn(query, oldscope, function (res, err) { - if (err) { - if (cb) { - return cb(null, err); - } - throw err; + // Now, compile all togeather into one function with query object in scope + var statement = function (params, cb, oldscope) { + query.params = params; + if (typeof query.limitParam !== 'undefined') { + query.limit = params ? params[query.limitParam] : undefined; + } + if (typeof query.offsetParam !== 'undefined') { + query.offset = params ? params[query.offsetParam] : undefined; } - if (query.rownums.length > 0) { - for (var i = 0, ilen = res.length; i < ilen; i++) { - for (var j = 0, jlen = query.rownums.length; j < jlen; j++) { - res[i][query.rownums[j]] = i + 1; + // Note the callback function has the data and error reversed due to existing code in promiseExec which has the + // err and data swapped. This trickles down into alasql.exec and further. Rather than risk breaking the whole thing, + // the (data, err) standard is maintained here. + var res1 = queryfn(query, oldscope, function (res, err) { + if (err) { + if (cb) { + return cb(null, err); } + throw err; } - } - - // Handle GROUP_ROW_NUMBER() and ROW_NUMBER() OVER (PARTITION BY ...) - restart numbering when grouping column(s) change - if (query.grouprownums && query.grouprownums.length > 0) { - for (var j = 0, jlen = query.grouprownums.length; j < jlen; j++) { - var config = query.grouprownums[j]; - var partitionColumns; - - // Determine which columns to partition by - if (config.partitionColumns && config.partitionColumns.length > 0) { - // Use explicit PARTITION BY columns - partitionColumns = config.partitionColumns; - } else { - // Fall back to first column for GROUP_ROW_NUMBER() - var columnKeys = Object.keys(res[0] || {}); - partitionColumns = [columnKeys[0]]; + if (query.rownums.length > 0) { + for (var i = 0, ilen = res.length; i < ilen; i++) { + for (var j = 0, jlen = query.rownums.length; j < jlen; j++) { + res[i][query.rownums[j]] = i + 1; + } } + } - var prevValues = null; - var rowNum = 0; + // Handle GROUP_ROW_NUMBER() and ROW_NUMBER() OVER (PARTITION BY ...) - restart numbering when grouping column(s) change + if (query.grouprownums && query.grouprownums.length > 0) { + for (var j = 0, jlen = query.grouprownums.length; j < jlen; j++) { + var config = query.grouprownums[j]; + var partitionColumns; - for (var i = 0, ilen = res.length; i < ilen; i++) { - // Get current partition key (combination of all partition columns) - var currentValues = partitionColumns - .map(function (col) { - return res[i][col]; - }) - .join('|'); - - // Reset counter when partition changes - if (i === 0 || currentValues !== prevValues) { - rowNum = 1; + // Determine which columns to partition by + if (config.partitionColumns && config.partitionColumns.length > 0) { + // Use explicit PARTITION BY columns + partitionColumns = config.partitionColumns; } else { - rowNum++; + // Fall back to first column for GROUP_ROW_NUMBER() + var columnKeys = Object.keys(res[0] || {}); + partitionColumns = [columnKeys[0]]; } - res[i][config.as] = rowNum; - prevValues = currentValues; + var prevValues = null; + var rowNum = 0; + + for (var i = 0, ilen = res.length; i < ilen; i++) { + // Get current partition key (combination of all partition columns) + var currentValues = partitionColumns + .map(function (col) { + return res[i][col]; + }) + .join('|'); + + // Reset counter when partition changes + if (i === 0 || currentValues !== prevValues) { + rowNum = 1; + } else { + rowNum++; + } + + res[i][config.as] = rowNum; + prevValues = currentValues; + } } } - } - // Handle window aggregate functions - COUNT/MAX/MIN/SUM/AVG with OVER (PARTITION BY ...) - if (query.windowaggrs && query.windowaggrs.length > 0) { - for (var j = 0, jlen = query.windowaggrs.length; j < jlen; j++) { - var config = query.windowaggrs[j]; - var partitions = {}; + // Handle window aggregate functions - COUNT/MAX/MIN/SUM/AVG with OVER (PARTITION BY ...) + if (query.windowaggrs && query.windowaggrs.length > 0) { + for (var j = 0, jlen = query.windowaggrs.length; j < jlen; j++) { + var config = query.windowaggrs[j]; + var partitions = {}; + + // Group rows by partition + for (var i = 0, ilen = res.length; i < ilen; i++) { + var partitionKey = + config.partitionColumns && config.partitionColumns.length > 0 + ? config.partitionColumns + .map(function (col) { + return res[i][col]; + }) + .join('|') + : '__all__'; + + if (!partitions[partitionKey]) partitions[partitionKey] = []; + partitions[partitionKey].push(i); + } - // Group rows by partition - for (var i = 0, ilen = res.length; i < ilen; i++) { - var partitionKey = - config.partitionColumns && config.partitionColumns.length > 0 - ? config.partitionColumns - .map(function (col) { - return res[i][col]; - }) - .join('|') - : '__all__'; - - if (!partitions[partitionKey]) partitions[partitionKey] = []; - partitions[partitionKey].push(i); - } + // Calculate and assign aggregate for each partition + for (var partitionKey in partitions) { + var rowIndices = partitions[partitionKey]; + var values = []; + var colId = config.expression && config.expression.columnid; + + // Collect values from partition rows + if (config.aggregatorid !== 'COUNT' || (colId && colId !== '*')) { + for (var k = 0; k < rowIndices.length; k++) { + var val = res[rowIndices[k]][colId]; + if (val != null) values.push(val); + } + } - // Calculate and assign aggregate for each partition - for (var partitionKey in partitions) { - var rowIndices = partitions[partitionKey]; - var values = []; - var colId = config.expression && config.expression.columnid; + // Calculate aggregate + var aggregateValue; + switch (config.aggregatorid) { + case 'COUNT': + aggregateValue = colId && colId !== '*' ? values.length : rowIndices.length; + break; + case 'SUM': + aggregateValue = values.reduce(function (sum, v) { + return sum + v; + }, 0); + break; + case 'AVG': + aggregateValue = + values.length > 0 + ? values.reduce(function (sum, v) { + return sum + v; + }, 0) / values.length + : null; + break; + case 'MAX': + aggregateValue = values.length > 0 ? Math.max.apply(null, values) : null; + break; + case 'MIN': + aggregateValue = values.length > 0 ? Math.min.apply(null, values) : null; + break; + } - // Collect values from partition rows - if (config.aggregatorid !== 'COUNT' || (colId && colId !== '*')) { + // Assign aggregate value to all rows in partition for (var k = 0; k < rowIndices.length; k++) { - var val = res[rowIndices[k]][colId]; - if (val != null) values.push(val); + res[rowIndices[k]][config.as] = aggregateValue; } } - - // Calculate aggregate - var aggregateValue; - switch (config.aggregatorid) { - case 'COUNT': - aggregateValue = colId && colId !== '*' ? values.length : rowIndices.length; - break; - case 'SUM': - aggregateValue = values.reduce(function (sum, v) { - return sum + v; - }, 0); - break; - case 'AVG': - aggregateValue = - values.length > 0 - ? values.reduce(function (sum, v) { - return sum + v; - }, 0) / values.length - : null; - break; - case 'MAX': - aggregateValue = values.length > 0 ? Math.max.apply(null, values) : null; - break; - case 'MIN': - aggregateValue = values.length > 0 ? Math.min.apply(null, values) : null; - break; - } - - // Assign aggregate value to all rows in partition - for (var k = 0; k < rowIndices.length; k++) { - res[rowIndices[k]][config.as] = aggregateValue; - } } } - } - var res2 = modify(query, res); + var res2 = modify(query, res); - if (cb) { - cb(res2); - } - return res2; - }); - return res1; - }; + if (cb) { + cb(res2); + } + return res2; + }); + return res1; + }; - statement.query = query; - return statement; - } + statement.query = query; + return statement; + } - execute(databaseid, params, cb) { - return this.compile(databaseid)(params, cb); - // throw new Error('Insert statement is should be compiled') - } + execute(databaseid, params, cb) { + return this.compile(databaseid)(params, cb); + // throw new Error('Insert statement is should be compiled') + } - compileWhereExists(query) { - if (!this.exists) return; - query.existsfn = this.exists.map(function (ex) { - var nq = ex.compile(query.database.databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } + compileWhereExists(query) { + if (!this.exists) return; + query.existsfn = this.exists.map(function (ex) { + var nq = ex.compile(query.database.databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); + } - compileQueries(query) { - if (!this.queries) return; + compileQueries(query) { + if (!this.queries) return; - // Helper function to detect if a subquery might be correlated - // A subquery is correlated if it references tables not in its own FROM clause - const isCorrelated = (subquery, outerQuery) => { - if (!subquery.from) return false; + // Helper function to detect if a subquery might be correlated + // A subquery is correlated if it references tables not in its own FROM clause + const isCorrelated = (subquery, outerQuery) => { + if (!subquery.from) return false; - // Get table names from subquery's FROM clause - const subqueryTables = new Set(); - subquery.from.forEach(f => { - if (f.tableid) subqueryTables.add(f.tableid); - if (f.as) subqueryTables.add(f.as); - }); + // Get table names from subquery's FROM clause + const subqueryTables = new Set(); + subquery.from.forEach(f => { + if (f.tableid) subqueryTables.add(f.tableid); + if (f.as) subqueryTables.add(f.as); + }); - // Check if WHERE clause references tables not in subquery's FROM - const referencesExternal = node => { - if (!node) return false; + // Check if WHERE clause references tables not in subquery's FROM + const referencesExternal = node => { + if (!node) return false; - // Check Column nodes for tableid using instanceof - if (node instanceof yy.Column) { - if (node.tableid && !subqueryTables.has(node.tableid)) { - return true; + // Check Column nodes for tableid using instanceof + if (node instanceof yy.Column) { + if (node.tableid && !subqueryTables.has(node.tableid)) { + return true; + } } - } - // Recursively check own properties only (not inherited) - for (let key of Object.keys(node)) { - if (node[key] && typeof node[key] === 'object') { - if (referencesExternal(node[key])) return true; + // Recursively check own properties only (not inherited) + for (let key of Object.keys(node)) { + if (node[key] && typeof node[key] === 'object') { + if (referencesExternal(node[key])) return true; + } } - } - return false; + return false; + }; + + return referencesExternal(subquery.where) || referencesExternal(subquery.columns); }; - return referencesExternal(subquery.where) || referencesExternal(subquery.columns); - }; - - // Compile each subquery. Nested scalar subqueries such as - // SELECT (SELECT (SELECT 1 AS c) AS b) AS a share one statement-level - // queries list (see #1025), so every compiled subquery must be able to - // resolve queriesidx against the same queriesfn array at runtime. - query.queriesfn = this.queries.map(function (q) { - var nq = q.compile(query.database.databaseid); - nq.query.modifier = 'RECORDSET'; - - // Mark as correlated if it references external tables - nq.query.isCorrelated = isCorrelated(q, query); - - // If the nested query has its own queries, ensure they're compiled too - // (e.g. IN/ALL/ANY subqueries that captured a nested queries list) - if (q.queries && q.queries.length > 0 && !nq.query.queriesfn) { - nq.query.queriesfn = q.queries.map(function (qq) { - var nnq = qq.compile(query.database.databaseid); - nnq.query.modifier = 'RECORDSET'; - return nnq; - }); - } - return nq; - }); - - // Share compiled subquery functions down the tree. Without this, - // multi-level scalar subqueries fail with - // "Cannot read properties of undefined (reading '0')" when the - // inner select expression evaluates this.queriesfn[queriesidx-1]. - (function shareQueriesfn(queriesfn) { - if (!queriesfn) return; - queriesfn.forEach(function (nq) { - if (!nq.query.queriesfn) { - nq.query.queriesfn = queriesfn; - } else if (nq.query.queriesfn !== queriesfn) { - shareQueriesfn(nq.query.queriesfn); + // Compile each subquery. Nested scalar subqueries such as + // SELECT (SELECT (SELECT 1 AS c) AS b) AS a share one statement-level + // queries list (see #1025), so every compiled subquery must be able to + // resolve queriesidx against the same queriesfn array at runtime. + query.queriesfn = this.queries.map(function (q) { + var nq = q.compile(query.database.databaseid); + nq.query.modifier = 'RECORDSET'; + + // Mark as correlated if it references external tables + nq.query.isCorrelated = isCorrelated(q, query); + + // If the nested query has its own queries, ensure they're compiled too + // (e.g. IN/ALL/ANY subqueries that captured a nested queries list) + if (q.queries && q.queries.length > 0 && !nq.query.queriesfn) { + nq.query.queriesfn = q.queries.map(function (qq) { + var nnq = qq.compile(query.database.databaseid); + nnq.query.modifier = 'RECORDSET'; + return nnq; + }); } + return nq; }); - })(query.queriesfn); - } -}; -/** + // Share compiled subquery functions down the tree. Without this, + // multi-level scalar subqueries fail with + // "Cannot read properties of undefined (reading '0')" when the + // inner select expression evaluates this.queriesfn[queriesidx-1]. + (function shareQueriesfn(queriesfn) { + if (!queriesfn) return; + queriesfn.forEach(function (nq) { + if (!nq.query.queriesfn) { + nq.query.queriesfn = queriesfn; + } else if (nq.query.queriesfn !== queriesfn) { + shareQueriesfn(nq.query.queriesfn); + } + }); + })(query.queriesfn); + } + }; + + /** Modify res according modifier @function @param {object} query Query object @param res {object|number|string|boolean} res Data to be converted */ -function modify(query, res) { - // jshint ignore:line - - /* If source is a primitive value then return it */ - if ( - typeof res === 'undefined' || - typeof res === 'number' || - typeof res === 'string' || - typeof res === 'boolean' - ) { - return res; - } - - var modifier = query.modifier || alasql.options.modifier; - var columns = query.columns; - - // 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; - } + function modify(query, res) { + // jshint ignore:line + + /* If source is a primitive value then return it */ + if ( + typeof res === 'undefined' || + typeof res === 'number' || + typeof res === 'string' || + typeof res === 'boolean' + ) { + return res; } - // 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; - }); + var modifier = query.modifier || alasql.options.modifier; + var columns = query.columns; - // 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) { + // 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; } } - columns = Object.keys(allcol).map(function (columnid) { + // Create columns from data + var dataColumns = Object.keys(allcol).map(function (columnid) { return {columnid: columnid}; }); - } else { - // Cannot recognize columns - columns = []; - if (query && query.sources) { - query.sources.forEach(source => { - if (source && source.columns && Array.isArray(source.columns)) { - columns = columns.concat(source.columns); + + // 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 = {}; + for (var i = Math.min(res.length, alasql.options.columnlookup || 10) - 1; 0 <= i; i--) { + for (var key in res[i]) { + allcol[key] = true; } + } + + columns = Object.keys(allcol).map(function (columnid) { + return {columnid: columnid}; }); + } else { + // Cannot recognize columns + columns = []; + if (query && query.sources) { + query.sources.forEach(source => { + if (source && source.columns && Array.isArray(source.columns)) { + columns = columns.concat(source.columns); + } + }); + } } } - } - switch (modifier) { - case 'VALUE': - if (res.length === 0) return undefined; - const keyValue = columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; - return res[0][keyValue]; + switch (modifier) { + case 'VALUE': + if (res.length === 0) return undefined; + const keyValue = + columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; + return res[0][keyValue]; - case 'ROW': - if (res.length === 0) return undefined; - return Object.values(res[0]); + case 'ROW': + if (res.length === 0) return undefined; + return Object.values(res[0]); - case 'COLUMN': - if (res.length === 0) return []; + case 'COLUMN': + if (res.length === 0) return []; - let key; - if (columns && columns.length > 0) { - key = columns[0].columnid; - } else { - key = Object.keys(res[0])[0]; - } + let key; + if (columns && columns.length > 0) { + key = columns[0].columnid; + } else { + key = Object.keys(res[0])[0]; + } - let ar = []; - for (var i = 0, ilen = res.length; i < ilen; i++) { - ar.push(res[i][key]); - } + let ar = []; + for (var i = 0, ilen = res.length; i < ilen; i++) { + ar.push(res[i][key]); + } - // Apply DISTINCT if specified - if (query.distinct) { - ar = alasql.utils.distinctArray(ar); - } + // Apply DISTINCT if specified + if (query.distinct) { + ar = alasql.utils.distinctArray(ar); + } - return ar; - - case 'MATRIX': - if (res.length === 0) return undefined; - return res.map(row => columns.map(col => row[col.columnid])); - - case 'INDEX': - if (res.length === 0) return undefined; - const keyIndex = columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; - const valIndex = columns && columns.length > 1 ? columns[1].columnid : Object.keys(res[0])[1]; - return res.reduce((acc, row) => ({...acc, [row[keyIndex]]: row[valIndex]}), {}); - - case 'RECORDSET': - // Assuming alasql.Recordset is available in the scope - return new alasql.Recordset({columns: columns, data: res}); - - case 'TEXTSTRING': - if (res.length === 0) return undefined; - const keyTextString = - columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; - return res.map(row => row[keyTextString]).join('\n'); - - case 'ALASQL_DETAILS': - // Returns both data and column metadata in a structured format - // Useful for internal operations that need both data and column info in one call - return { - data: res, - columns: columns, - length: res.length, - }; + return ar; + + case 'MATRIX': + if (res.length === 0) return undefined; + return res.map(row => columns.map(col => row[col.columnid])); + + case 'INDEX': + if (res.length === 0) return undefined; + const keyIndex = + columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; + const valIndex = + columns && columns.length > 1 ? columns[1].columnid : Object.keys(res[0])[1]; + return res.reduce((acc, row) => ({...acc, [row[keyIndex]]: row[valIndex]}), {}); + + case 'RECORDSET': + // Assuming alasql.Recordset is available in the scope + return new alasql.Recordset({columns: columns, data: res}); + + case 'TEXTSTRING': + if (res.length === 0) return undefined; + const keyTextString = + columns && columns.length > 0 ? columns[0].columnid : Object.keys(res[0])[0]; + return res.map(row => row[keyTextString]).join('\n'); + + case 'ALASQL_DETAILS': + // Returns both data and column metadata in a structured format + // Useful for internal operations that need both data and column info in one call + return { + data: res, + columns: columns, + length: res.length, + }; + } + return res; } - return res; -} internal.modify = modify; } diff --git a/src/select/union.js b/src/select/union.js index 49c972df00..048706af3a 100755 --- a/src/select/union.js +++ b/src/select/union.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // UNION for Alasql.js // Date: 03.11.2014 @@ -8,19 +8,19 @@ export default function install(alasql) { // */ -// SELECT UNION statement + // SELECT UNION statement -yy.Union = class Union { - constructor(params) { - Object.assign(this, params); - } + yy.Union = class Union { + constructor(params) { + Object.assign(this, params); + } - toString() { - return 'UNION'; - } + toString() { + return 'UNION'; + } - compile(tableid) { - return null; - } -}; + compile(tableid) { + return null; + } + }; } diff --git a/src/select/where.js b/src/select/where.js index 531249fb1b..44c646e045 100755 --- a/src/select/where.js +++ b/src/select/where.js @@ -1,221 +1,221 @@ export default function install(alasql) { var yy = alasql.yy; -yy.Select.prototype.compileWhere = function (query) { - if (this.where) { - if (typeof this.where == 'function') { - return this.where; - } else { - var s = this.where.toJS('p', query.defaultTableid, query.defcols); - query.wherefns = s; - // console.log(s); - return new Function('p,params,alasql', 'var y;return ' + s); - } - } else - return function () { - return true; - }; -}; - -// Helper to set up join optimization on a source -function setupJoinOptimization(source, leftExpr, rightExpr) { - if (source.onleftfn) return; // Already optimized - source.onleftfns = leftExpr; - source.onrightfns = rightExpr; - source.onleftfn = new Function('p,params,alasql', 'var y;return ' + leftExpr); - source.onrightfn = new Function('p,params,alasql', 'var y;return ' + rightExpr); - source.optimization = 'ix'; -} + yy.Select.prototype.compileWhere = function (query) { + if (this.where) { + if (typeof this.where == 'function') { + return this.where; + } else { + var s = this.where.toJS('p', query.defaultTableid, query.defcols); + query.wherefns = s; + // console.log(s); + return new Function('p,params,alasql', 'var y;return ' + s); + } + } else + return function () { + return true; + }; + }; + + // Helper to set up join optimization on a source + function setupJoinOptimization(source, leftExpr, rightExpr) { + if (source.onleftfn) return; // Already optimized + source.onleftfns = leftExpr; + source.onrightfns = rightExpr; + source.onleftfn = new Function('p,params,alasql', 'var y;return ' + leftExpr); + source.onrightfn = new Function('p,params,alasql', 'var y;return ' + rightExpr); + source.optimization = 'ix'; + } -// Helper to add a single-table WHERE condition to a source -function addSourceWhereCondition(source, leftExpr, rightExpr) { - var condition = '(' + leftExpr + '==' + rightExpr + ')'; - source.srcwherefns = source.srcwherefns ? source.srcwherefns + '&&' + condition : condition; -} + // Helper to add a single-table WHERE condition to a source + function addSourceWhereCondition(source, leftExpr, rightExpr) { + var condition = '(' + leftExpr + '==' + rightExpr + ')'; + source.srcwherefns = source.srcwherefns ? source.srcwherefns + '&&' + condition : condition; + } -yy.Select.prototype.compileWhereJoins = function (query) { - // Optimize implicit joins by extracting join conditions from WHERE clause - // and setting up indexed lookups on sources - if (!this.where) return; - - // Only optimize if we have multiple sources from FROM clause (implicit joins) - if (query.sources.length <= 1) return; - - // Check if any sources already have optimization (from explicit JOINs) - // If so, skip optimization to avoid conflicts - var hasExplicitJoins = query.sources.some(function (source, idx) { - return idx > 0 && source.onleftfn; - }); - if (hasExplicitJoins) return; - - // Extract equality conditions from WHERE clause - var conditions = extractWhereConditions(this.where); - - // Build a map of source aliases to their indices - var aliasToIdx = {}; - query.sources.forEach(function (source, idx) { - aliasToIdx[source.alias] = idx; - }); - - // Process each condition to find join relationships - conditions.forEach(function (cond) { - if (cond.op !== '=') return; - if (cond.allsome) return; - - // Extract aliases directly from the AST nodes instead of parsing JS strings - var leftAliases = extractAliasesFromAst(cond.left); - var rightAliases = extractAliasesFromAst(cond.right); - - var ls = cond.left.toJS('p', query.defaultTableid, query.defcols); - var rs = cond.right.toJS('p', query.defaultTableid, query.defcols); - - // For a join condition, we need exactly one alias on each side - if (leftAliases.length === 1 && rightAliases.length === 1) { - var leftAlias = leftAliases[0]; - var rightAlias = rightAliases[0]; - - // Make sure both aliases exist in our sources - if (aliasToIdx[leftAlias] === undefined || aliasToIdx[rightAlias] === undefined) { - return; - } + yy.Select.prototype.compileWhereJoins = function (query) { + // Optimize implicit joins by extracting join conditions from WHERE clause + // and setting up indexed lookups on sources + if (!this.where) return; + + // Only optimize if we have multiple sources from FROM clause (implicit joins) + if (query.sources.length <= 1) return; + + // Check if any sources already have optimization (from explicit JOINs) + // If so, skip optimization to avoid conflicts + var hasExplicitJoins = query.sources.some(function (source, idx) { + return idx > 0 && source.onleftfn; + }); + if (hasExplicitJoins) return; + + // Extract equality conditions from WHERE clause + var conditions = extractWhereConditions(this.where); + + // Build a map of source aliases to their indices + var aliasToIdx = {}; + query.sources.forEach(function (source, idx) { + aliasToIdx[source.alias] = idx; + }); + + // Process each condition to find join relationships + conditions.forEach(function (cond) { + if (cond.op !== '=') return; + if (cond.allsome) return; + + // Extract aliases directly from the AST nodes instead of parsing JS strings + var leftAliases = extractAliasesFromAst(cond.left); + var rightAliases = extractAliasesFromAst(cond.right); + + var ls = cond.left.toJS('p', query.defaultTableid, query.defcols); + var rs = cond.right.toJS('p', query.defaultTableid, query.defcols); + + // For a join condition, we need exactly one alias on each side + if (leftAliases.length === 1 && rightAliases.length === 1) { + var leftAlias = leftAliases[0]; + var rightAlias = rightAliases[0]; + + // Make sure both aliases exist in our sources + if (aliasToIdx[leftAlias] === undefined || aliasToIdx[rightAlias] === undefined) { + return; + } - var leftIdx = aliasToIdx[leftAlias]; - var rightIdx = aliasToIdx[rightAlias]; + var leftIdx = aliasToIdx[leftAlias]; + var rightIdx = aliasToIdx[rightAlias]; - // The source that comes later in the FROM list should get the join optimization - // because doJoin processes sources in order - if (rightIdx > leftIdx) { - setupJoinOptimization(query.sources[rightIdx], ls, rs); - } else if (leftIdx > rightIdx) { - setupJoinOptimization(query.sources[leftIdx], rs, ls); - } - } else if (leftAliases.length === 1 && rightAliases.length === 0) { - // Single-table condition (e.g., t1.a = 5) - if (aliasToIdx[leftAliases[0]] !== undefined) { - addSourceWhereCondition(query.sources[aliasToIdx[leftAliases[0]]], ls, rs); - } - } else if (leftAliases.length === 0 && rightAliases.length === 1) { - // Single-table condition with alias on right (e.g., 5 = t1.a) - if (aliasToIdx[rightAliases[0]] !== undefined) { - addSourceWhereCondition(query.sources[aliasToIdx[rightAliases[0]]], ls, rs); + // The source that comes later in the FROM list should get the join optimization + // because doJoin processes sources in order + if (rightIdx > leftIdx) { + setupJoinOptimization(query.sources[rightIdx], ls, rs); + } else if (leftIdx > rightIdx) { + setupJoinOptimization(query.sources[leftIdx], rs, ls); + } + } else if (leftAliases.length === 1 && rightAliases.length === 0) { + // Single-table condition (e.g., t1.a = 5) + if (aliasToIdx[leftAliases[0]] !== undefined) { + addSourceWhereCondition(query.sources[aliasToIdx[leftAliases[0]]], ls, rs); + } + } else if (leftAliases.length === 0 && rightAliases.length === 1) { + // Single-table condition with alias on right (e.g., 5 = t1.a) + if (aliasToIdx[rightAliases[0]] !== undefined) { + addSourceWhereCondition(query.sources[aliasToIdx[rightAliases[0]]], ls, rs); + } } - } - }); + }); - // Compile the srcwherefn for sources that have single-table conditions - query.sources.forEach(function (source) { - if (source.srcwherefns) { - source.srcwherefn = new Function('p,params,alasql', 'var y;return ' + source.srcwherefns); - } - }); -}; + // Compile the srcwherefn for sources that have single-table conditions + query.sources.forEach(function (source) { + if (source.srcwherefns) { + source.srcwherefn = new Function('p,params,alasql', 'var y;return ' + source.srcwherefns); + } + }); + }; -// Helper function to extract all equality conditions from a WHERE clause -function extractWhereConditions(where) { - var conditions = []; + // Helper function to extract all equality conditions from a WHERE clause + function extractWhereConditions(where) { + var conditions = []; - function traverse(node) { - if (!node) return; + function traverse(node) { + if (!node) return; - // Handle Expression wrapper - get the inner expression - if (node.expression) { - traverse(node.expression); - return; - } + // Handle Expression wrapper - get the inner expression + if (node.expression) { + traverse(node.expression); + return; + } - if (!(node instanceof yy.Op)) return; + if (!(node instanceof yy.Op)) return; - if (node.op === 'AND') { - traverse(node.left); - traverse(node.right); - } else if (node.op === '=') { - conditions.push(node); + if (node.op === 'AND') { + traverse(node.left); + traverse(node.right); + } else if (node.op === '=') { + conditions.push(node); + } } + + traverse(where); + return conditions; } - traverse(where); - return conditions; -} + // Helper function to extract table aliases from an AST node + function extractAliasesFromAst(node) { + var aliases = []; -// Helper function to extract table aliases from an AST node -function extractAliasesFromAst(node) { - var aliases = []; + function traverse(n) { + if (!n) return; - function traverse(n) { - if (!n) return; + // If it's a Column node, extract the tableid + if (n instanceof yy.Column) { + if (n.tableid && aliases.indexOf(n.tableid) === -1) { + aliases.push(n.tableid); + } + return; + } - // If it's a Column node, extract the tableid - if (n instanceof yy.Column) { - if (n.tableid && aliases.indexOf(n.tableid) === -1) { - aliases.push(n.tableid); + // Recursively traverse child nodes for operators + if (n instanceof yy.Op) { + traverse(n.left); + traverse(n.right); } - return; } - // Recursively traverse child nodes for operators - if (n instanceof yy.Op) { - traverse(n.left); - traverse(n.right); - } + traverse(node); + return aliases; } - traverse(node); - return aliases; -} - -function optimizeWhereJoin(query, ast) { - if (!ast) return false; - if (!(ast instanceof yy.Op)) return; - if (ast.op != '=' && ast.op != 'AND') return; - if (ast.allsome) return; - - var s = ast.toJS('p', query.defaultTableid, query.defcols); - var fsrc = []; - query.sources.forEach(function (source, idx) { - // Optimization allowed only for tables only - if (source.tableid) { - // This is a good place to remove all unnecessary optimizations - if (s.indexOf("p['" + source.alias + "']") > -1) fsrc.push(source); - } - }); - if (fsrc.length == 0) { - return; - } else if (fsrc.length == 1) { - if ( - !(s.match(/p\[\'.*?\'\]/g) || []).every(function (s) { - return s == "p['" + fsrc[0].alias + "']"; - }) - ) { + function optimizeWhereJoin(query, ast) { + if (!ast) return false; + if (!(ast instanceof yy.Op)) return; + if (ast.op != '=' && ast.op != 'AND') return; + if (ast.allsome) return; + + var s = ast.toJS('p', query.defaultTableid, query.defcols); + var fsrc = []; + query.sources.forEach(function (source, idx) { + // Optimization allowed only for tables only + if (source.tableid) { + // This is a good place to remove all unnecessary optimizations + if (s.indexOf("p['" + source.alias + "']") > -1) fsrc.push(source); + } + }); + if (fsrc.length == 0) { return; - } - - var src = fsrc[0]; - src.srcwherefns = src.srcwherefns ? src.srcwherefns + '&&' + s : s; + } else if (fsrc.length == 1) { + if ( + !(s.match(/p\[\'.*?\'\]/g) || []).every(function (s) { + return s == "p['" + fsrc[0].alias + "']"; + }) + ) { + return; + } - if (ast instanceof yy.Op && ast.op == '=' && !ast.allsome) { - if (ast.left instanceof yy.Column) { - var ls = ast.left.toJS('p', query.defaultTableid, query.defcols); - var rs = ast.right.toJS('p', query.defaultTableid, query.defcols); - if (rs.indexOf("p['" + fsrc[0].alias + "']") == -1) { - fsrc[0].wxleftfns = ls; - fsrc[0].wxrightfns = rs; + var src = fsrc[0]; + src.srcwherefns = src.srcwherefns ? src.srcwherefns + '&&' + s : s; + + if (ast instanceof yy.Op && ast.op == '=' && !ast.allsome) { + if (ast.left instanceof yy.Column) { + var ls = ast.left.toJS('p', query.defaultTableid, query.defcols); + var rs = ast.right.toJS('p', query.defaultTableid, query.defcols); + if (rs.indexOf("p['" + fsrc[0].alias + "']") == -1) { + fsrc[0].wxleftfns = ls; + fsrc[0].wxrightfns = rs; + } } - } - if (ast.right instanceof yy.Column) { - var ls = ast.left.toJS('p', query.defaultTableid, query.defcols); - var rs = ast.right.toJS('p', query.defaultTableid, query.defcols); - if (ls.indexOf("p['" + fsrc[0].alias + "']") == -1) { - fsrc[0].wxleftfns = rs; - fsrc[0].wxrightfns = ls; + if (ast.right instanceof yy.Column) { + var ls = ast.left.toJS('p', query.defaultTableid, query.defcols); + var rs = ast.right.toJS('p', query.defaultTableid, query.defcols); + if (ls.indexOf("p['" + fsrc[0].alias + "']") == -1) { + fsrc[0].wxleftfns = rs; + fsrc[0].wxrightfns = ls; + } } } - } - ast.reduced = true; - return; - } else { - if (ast.op == 'AND') { - optimizeWhereJoin(query, ast.left); - optimizeWhereJoin(query, ast.right); + ast.reduced = true; + return; + } else { + if (ast.op == 'AND') { + optimizeWhereJoin(query, ast.left); + optimizeWhereJoin(query, ast.right); + } } } } -} diff --git a/src/select/window.js b/src/select/window.js index 97a03193ad..a36c8e61f2 100755 --- a/src/select/window.js +++ b/src/select/window.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // CROSS AND OUTER APPLY for Alasql.js // Date: 03.11.2014 @@ -8,22 +8,22 @@ export default function install(alasql) { // */ -yy.Over = class Over { - constructor(params) { - Object.assign(this, params); - } - - toString() { - let s = 'OVER ('; - if (this.partition) { - s += `PARTITION BY ${this.partition.toString()}`; - if (this.order) s += ' '; + yy.Over = class Over { + constructor(params) { + Object.assign(this, params); } - if (this.order) { - s += `ORDER BY ${this.order.toString()}`; + + toString() { + let s = 'OVER ('; + if (this.partition) { + s += `PARTITION BY ${this.partition.toString()}`; + if (this.order) s += ' '; + } + if (this.order) { + s += `ORDER BY ${this.order.toString()}`; + } + s += ')'; + return s; } - s += ')'; - return s; - } -}; + }; } diff --git a/src/statements/altertable.js b/src/statements/altertable.js index 506c8f25bd..f26ebe73b3 100755 --- a/src/statements/altertable.js +++ b/src/statements/altertable.js @@ -1,172 +1,174 @@ export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var yy = alasql.yy; -/* + /* // // ALTER TABLE for Alasql.js // Date: 03.11.2014 // (c) 2014, Andrey Gershun // */ -/* global alasql yy */ - -// ALTER TABLE table1 RENAME TO table2 -yy.AlterTable = function (params) { - return Object.assign(this, params); -}; -yy.AlterTable.prototype.toString = function () { - let s = 'ALTER TABLE ' + this.table.toString(); - if (this.renameto) s += ' RENAME TO ' + this.renameto; - return s; -}; - -yy.AlterTable.prototype.execute = function (databaseid, params, cb) { - var dbid = this.table.databaseid || databaseid; - let db = alasql.databases[dbid]; - schemaChanged(db); - - if (this.renameto) { - var oldtableid = this.table.tableid; - var newtableid = this.renameto; - var res = 1; - if (db.tables[newtableid]) { - throw new Error( - `Can not rename a table "${oldtableid}" to "${newtableid}" because the table with this name already exists` - ); - } else if (newtableid === oldtableid) { - throw new Error(`Can not rename a table "${oldtableid}" to itself`); - } else { - db.tables[newtableid] = db.tables[oldtableid]; - delete db.tables[oldtableid]; - res = 1; + /* global alasql yy */ + + // ALTER TABLE table1 RENAME TO table2 + yy.AlterTable = function (params) { + return Object.assign(this, params); + }; + yy.AlterTable.prototype.toString = function () { + let s = 'ALTER TABLE ' + this.table.toString(); + if (this.renameto) s += ' RENAME TO ' + this.renameto; + return s; + }; + + yy.AlterTable.prototype.execute = function (databaseid, params, cb) { + var dbid = this.table.databaseid || databaseid; + let db = alasql.databases[dbid]; + schemaChanged(db); + + if (this.renameto) { + var oldtableid = this.table.tableid; + var newtableid = this.renameto; + var res = 1; + if (db.tables[newtableid]) { + throw new Error( + `Can not rename a table "${oldtableid}" to "${newtableid}" because the table with this name already exists` + ); + } else if (newtableid === oldtableid) { + throw new Error(`Can not rename a table "${oldtableid}" to itself`); + } else { + db.tables[newtableid] = db.tables[oldtableid]; + delete db.tables[oldtableid]; + res = 1; + } + if (cb) cb(res); + return res; + } + + // A table in a storage engine is loaded before the change and saved after it, as INSERT, + // UPDATE and DELETE do. Without this the change is lost on the next statement (#1107) + var tableid = this.table.tableid; + var engine = alasql.options.autocommit && db.engineid && alasql.engines[db.engineid]; + if (engine && engine.loadTableData) { + engine.loadTableData(dbid, tableid); + } + var res = this.alterColumn(db.tables[tableid], tableid); + if (engine && engine.saveTableData) { + engine.saveTableData(dbid, tableid); } - if (cb) cb(res); - return res; - } - - // A table in a storage engine is loaded before the change and saved after it, as INSERT, - // UPDATE and DELETE do. Without this the change is lost on the next statement (#1107) - var tableid = this.table.tableid; - var engine = alasql.options.autocommit && db.engineid && alasql.engines[db.engineid]; - if (engine && engine.loadTableData) { - engine.loadTableData(dbid, tableid); - } - var res = this.alterColumn(db.tables[tableid], tableid); - if (engine && engine.saveTableData) { - engine.saveTableData(dbid, tableid); - } - return cb ? cb(res) : res; -}; - -/** + return cb ? cb(res) : res; + }; + + /** Add, modify, rename or drop a column of a table in memory @return {number} 1, or the number of rows for RENAME COLUMN and DROP COLUMN */ -yy.AlterTable.prototype.alterColumn = function (table, tableid) { - if (this.addcolumn) { - var columnid = this.addcolumn.columnid; - if (table.xcolumns[columnid]) { - throw new Error( - `Cannot add column "${columnid}" because it already exists in table "${tableid}"` - ); - } + yy.AlterTable.prototype.alterColumn = function (table, tableid) { + if (this.addcolumn) { + var columnid = this.addcolumn.columnid; + if (table.xcolumns[columnid]) { + throw new Error( + `Cannot add column "${columnid}" because it already exists in table "${tableid}"` + ); + } - var col = { - columnid: columnid, - dbtypeid: this.addcolumn.dbtypeid, - dbsize: this.addcolumn.dbsize, - dbprecision: this.addcolumn.dbprecision, - dbenum: this.addcolumn.dbenum, - defaultfns: null, // TODO defaultfns!!! - }; + var col = { + columnid: columnid, + dbtypeid: this.addcolumn.dbtypeid, + dbsize: this.addcolumn.dbsize, + dbprecision: this.addcolumn.dbprecision, + dbenum: this.addcolumn.dbenum, + defaultfns: null, // TODO defaultfns!!! + }; - var defaultfn = function () {}; + var defaultfn = function () {}; - table.columns.push(col); - table.xcolumns[columnid] = col; + table.columns.push(col); + table.xcolumns[columnid] = col; - for (let i = 0, ilen = table.data.length; i < ilen; i++) { - table.data[i][columnid] = defaultfn(); + for (let i = 0, ilen = table.data.length; i < ilen; i++) { + table.data[i][columnid] = defaultfn(); + } + + return 1; } - return 1; - } + if (this.modifycolumn) { + var columnid = this.modifycolumn.columnid; + + if (!table.xcolumns[columnid]) { + throw new Error( + `Cannot modify column "${columnid}" because it was not found in table "${tableid}"` + ); + } - if (this.modifycolumn) { - var columnid = this.modifycolumn.columnid; + var col = table.xcolumns[columnid]; + col.dbtypeid = this.modifycolumn.dbtypeid; + col.dbsize = this.modifycolumn.dbsize; + col.dbprecision = this.modifycolumn.dbprecision; + col.dbenum = this.modifycolumn.dbenum; - if (!table.xcolumns[columnid]) { - throw new Error( - `Cannot modify column "${columnid}" because it was not found in table "${tableid}"` - ); + return 1; } - var col = table.xcolumns[columnid]; - col.dbtypeid = this.modifycolumn.dbtypeid; - col.dbsize = this.modifycolumn.dbsize; - col.dbprecision = this.modifycolumn.dbprecision; - col.dbenum = this.modifycolumn.dbenum; + if (this.renamecolumn) { + var columnid = this.renamecolumn; + var tocolumnid = this.to; - return 1; - } + if (!table.xcolumns[columnid]) { + throw new Error('Column "' + columnid + '" is not found in the table "' + tableid + '"'); + } + if (table.xcolumns[tocolumnid]) { + throw new Error( + 'Column "' + tocolumnid + '" already exists in the table "' + tableid + '"' + ); + } - if (this.renamecolumn) { - var columnid = this.renamecolumn; - var tocolumnid = this.to; + if (columnid != tocolumnid) { + for (var j = 0; j < table.columns.length; j++) { + if (table.columns[j].columnid == columnid) { + table.columns[j].columnid = tocolumnid; + } + } - if (!table.xcolumns[columnid]) { - throw new Error('Column "' + columnid + '" is not found in the table "' + tableid + '"'); - } - if (table.xcolumns[tocolumnid]) { - throw new Error('Column "' + tocolumnid + '" already exists in the table "' + tableid + '"'); + table.xcolumns[tocolumnid] = table.xcolumns[columnid]; + delete table.xcolumns[columnid]; + + for (var i = 0, ilen = table.data.length; i < ilen; i++) { + table.data[i][tocolumnid] = table.data[i][columnid]; + delete table.data[i][columnid]; + } + return table.data.length; + } + return 0; } - if (columnid != tocolumnid) { + if (this.dropcolumn) { + var columnid = this.dropcolumn; + + var found = false; for (var j = 0; j < table.columns.length; j++) { if (table.columns[j].columnid == columnid) { - table.columns[j].columnid = tocolumnid; + found = true; + table.columns.splice(j, 1); + break; } } - table.xcolumns[tocolumnid] = table.xcolumns[columnid]; + if (!found) { + throw new Error( + `Cannot drop column "${columnid}" because it was not found in the table ${tableid}"` + ); + } + delete table.xcolumns[columnid]; - for (var i = 0, ilen = table.data.length; i < ilen; i++) { - table.data[i][tocolumnid] = table.data[i][columnid]; + for (i = 0, ilen = table.data.length; i < ilen; i++) { delete table.data[i][columnid]; } - return table.data.length; - } - return 0; - } - - if (this.dropcolumn) { - var columnid = this.dropcolumn; - - var found = false; - for (var j = 0; j < table.columns.length; j++) { - if (table.columns[j].columnid == columnid) { - found = true; - table.columns.splice(j, 1); - break; - } - } - - if (!found) { - throw new Error( - `Cannot drop column "${columnid}" because it was not found in the table ${tableid}"` - ); - } - delete table.xcolumns[columnid]; - - for (i = 0, ilen = table.data.length; i < ilen; i++) { - delete table.data[i][columnid]; + return table.data.length; } - return table.data.length; - } - - throw Error('Unknown ALTER TABLE method'); -}; + throw Error('Unknown ALTER TABLE method'); + }; } diff --git a/src/statements/assert.js b/src/statements/assert.js index d2c45fbc8d..a88d81c9bf 100755 --- a/src/statements/assert.js +++ b/src/statements/assert.js @@ -2,7 +2,7 @@ import {deepEqual} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // HELP for Alasql.js // Date: 03.11.2014 @@ -10,26 +10,26 @@ export default function install(alasql) { // */ -yy.Assert = function (params) { - return Object.assign(this, params); -}; -yy.Source.prototype.toString = function () { - var s = 'ASSERT'; - if (this.value) s += ' ' + JSON.stringify(this.value); - return s; -}; + yy.Assert = function (params) { + return Object.assign(this, params); + }; + yy.Source.prototype.toString = function () { + var s = 'ASSERT'; + if (this.value) s += ' ' + JSON.stringify(this.value); + return s; + }; -// SOURCE FILE -yy.Assert.prototype.execute = function (databaseid) { - if (!deepEqual(alasql.res, this.value)) { - throw new Error( - (this.message || 'Assert wrong') + - ': ' + - JSON.stringify(alasql.res) + - ' == ' + - JSON.stringify(this.value) - ); - } - return 1; -}; + // SOURCE FILE + yy.Assert.prototype.execute = function (databaseid) { + if (!deepEqual(alasql.res, this.value)) { + throw new Error( + (this.message || 'Assert wrong') + + ': ' + + JSON.stringify(alasql.res) + + ' == ' + + JSON.stringify(this.value) + ); + } + return 1; + }; } diff --git a/src/statements/commit.js b/src/statements/commit.js index 9cedb2a743..f4865d8f91 100755 --- a/src/statements/commit.js +++ b/src/statements/commit.js @@ -1,63 +1,63 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // Commit for Alasql.js // Date: 01.12.2014 // (c) 2014, Andrey Gershun // */ -yy.BeginTransaction = function (params) { - return Object.assign(this, params); -}; -yy.BeginTransaction.prototype.toString = function () { - return 'BEGIN TRANSACTION'; -}; + yy.BeginTransaction = function (params) { + return Object.assign(this, params); + }; + yy.BeginTransaction.prototype.toString = function () { + return 'BEGIN TRANSACTION'; + }; -yy.BeginTransaction.prototype.execute = function (databaseid, params, cb) { - var res = 1; - if (alasql.databases[databaseid].engineid) { - return alasql.engines[alasql.databases[alasql.useid].engineid].begin(databaseid, cb); - } else { - // alasql commit!!! - } - if (cb) res = cb(res); - return res; -}; + yy.BeginTransaction.prototype.execute = function (databaseid, params, cb) { + var res = 1; + if (alasql.databases[databaseid].engineid) { + return alasql.engines[alasql.databases[alasql.useid].engineid].begin(databaseid, cb); + } else { + // alasql commit!!! + } + if (cb) res = cb(res); + return res; + }; -yy.CommitTransaction = function (params) { - return Object.assign(this, params); -}; -yy.CommitTransaction.prototype.toString = function () { - return 'COMMIT TRANSACTION'; -}; + yy.CommitTransaction = function (params) { + return Object.assign(this, params); + }; + yy.CommitTransaction.prototype.toString = function () { + return 'COMMIT TRANSACTION'; + }; -yy.CommitTransaction.prototype.execute = function (databaseid, params, cb) { - var res = 1; - if (alasql.databases[databaseid].engineid) { - return alasql.engines[alasql.databases[alasql.useid].engineid].commit(databaseid, cb); - } else { - // alasql commit!!! - } - if (cb) res = cb(res); - return res; -}; + yy.CommitTransaction.prototype.execute = function (databaseid, params, cb) { + var res = 1; + if (alasql.databases[databaseid].engineid) { + return alasql.engines[alasql.databases[alasql.useid].engineid].commit(databaseid, cb); + } else { + // alasql commit!!! + } + if (cb) res = cb(res); + return res; + }; -yy.RollbackTransaction = function (params) { - return Object.assign(this, params); -}; -yy.RollbackTransaction.prototype.toString = function () { - return 'ROLLBACK TRANSACTION'; -}; + yy.RollbackTransaction = function (params) { + return Object.assign(this, params); + }; + yy.RollbackTransaction.prototype.toString = function () { + return 'ROLLBACK TRANSACTION'; + }; -yy.RollbackTransaction.prototype.execute = function (databaseid, params, cb) { - var res = 1; - if (alasql.databases[databaseid].engineid) { - return alasql.engines[alasql.databases[databaseid].engineid].rollback(databaseid, cb); - } else { - // alasql commit!!! - } - if (cb) res = cb(res); - return res; -}; + yy.RollbackTransaction.prototype.execute = function (databaseid, params, cb) { + var res = 1; + if (alasql.databases[databaseid].engineid) { + return alasql.engines[alasql.databases[databaseid].engineid].rollback(databaseid, cb); + } else { + // alasql commit!!! + } + if (cb) res = cb(res); + return res; + }; } diff --git a/src/statements/createindex.js b/src/statements/createindex.js index a3d7d58ef2..a28e3215f9 100755 --- a/src/statements/createindex.js +++ b/src/statements/createindex.js @@ -4,7 +4,7 @@ export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var yy = alasql.yy; var indexColumnToJS = internal.indexColumnToJS; -/* + /* // // CREATE TABLE for Alasql.js // Date: 03.11.2014 @@ -12,105 +12,105 @@ export default function install(alasql, internal) { // */ -yy.CreateIndex = function (params) { - return Object.assign(this, params); -}; -yy.CreateIndex.prototype.toString = function () { - var s = 'CREATE'; - if (this.unique) s += ' UNIQUE'; - s += ' INDEX ' + this.indexid + ' ON ' + this.table.toString(); - s += '(' + this.columns.toString() + ')'; - return s; -}; + yy.CreateIndex = function (params) { + return Object.assign(this, params); + }; + yy.CreateIndex.prototype.toString = function () { + var s = 'CREATE'; + if (this.unique) s += ' UNIQUE'; + s += ' INDEX ' + this.indexid + ' ON ' + this.table.toString(); + s += '(' + this.columns.toString() + ')'; + return s; + }; -// CREATE TABLE -yy.CreateIndex.prototype.execute = function (databaseid, params, cb) { - // var self = this; - var db = alasql.databases[this.table.databaseid || databaseid]; - var tableid = this.table.tableid; - var table = db.tables[tableid]; - var indexid = this.indexid; - db.indices[indexid] = tableid; - // The schema changed, so statements in the statement cache must be compiled again - schemaChanged(db); + // CREATE TABLE + yy.CreateIndex.prototype.execute = function (databaseid, params, cb) { + // var self = this; + var db = alasql.databases[this.table.databaseid || databaseid]; + var tableid = this.table.tableid; + var table = db.tables[tableid]; + var indexid = this.indexid; + db.indices[indexid] = tableid; + // The schema changed, so statements in the statement cache must be compiled again + schemaChanged(db); - var rightfns = this.columns - .map(function (expr) { - return indexColumnToJS(expr.expression, table); - }) - .join("+'`'+"); + var rightfns = this.columns + .map(function (expr) { + return indexColumnToJS(expr.expression, table); + }) + .join("+'`'+"); - var rightfn = new Function('r,params,alasql', 'return ' + rightfns); + var rightfn = new Function('r,params,alasql', 'return ' + rightfns); - if (this.unique) { - // Create a unique constraint similar to table.uk - var uniqueConstraint = {}; - table.uk = table.uk || []; - table.uk.push(uniqueConstraint); - uniqueConstraint.columns = this.columns; - uniqueConstraint.onrightfns = rightfns; - uniqueConstraint.onrightfn = rightfn; - uniqueConstraint.hh = hash(rightfns); + if (this.unique) { + // Create a unique constraint similar to table.uk + var uniqueConstraint = {}; + table.uk = table.uk || []; + table.uk.push(uniqueConstraint); + uniqueConstraint.columns = this.columns; + uniqueConstraint.onrightfns = rightfns; + uniqueConstraint.onrightfn = rightfn; + uniqueConstraint.hh = hash(rightfns); - // Store in uniqdefs for reference - table.uniqdefs[indexid] = { - rightfns: rightfns, - hh: uniqueConstraint.hh, - }; + // Store in uniqdefs for reference + table.uniqdefs[indexid] = { + rightfns: rightfns, + hh: uniqueConstraint.hh, + }; - // Initialize the unique index - table.uniqs[uniqueConstraint.hh] = {}; + // Initialize the unique index + table.uniqs[uniqueConstraint.hh] = {}; - // Populate existing data and check for duplicates - if (table.data.length > 0) { - for (var i = 0, ilen = table.data.length; i < ilen; i++) { - var addr = rightfn(table.data[i], params, alasql); - if (typeof table.uniqs[uniqueConstraint.hh][addr] !== 'undefined') { - throw new Error('Cannot create unique index with duplicate values'); + // Populate existing data and check for duplicates + if (table.data.length > 0) { + for (var i = 0, ilen = table.data.length; i < ilen; i++) { + var addr = rightfn(table.data[i], params, alasql); + if (typeof table.uniqs[uniqueConstraint.hh][addr] !== 'undefined') { + throw new Error('Cannot create unique index with duplicate values'); + } + table.uniqs[uniqueConstraint.hh][addr] = table.data[i]; } - table.uniqs[uniqueConstraint.hh][addr] = table.data[i]; } - } - } else { - var hh = hash(rightfns); - table.inddefs[indexid] = {rightfns: rightfns, hh: hh}; - table.indices[hh] = {}; + } else { + var hh = hash(rightfns); + table.inddefs[indexid] = {rightfns: rightfns, hh: hh}; + table.indices[hh] = {}; - var ix = (table.indices[hh] = {}); - if (table.data.length > 0) { - for (var i = 0, ilen = table.data.length; i < ilen; i++) { - var addr = rightfn(table.data[i], params, alasql); - if (!ix[addr]) { - ix[addr] = []; + var ix = (table.indices[hh] = {}); + if (table.data.length > 0) { + for (var i = 0, ilen = table.data.length; i < ilen; i++) { + var addr = rightfn(table.data[i], params, alasql); + if (!ix[addr]) { + ix[addr] = []; + } + ix[addr].push(table.data[i]); } - ix[addr].push(table.data[i]); } } - } - var res = 1; - if (cb) res = cb(res); - return res; -}; + var res = 1; + if (cb) res = cb(res); + return res; + }; -yy.Reindex = function (params) { - return Object.assign(this, params); -}; -yy.Reindex.prototype.toString = function () { - var s = 'REINDEX ' + this.indexid; - return s; -}; + yy.Reindex = function (params) { + return Object.assign(this, params); + }; + yy.Reindex.prototype.toString = function () { + var s = 'REINDEX ' + this.indexid; + return s; + }; -// CREATE TABLE -yy.Reindex.prototype.execute = function (databaseid, params, cb) { - // var self = this; - var db = alasql.databases[databaseid]; - var indexid = this.indexid; - // console.log(db.indices); - var tableid = db.indices[indexid]; - var table = db.tables[tableid]; - table.indexColumns(); - var res = 1; - if (cb) res = cb(res); - return res; -}; + // CREATE TABLE + yy.Reindex.prototype.execute = function (databaseid, params, cb) { + // var self = this; + var db = alasql.databases[databaseid]; + var indexid = this.indexid; + // console.log(db.indices); + var tableid = db.indices[indexid]; + var table = db.tables[tableid]; + table.indexColumns(); + var res = 1; + if (cb) res = cb(res); + return res; + }; } diff --git a/src/statements/createtable.js b/src/statements/createtable.js index a717e14b07..a355c83f56 100755 --- a/src/statements/createtable.js +++ b/src/statements/createtable.js @@ -3,7 +3,7 @@ import {cloneDeep, hash} from '../utils/helpers.js'; export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var yy = alasql.yy; -/* + /* // // CREATE TABLE for Alasql.js // Date: 03.11.2014 @@ -11,424 +11,429 @@ export default function install(alasql, internal) { // */ -/* global alasql, yy, hash */ + /* global alasql, yy, hash */ -yy.ColumnDef = function (params) { - return Object.assign(this, params); -}; -yy.ColumnDef.prototype.toString = function () { - let s = this.columnid; - if (this.dbtypeid) { - s += ' ' + this.dbtypeid; - } + yy.ColumnDef = function (params) { + return Object.assign(this, params); + }; + yy.ColumnDef.prototype.toString = function () { + let s = this.columnid; + if (this.dbtypeid) { + s += ' ' + this.dbtypeid; + } - if (this.dbsize) { - s += '(' + this.dbsize; - if (this.dbprecision) { - s += ',' + this.dbprecision; + if (this.dbsize) { + s += '(' + this.dbsize; + if (this.dbprecision) { + s += ',' + this.dbprecision; + } + s += ')'; } - s += ')'; - } - if (this.primarykey) { - s += ' PRIMARY KEY'; - } + if (this.primarykey) { + s += ' PRIMARY KEY'; + } - if (this.notnull) { - s += ' NOT NULL'; - } + if (this.notnull) { + s += ' NOT NULL'; + } - return s; -}; + return s; + }; -yy.CreateTable = function (params) { - return Object.assign(this, params); -}; -yy.CreateTable.prototype.toString = function () { - let s = `CREATE${this.temporary ? ' TEMPORARY' : ''}${this.view ? ' VIEW' : ` ${this.class ? 'CLASS' : 'TABLE'}`}${this.ifnotexists ? ' IF NOT EXISTS' : ''} ${this.table.toString()}`; + yy.CreateTable = function (params) { + return Object.assign(this, params); + }; + yy.CreateTable.prototype.toString = function () { + let s = `CREATE${this.temporary ? ' TEMPORARY' : ''}${this.view ? ' VIEW' : ` ${this.class ? 'CLASS' : 'TABLE'}`}${this.ifnotexists ? ' IF NOT EXISTS' : ''} ${this.table.toString()}`; - if (this.viewcolumns) { - s += `(${this.viewcolumns.map(vcol => vcol.toString()).join(',')})`; - } + if (this.viewcolumns) { + s += `(${this.viewcolumns.map(vcol => vcol.toString()).join(',')})`; + } - if (this.as) { - s += ` AS ${this.as}`; - } else { - s += ` (${this.columns.map(col => col.toString()).join(',')})`; - } + if (this.as) { + s += ` AS ${this.as}`; + } else { + s += ` (${this.columns.map(col => col.toString()).join(',')})`; + } - if (this.view && this.select) { - s += ` AS ${this.select.toString()}`; - } + if (this.view && this.select) { + s += ` AS ${this.select.toString()}`; + } - return s; -}; + return s; + }; -/** + /** JS for a column of an index, or with a prefix length for its first `prefix` characters, as in MySQL's a(N) (#406) */ -function columnPrefixToJS(columnid, prefix) { - var col = 'r[' + JSON.stringify(columnid) + ']'; - if (prefix === undefined) { - return col; + function columnPrefixToJS(columnid, prefix) { + var col = 'r[' + JSON.stringify(columnid) + ']'; + if (prefix === undefined) { + return col; + } + return '(' + col + '==null?' + col + ':String(' + col + ').substr(0,' + prefix + '))'; } - return '(' + col + '==null?' + col + ':String(' + col + ').substr(0,' + prefix + '))'; -} -/** + /** Inside CREATE TABLE and CREATE INDEX a double-quoted string that names a column of the table is that column, as SQLite reads CHECK ("a" >= 0) and UNIQUE ("a", "b"). Elsewhere it stays a string (#989) */ -function quotedColumns(expr, columnids) { - if (expr instanceof yy.StringValue) { - return expr.doubleQuoted && columnids.indexOf(expr.value) > -1 - ? new yy.Column({columnid: expr.value}) - : expr; - } - for (var key in expr) { - if ( - Object.prototype.hasOwnProperty.call(expr, key) && - expr[key] && - typeof expr[key] === 'object' - ) { - expr[key] = quotedColumns(expr[key], columnids); + function quotedColumns(expr, columnids) { + if (expr instanceof yy.StringValue) { + return expr.doubleQuoted && columnids.indexOf(expr.value) > -1 + ? new yy.Column({columnid: expr.value}) + : expr; + } + for (var key in expr) { + if ( + Object.prototype.hasOwnProperty.call(expr, key) && + expr[key] && + typeof expr[key] === 'object' + ) { + expr[key] = quotedColumns(expr[key], columnids); + } } + return expr; } - return expr; -} -/** + /** JS for one column of a UNIQUE constraint or CREATE INDEX. These take expressions, so a(N) is parsed as a function call. When a is a column of the table, it is a prefix (#406) */ -function indexColumnToJS(expr, table) { - expr = quotedColumns(expr, Object.keys(table.xcolumns || {})); - if ( - expr instanceof yy.FuncValue && - table.xcolumns && - table.xcolumns[expr.funcid] && - expr.args && - expr.args.length === 1 && - expr.args[0] instanceof yy.NumValue - ) { - return columnPrefixToJS(expr.funcid, expr.args[0].value); + function indexColumnToJS(expr, table) { + expr = quotedColumns(expr, Object.keys(table.xcolumns || {})); + if ( + expr instanceof yy.FuncValue && + table.xcolumns && + table.xcolumns[expr.funcid] && + expr.args && + expr.args.length === 1 && + expr.args[0] instanceof yy.NumValue + ) { + return columnPrefixToJS(expr.funcid, expr.args[0].value); + } + return expr.toJS('r', ''); } - return expr.toJS('r', ''); -} -// CREATE TABLE -//yy.CreateTable.prototype.compile = returnUndefined; -yy.CreateTable.prototype.execute = function (databaseid, params, cb) { - // var self = this; - var db = alasql.databases[this.table.databaseid || databaseid]; + // CREATE TABLE + //yy.CreateTable.prototype.compile = returnUndefined; + yy.CreateTable.prototype.execute = function (databaseid, params, cb) { + // var self = this; + var db = alasql.databases[this.table.databaseid || databaseid]; - var tableid = this.table.tableid; - if (!tableid) { - throw new Error('Table name is not defined'); - } + var tableid = this.table.tableid; + if (!tableid) { + throw new Error('Table name is not defined'); + } - var columns = this.columns; - var constraints = this.constraints || []; - var columnids = (columns || []).map(function (col) { - return col.columnid; - }); + var columns = this.columns; + var constraints = this.constraints || []; + var columnids = (columns || []).map(function (col) { + return col.columnid; + }); - // IF NOT EXISTS - if (this.ifnotexists && db.tables[tableid]) { - return cb ? cb(0) : 0; - } + // IF NOT EXISTS + if (this.ifnotexists && db.tables[tableid]) { + return cb ? cb(0) : 0; + } - if (db.tables[tableid]) { - throw new Error( - "Can not create table '" + - tableid + - "', because it already exists in the database '" + - db.databaseid + - "'" - ); - } + if (db.tables[tableid]) { + throw new Error( + "Can not create table '" + + tableid + + "', because it already exists in the database '" + + db.databaseid + + "'" + ); + } - var table = (db.tables[tableid] = new alasql.Table()); // TODO Can use special object? - // The schema changed, so statements in the statement cache must be compiled again - schemaChanged(db); - // If this is a class - if (this.class) { - table.isclass = true; - } + var table = (db.tables[tableid] = new alasql.Table()); // TODO Can use special object? + // The schema changed, so statements in the statement cache must be compiled again + schemaChanged(db); + // If this is a class + if (this.class) { + table.isclass = true; + } - var ss = []; // DEFAULT function components - var uss = []; // ON UPDATE function components - if (columns) { - columns.forEach(function (col) { - var dbtypeid = col.dbtypeid; - if (!alasql.fn[dbtypeid]) { - dbtypeid = dbtypeid.toUpperCase(); - } + var ss = []; // DEFAULT function components + var uss = []; // ON UPDATE function components + if (columns) { + columns.forEach(function (col) { + var dbtypeid = col.dbtypeid; + if (!alasql.fn[dbtypeid]) { + dbtypeid = dbtypeid.toUpperCase(); + } - // Process SERIAL data type like Postgress - if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].indexOf(dbtypeid) > -1) { - col.identity = {value: 1, step: 1}; - } + // Process SERIAL data type like Postgress + if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].indexOf(dbtypeid) > -1) { + col.identity = {value: 1, step: 1}; + } - var newcol = { - columnid: col.columnid, - dbtypeid: dbtypeid, - dbsize: col.dbsize, // Fixed issue #150 - dbprecision: col.dbprecision, // Fixed issue #150 - notnull: col.notnull, - identity: col.identity, - }; - if (col.identity) { - table.identities[col.columnid] = { - value: +col.identity.value, - step: +col.identity.step, + var newcol = { + columnid: col.columnid, + dbtypeid: dbtypeid, + dbsize: col.dbsize, // Fixed issue #150 + dbprecision: col.dbprecision, // Fixed issue #150 + notnull: col.notnull, + identity: col.identity, }; - } - if (col.check) { - table.checks.push({ - id: col.check.constrantid, - fn: new Function( - 'r,params,alasql', - 'var y;return ' + quotedColumns(col.check.expression, columnids).toJS('r', '') - ), - }); - } + if (col.identity) { + table.identities[col.columnid] = { + value: +col.identity.value, + step: +col.identity.step, + }; + } + if (col.check) { + table.checks.push({ + id: col.check.constrantid, + fn: new Function( + 'r,params,alasql', + 'var y;return ' + quotedColumns(col.check.expression, columnids).toJS('r', '') + ), + }); + } - if (col.default) { - ss.push(JSON.stringify('' + col.columnid) + ':' + col.default.toJS('r', '')); - } + if (col.default) { + ss.push(JSON.stringify('' + col.columnid) + ':' + col.default.toJS('r', '')); + } + + // Check for primary key + if (col.primarykey) { + var pk = (table.pk = {}); + pk.columns = [col.columnid]; + pk.onrightfns = `r[${JSON.stringify(col.columnid)}]`; + pk.onrightfn = new Function('r', 'var y;return ' + pk.onrightfns); + pk.hh = hash(pk.onrightfns); + table.uniqs[pk.hh] = {}; + } + + // UNIQUE clause + if (col.unique) { + var uk = {}; + table.uk = table.uk || []; + table.uk.push(uk); + uk.columns = [col.columnid]; + uk.onrightfns = `r[${JSON.stringify(col.columnid)}]`; + uk.onrightfn = new Function('r', 'var y;return ' + uk.onrightfns); + uk.hh = hash(uk.onrightfns); + table.uniqs[uk.hh] = {}; + } + + // UNIQUE clause + if (col.foreignkey) { + var fk = col.foreignkey.table; + // The check uses the primary key of the referenced table, as in 4.x; a named + // column is used only when that table has no primary key in memory (#1846) + var fkcolumnid = col.foreignkey.columnid; + var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; + if (fktable.pk && fktable.pk.columns && fktable.pk.columns.length > 0) { + fkcolumnid = fktable.pk.columns[0]; + } else if (typeof fkcolumnid === 'undefined') { + throw new Error('FOREIGN KEY allowed only to tables with PRIMARY KEYs'); + } + // Store foreignkey property in column for later access + newcol.foreignkey = { + tableid: fk.tableid, + columnid: fkcolumnid, + }; + var fkfn = function (r) { + var rr = {}; + var val = r[col.columnid]; + // Only check foreign key if value is not null, undefined, or NaN + if (val != null && !(typeof val === 'number' && isNaN(val))) { + rr[fkcolumnid] = val; + // Get the table when checking, as storage engines can replace the table object + var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; + var addr = fktable.pk.onrightfn(rr); + if (!fktable.uniqs[fktable.pk.hh][addr]) { + throw new Error( + 'Foreign key "' + val + '" not found in table "' + fk.tableid + '"' + ); + } + } + return true; + }; + table.checks.push({fn: fkfn, fk: true}); + } + + if (col.onupdate) { + uss.push(`r[${JSON.stringify(col.columnid)}]=` + col.onupdate.toJS('r', '')); + } + + table.columns.push(newcol); + table.xcolumns[newcol.columnid] = newcol; + }); + } + table.defaultfns = ss.join(','); + table.onupdatefns = uss.join(';'); + + constraints.forEach(function (con) { + var checkfn; - // Check for primary key - if (col.primarykey) { + if (con.type === 'PRIMARY KEY') { + if (table.pk) { + throw new Error('Primary key already exists'); + } var pk = (table.pk = {}); - pk.columns = [col.columnid]; - pk.onrightfns = `r[${JSON.stringify(col.columnid)}]`; + pk.columns = con.columns; + pk.onrightfns = pk.columns + .map(function (columnid, i) { + return columnPrefixToJS(columnid, con.prefixes && con.prefixes[i]); + }) + .join("+'`'+"); pk.onrightfn = new Function('r', 'var y;return ' + pk.onrightfns); pk.hh = hash(pk.onrightfns); table.uniqs[pk.hh] = {}; - } - - // UNIQUE clause - if (col.unique) { + // Mark columns with primarykey property + pk.columns.forEach(function (columnid) { + if (table.xcolumns[columnid]) { + table.xcolumns[columnid].primarykey = true; + } + }); + } else if (con.type === 'CHECK') { + checkfn = new Function( + 'r,params,alasql', + 'var y;return ' + quotedColumns(con.expression, columnids).toJS('r', '') + ); + } else if (con.type === 'UNIQUE') { var uk = {}; table.uk = table.uk || []; table.uk.push(uk); - uk.columns = [col.columnid]; - uk.onrightfns = `r[${JSON.stringify(col.columnid)}]`; + uk.columns = con.columns; + uk.onrightfns = uk.columns + .map(function (expr) { + return indexColumnToJS(expr.expression, table); + }) + .join("+'`'+"); uk.onrightfn = new Function('r', 'var y;return ' + uk.onrightfns); uk.hh = hash(uk.onrightfns); table.uniqs[uk.hh] = {}; - } - - // UNIQUE clause - if (col.foreignkey) { - var fk = col.foreignkey.table; + } else if (con.type === 'INDEX') { + // Register the index the way CREATE INDEX does, so INSERT keeps it up to date + var rightfns = con.columns + .map(function (columnid, i) { + var prefix = con.prefixes && con.prefixes[i]; + if (prefix === undefined) { + return new yy.Column({columnid: columnid}).toJS('r', ''); + } + return columnPrefixToJS(columnid, prefix); + }) + .join("+'`'+"); + var hh = hash(rightfns); + table.inddefs[con.indexid] = {rightfns: rightfns, hh: hh}; + table.indices[hh] = {}; + db.indices[con.indexid] = tableid; + } else if (con.type === 'FOREIGN KEY') { + var fk = con.fktable; + if (con.fkcolumns && con.fkcolumns.length > 0) { + //Composite foreign keys + fk.fkcolumns = con.fkcolumns; + } var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; - if (typeof fk.columnid === 'undefined') { - if (fktable.pk.columns && fktable.pk.columns.length > 0) { - fk.columnid = fktable.pk.columns[0]; - } else { - throw new Error('FOREIGN KEY allowed only to tables with PRIMARY KEYs'); - } + if (typeof fk.fkcolumns === 'undefined') { + //Composite foreign keys + fk.fkcolumns = fktable.pk.columns; } - // Store foreignkey property in column for later access - newcol.foreignkey = { - tableid: fk.tableid, - columnid: fk.columnid, - }; - var fkfn = function (r) { - var rr = {}; - var val = r[col.columnid]; - // Only check foreign key if value is not null, undefined, or NaN - if (val != null && !(typeof val === 'number' && isNaN(val))) { - rr[fk.columnid] = val; - var addr = fktable.pk.onrightfn(rr); - if (!fktable.uniqs[fktable.pk.hh][addr]) { - throw new Error('Foreign key "' + val + '" not found in table "' + fk.tableid + '"'); - } - } - return true; - }; - table.checks.push({fn: fkfn, fk: true}); - } - - if (col.onupdate) { - uss.push(`r[${JSON.stringify(col.columnid)}]=` + col.onupdate.toJS('r', '')); - } - - table.columns.push(newcol); - table.xcolumns[newcol.columnid] = newcol; - }); - } - table.defaultfns = ss.join(','); - table.onupdatefns = uss.join(';'); + fk.columns = con.columns; - constraints.forEach(function (con) { - var checkfn; - - if (con.type === 'PRIMARY KEY') { - if (table.pk) { - throw new Error('Primary key already exists'); - } - var pk = (table.pk = {}); - pk.columns = con.columns; - pk.onrightfns = pk.columns - .map(function (columnid, i) { - return columnPrefixToJS(columnid, con.prefixes && con.prefixes[i]); - }) - .join("+'`'+"); - pk.onrightfn = new Function('r', 'var y;return ' + pk.onrightfns); - pk.hh = hash(pk.onrightfns); - table.uniqs[pk.hh] = {}; - // Mark columns with primarykey property - pk.columns.forEach(function (columnid) { - if (table.xcolumns[columnid]) { - table.xcolumns[columnid].primarykey = true; + if (fk.fkcolumns.length > fk.columns.length) { + throw new Error('Invalid foreign key on table ' + tableid); } - }); - } else if (con.type === 'CHECK') { - checkfn = new Function( - 'r,params,alasql', - 'var y;return ' + quotedColumns(con.expression, columnids).toJS('r', '') - ); - } else if (con.type === 'UNIQUE') { - var uk = {}; - table.uk = table.uk || []; - table.uk.push(uk); - uk.columns = con.columns; - uk.onrightfns = uk.columns - .map(function (expr) { - return indexColumnToJS(expr.expression, table); - }) - .join("+'`'+"); - uk.onrightfn = new Function('r', 'var y;return ' + uk.onrightfns); - uk.hh = hash(uk.onrightfns); - table.uniqs[uk.hh] = {}; - } else if (con.type === 'INDEX') { - // Register the index the way CREATE INDEX does, so INSERT keeps it up to date - var rightfns = con.columns - .map(function (columnid, i) { - var prefix = con.prefixes && con.prefixes[i]; - if (prefix === undefined) { - return new yy.Column({columnid: columnid}).toJS('r', ''); - } - return columnPrefixToJS(columnid, prefix); - }) - .join("+'`'+"); - var hh = hash(rightfns); - table.inddefs[con.indexid] = {rightfns: rightfns, hh: hh}; - table.indices[hh] = {}; - db.indices[con.indexid] = tableid; - } else if (con.type === 'FOREIGN KEY') { - var fk = con.fktable; - if (con.fkcolumns && con.fkcolumns.length > 0) { - //Composite foreign keys - fk.fkcolumns = con.fkcolumns; - } - var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; - if (typeof fk.fkcolumns === 'undefined') { - //Composite foreign keys - fk.fkcolumns = fktable.pk.columns; - } - fk.columns = con.columns; - if (fk.fkcolumns.length > fk.columns.length) { - throw new Error('Invalid foreign key on table ' + tableid); - } + // Mark columns with foreignkey property + fk.columns.forEach(function (columnid, i) { + if (table.xcolumns[columnid]) { + table.xcolumns[columnid].foreignkey = { + tableid: fk.tableid, + columnid: fk.fkcolumns[i], + constraintid: con.constraintid, + }; + } + }); - // Mark columns with foreignkey property - fk.columns.forEach(function (columnid, i) { - if (table.xcolumns[columnid]) { - table.xcolumns[columnid].foreignkey = { - tableid: fk.tableid, - columnid: fk.fkcolumns[i], - constraintid: con.constraintid, - }; - } - }); + checkfn = function (r) { + var rr = {}; - checkfn = function (r) { - var rr = {}; + //Composite foreign keys + fk.fkcolumns.forEach(function (colFk, i) { + var val = r[fk.columns[i]]; + // Only include non-null, non-undefined, non-NaN values + if (val != null && !(typeof val === 'number' && isNaN(val))) { + rr[colFk] = val; + } + }); - //Composite foreign keys - fk.fkcolumns.forEach(function (colFk, i) { - var val = r[fk.columns[i]]; - // Only include non-null, non-undefined, non-NaN values - if (val != null && !(typeof val === 'number' && isNaN(val))) { - rr[colFk] = val; + if (Object.keys(rr).length === 0) { + //all values of foreign key was null + return true; + } + if (Object.keys(rr).length !== fk.columns.length) { + throw new Error('Invalid foreign key on table ' + tableid); + } + //reset fkTable as we need an up to date uniqs + var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; + var addr = fktable.pk.onrightfn(rr); + + if (!fktable.uniqs[fktable.pk.hh][addr]) { + var keyValues = fk.columns.map(function (col) { + return r[col]; + }); + throw new Error( + 'Foreign key "' + keyValues.join(', ') + '" not found in table "' + fk.tableid + '"' + ); } - }); - - if (Object.keys(rr).length === 0) { - //all values of foreign key was null return true; - } - if (Object.keys(rr).length !== fk.columns.length) { - throw new Error('Invalid foreign key on table ' + tableid); - } - //reset fkTable as we need an up to date uniqs - var fktable = alasql.databases[fk.databaseid || databaseid].tables[fk.tableid]; - var addr = fktable.pk.onrightfn(rr); + }; + } + if (checkfn) { + table.checks.push({ + fn: checkfn, + id: con.constraintid, + fk: con.type === 'FOREIGN KEY', + }); + } + }); - if (!fktable.uniqs[fktable.pk.hh][addr]) { - var keyValues = fk.columns.map(function (col) { - return r[col]; - }); - throw new Error( - 'Foreign key "' + keyValues.join(', ') + '" not found in table "' + fk.tableid + '"' - ); - } - return true; - }; - } - if (checkfn) { - table.checks.push({ - fn: checkfn, - id: con.constraintid, - fk: con.type === 'FOREIGN KEY', + if (this.view && this.viewcolumns) { + var self = this; + this.viewcolumns.forEach(function (vcol, idx) { + self.select.columns[idx].as = vcol.columnid; }); } - }); - - if (this.view && this.viewcolumns) { - var self = this; - this.viewcolumns.forEach(function (vcol, idx) { - self.select.columns[idx].as = vcol.columnid; - }); - } - //Used in 420from queryfn when table.view = true! - if (this.view && this.select) { - table.view = true; - // Store the uncompiled select statement and databaseid for lazy compilation - table.viewSelect = this.select; - table.viewDatabaseid = this.table.databaseid || databaseid; - } + //Used in 420from queryfn when table.view = true! + if (this.view && this.select) { + table.view = true; + // Store the uncompiled select statement and databaseid for lazy compilation + table.viewSelect = this.select; + table.viewDatabaseid = this.table.databaseid || databaseid; + } - if (db.engineid) { - return alasql.engines[db.engineid].createTable( - this.table.databaseid || databaseid, - tableid, - this.ifnotexists, - cb - ); - } + if (db.engineid) { + return alasql.engines[db.engineid].createTable( + this.table.databaseid || databaseid, + tableid, + this.ifnotexists, + cb + ); + } - table.insert = function (r, orreplace, ignore) { - var oldinserted = alasql.inserted; - alasql.inserted = [r]; + table.insert = function (r, orreplace, ignore) { + var oldinserted = alasql.inserted; + alasql.inserted = [r]; - var table = this; + var table = this; - // orreplace and ignore are mutually exclusive - orreplace takes precedence - if (orreplace && ignore) { - ignore = false; - } + // orreplace and ignore are mutually exclusive - orreplace takes precedence + if (orreplace && ignore) { + ignore = false; + } - var toreplace = false; // For INSERT OR REPLACE + var toreplace = false; // For INSERT OR REPLACE - /* + /* // IDENTINY or AUTO_INCREMENT // if(table.identities && table.identities.length>0) { // table.identities.forEach(function(ident){ @@ -436,360 +441,363 @@ yy.CreateTable.prototype.execute = function (databaseid, params, cb) { // }); // } */ - // Trigger prevent functionality - var prevent = false; - for (var tr in table.beforeinsert) { - var trigger = table.beforeinsert[tr]; - if (trigger) { - if (alasql.executeTrigger(trigger, databaseid, r) === false) { - prevent = prevent || true; - } - } - } - if (prevent) return; - - // Trigger prevent functionality - var escape = false; - for (tr in table.insteadofinsert) { - escape = true; - trigger = table.insteadofinsert[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, r); - } - } - if (escape) return; - - //console.log(262,r); - //console.log(263,table.identities) - for (var columnid in table.identities) { - var ident = table.identities[columnid]; - // console.log(ident); - // Only auto-assign identity value if not explicitly provided or if NULL - if (typeof r[columnid] === 'undefined' || r[columnid] === null) { - r[columnid] = ident.value; - } - // console.log(ident); - } - //console.log(270,r); - - if (table.checks && table.checks.length > 0) { - table.checks.forEach(function (check) { - // In SQL, CHECK constraints treat NULL (undefined) as passing - // Only fail if the check explicitly returns false - if (check.fn(r, {}, alasql) === false) { - // if(orreplace) toreplace=true; else - throw new Error('Violation of CHECK constraint ' + (check.id || '')); + // Trigger prevent functionality + var prevent = false; + for (var tr in table.beforeinsert) { + var trigger = table.beforeinsert[tr]; + if (trigger) { + if (alasql.executeTrigger(trigger, databaseid, r) === false) { + prevent = prevent || true; + } } - }); - } - - table.columns.forEach(function (column) { - if (column.notnull && typeof r[column.columnid] === 'undefined') { - throw new Error('Wrong NULL value in NOT NULL column ' + column.columnid); - } - }); - if (table.pk) { - var pk = table.pk; - var addr = pk.onrightfn(r); - - if (typeof table.uniqs[pk.hh][addr] !== 'undefined') { - //console.log(pk,addr,pk.onrightfn({ono:1})); - //console.log(r, pk.onrightfn(r), pk.onrightfns); - if (orreplace) toreplace = table.uniqs[pk.hh][addr]; - else if (ignore) { - alasql.inserted = oldinserted; - return false; // Silently skip insertion and indicate it was skipped - } else - throw new Error('Cannot insert record, because it already exists in primary key index'); } - // table.uniqs[pk.hh][addr]=r; - } + if (prevent) return; - if (table.uk && table.uk.length) { - for (var i = 0; i < table.uk.length; i++) { - var uk = table.uk[i]; - var ukaddr = uk.onrightfn(r); - if (typeof table.uniqs[uk.hh][ukaddr] !== 'undefined') { - if (orreplace) { - toreplace = table.uniqs[uk.hh][ukaddr]; - } else if (ignore) { - alasql.inserted = oldinserted; - return false; // Silently skip insertion and indicate it was skipped - } else { - throw new Error('Cannot insert record, because it already exists in unique index'); - } + // Trigger prevent functionality + var escape = false; + for (tr in table.insteadofinsert) { + escape = true; + trigger = table.insteadofinsert[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, r); } - // table.uniqs[uk.hh][ukaddr]=r; } - } - - if (toreplace) { - // Do UPDATE!!! - // console.log(); - table.update( - function (t) { - for (var f in r) t[f] = r[f]; - }, - table.data.indexOf(toreplace), - params - ); - } else { - table.data.push(r); - - // Final change before insert - - // Update indices + if (escape) return; + //console.log(262,r); + //console.log(263,table.identities) for (var columnid in table.identities) { var ident = table.identities[columnid]; // console.log(ident); - // Only increment if we used the auto-generated value - // If user provided explicit value >= current counter, update counter - if ( - typeof r[columnid] !== 'undefined' && - r[columnid] !== null && - +r[columnid] >= ident.value - ) { - ident.value = +r[columnid] + ident.step; - } else { - ident.value += ident.step; + // Only auto-assign identity value if not explicitly provided or if NULL + if (typeof r[columnid] === 'undefined' || r[columnid] === null) { + r[columnid] = ident.value; } // console.log(ident); } + //console.log(270,r); + if (table.checks && table.checks.length > 0) { + table.checks.forEach(function (check) { + // In SQL, CHECK constraints treat NULL (undefined) as passing + // Only fail if the check explicitly returns false + if (check.fn(r, {}, alasql) === false) { + // if(orreplace) toreplace=true; else + throw new Error('Violation of CHECK constraint ' + (check.id || '')); + } + }); + } + + table.columns.forEach(function (column) { + if (column.notnull && typeof r[column.columnid] === 'undefined') { + throw new Error('Wrong NULL value in NOT NULL column ' + column.columnid); + } + }); if (table.pk) { var pk = table.pk; var addr = pk.onrightfn(r); - table.uniqs[pk.hh][addr] = r; + + if (typeof table.uniqs[pk.hh][addr] !== 'undefined') { + //console.log(pk,addr,pk.onrightfn({ono:1})); + //console.log(r, pk.onrightfn(r), pk.onrightfns); + if (orreplace) toreplace = table.uniqs[pk.hh][addr]; + else if (ignore) { + alasql.inserted = oldinserted; + return false; // Silently skip insertion and indicate it was skipped + } else + throw new Error('Cannot insert record, because it already exists in primary key index'); + } + // table.uniqs[pk.hh][addr]=r; } + if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { + for (var i = 0; i < table.uk.length; i++) { + var uk = table.uk[i]; var ukaddr = uk.onrightfn(r); - table.uniqs[uk.hh][ukaddr] = r; - }); + if (typeof table.uniqs[uk.hh][ukaddr] !== 'undefined') { + if (orreplace) { + toreplace = table.uniqs[uk.hh][ukaddr]; + } else if (ignore) { + alasql.inserted = oldinserted; + return false; // Silently skip insertion and indicate it was skipped + } else { + throw new Error('Cannot insert record, because it already exists in unique index'); + } + } + // table.uniqs[uk.hh][ukaddr]=r; + } } - // Update regular indices (non-unique) - if (table.inddefs) { - for (var indexid in table.inddefs) { - var indexdef = table.inddefs[indexid]; - var hh = indexdef.hh; - if (table.indices[hh]) { - var addr = new Function('r,params,alasql', 'return ' + indexdef.rightfns)( - r, - params, - alasql - ); - if (!table.indices[hh][addr]) { - table.indices[hh][addr] = []; + if (toreplace) { + // Do UPDATE!!! + // console.log(); + table.update( + function (t) { + for (var f in r) t[f] = r[f]; + }, + table.data.indexOf(toreplace), + params + ); + } else { + table.data.push(r); + + // Final change before insert + + // Update indices + + for (var columnid in table.identities) { + var ident = table.identities[columnid]; + // console.log(ident); + // Only increment if we used the auto-generated value + // If user provided explicit value >= current counter, update counter + if ( + typeof r[columnid] !== 'undefined' && + r[columnid] !== null && + +r[columnid] >= ident.value + ) { + ident.value = +r[columnid] + ident.step; + } else { + ident.value += ident.step; + } + // console.log(ident); + } + + if (table.pk) { + var pk = table.pk; + var addr = pk.onrightfn(r); + table.uniqs[pk.hh][addr] = r; + } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + var ukaddr = uk.onrightfn(r); + table.uniqs[uk.hh][ukaddr] = r; + }); + } + + // Update regular indices (non-unique) + if (table.inddefs) { + for (var indexid in table.inddefs) { + var indexdef = table.inddefs[indexid]; + var hh = indexdef.hh; + if (table.indices[hh]) { + var addr = new Function('r,params,alasql', 'return ' + indexdef.rightfns)( + r, + params, + alasql + ); + if (!table.indices[hh][addr]) { + table.indices[hh][addr] = []; + } + table.indices[hh][addr].push(r); } - table.indices[hh][addr].push(r); } } } - } - // Trigger prevent functionality - for (var tr in table.afterinsert) { - var trigger = table.afterinsert[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, r); + // Trigger prevent functionality + for (var tr in table.afterinsert) { + var trigger = table.afterinsert[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, r); + } } - } - alasql.inserted = oldinserted; - }; + alasql.inserted = oldinserted; + }; - table.delete = function (index) { - var table = this; - var r = table.data[index]; + table.delete = function (index) { + var table = this; + var r = table.data[index]; - // Prevent trigger - var prevent = false; - for (var tr in table.beforedelete) { - var trigger = table.beforedelete[tr]; - if (trigger) { - if (alasql.executeTrigger(trigger, databaseid, r) === false) { - prevent = prevent || true; + // Prevent trigger + var prevent = false; + for (var tr in table.beforedelete) { + var trigger = table.beforedelete[tr]; + if (trigger) { + if (alasql.executeTrigger(trigger, databaseid, r) === false) { + prevent = prevent || true; + } } } - } - if (prevent) return false; - - // Trigger prevent functionality - var escape = false; - for (var tr in table.insteadofdelete) { - escape = true; - var trigger = table.insteadofdelete[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, r); - } - } - if (escape) return; + if (prevent) return false; - if (this.pk) { - var pk = this.pk; - var addr = pk.onrightfn(r); - if (typeof this.uniqs[pk.hh][addr] === 'undefined') { - throw new Error('Something wrong with primary key index on table'); - } else { - this.uniqs[pk.hh][addr] = undefined; + // Trigger prevent functionality + var escape = false; + for (var tr in table.insteadofdelete) { + escape = true; + var trigger = table.insteadofdelete[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, r); + } } - } - if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { - var ukaddr = uk.onrightfn(r); - if (typeof table.uniqs[uk.hh][ukaddr] === 'undefined') { - throw new Error('Something wrong with unique index on table'); + if (escape) return; + + if (this.pk) { + var pk = this.pk; + var addr = pk.onrightfn(r); + if (typeof this.uniqs[pk.hh][addr] === 'undefined') { + throw new Error('Something wrong with primary key index on table'); + } else { + this.uniqs[pk.hh][addr] = undefined; } - table.uniqs[uk.hh][ukaddr] = undefined; - }); - } - }; + } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + var ukaddr = uk.onrightfn(r); + if (typeof table.uniqs[uk.hh][ukaddr] === 'undefined') { + throw new Error('Something wrong with unique index on table'); + } + table.uniqs[uk.hh][ukaddr] = undefined; + }); + } + }; - table.deleteall = function () { - this.data.length = 0; - if (this.pk) { - // var r = this.data[i]; - this.uniqs[this.pk.hh] = {}; - } - if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { - table.uniqs[uk.hh] = {}; - }); - } - }; + table.deleteall = function () { + this.data.length = 0; + if (this.pk) { + // var r = this.data[i]; + this.uniqs[this.pk.hh] = {}; + } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + table.uniqs[uk.hh] = {}; + }); + } + }; + + table.update = function (assignfn, i, params) { + // TODO: Analyze the speed + var r = cloneDeep(this.data[i]); - table.update = function (assignfn, i, params) { - // TODO: Analyze the speed - var r = cloneDeep(this.data[i]); - - var pk; - // PART 1 - PRECHECK - if (this.pk) { - pk = this.pk; - pk.pkaddr = pk.onrightfn(r, params); - if (typeof this.uniqs[pk.hh][pk.pkaddr] === 'undefined') { - // If index is not populated (e.g., data was directly assigned), rebuild it. - // This allows UPDATE to work when data bypasses normal INSERT validation. - // Note: If data contains duplicate PKs, last occurrence wins (matches direct assignment semantics). - // This rebuild happens at most once per table since the index will be populated afterward. - this.uniqs[pk.hh] = {}; - for (var j = 0; j < this.data.length; j++) { - var addr = pk.onrightfn(this.data[j]); - this.uniqs[pk.hh][addr] = this.data[j]; - } - // Recalculate pkaddr after rebuilding + var pk; + // PART 1 - PRECHECK + if (this.pk) { + pk = this.pk; pk.pkaddr = pk.onrightfn(r, params); - } - } - if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { - uk.ukaddr = uk.onrightfn(r); - if (typeof table.uniqs[uk.hh][uk.ukaddr] === 'undefined') { + if (typeof this.uniqs[pk.hh][pk.pkaddr] === 'undefined') { // If index is not populated (e.g., data was directly assigned), rebuild it. // This allows UPDATE to work when data bypasses normal INSERT validation. - // Note: If data contains duplicate unique keys, last occurrence wins (matches direct assignment semantics). + // Note: If data contains duplicate PKs, last occurrence wins (matches direct assignment semantics). // This rebuild happens at most once per table since the index will be populated afterward. - table.uniqs[uk.hh] = {}; - for (var j = 0; j < table.data.length; j++) { - var addr = uk.onrightfn(table.data[j]); - table.uniqs[uk.hh][addr] = table.data[j]; + this.uniqs[pk.hh] = {}; + for (var j = 0; j < this.data.length; j++) { + var addr = pk.onrightfn(this.data[j]); + this.uniqs[pk.hh][addr] = this.data[j]; } - // Recalculate ukaddr after rebuilding - uk.ukaddr = uk.onrightfn(r); + // Recalculate pkaddr after rebuilding + pk.pkaddr = pk.onrightfn(r, params); } - }); - } + } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + uk.ukaddr = uk.onrightfn(r); + if (typeof table.uniqs[uk.hh][uk.ukaddr] === 'undefined') { + // If index is not populated (e.g., data was directly assigned), rebuild it. + // This allows UPDATE to work when data bypasses normal INSERT validation. + // Note: If data contains duplicate unique keys, last occurrence wins (matches direct assignment semantics). + // This rebuild happens at most once per table since the index will be populated afterward. + table.uniqs[uk.hh] = {}; + for (var j = 0; j < table.data.length; j++) { + var addr = uk.onrightfn(table.data[j]); + table.uniqs[uk.hh][addr] = table.data[j]; + } + // Recalculate ukaddr after rebuilding + uk.ukaddr = uk.onrightfn(r); + } + }); + } - assignfn(r, params, alasql); + assignfn(r, params, alasql); - // Prevent trigger - var prevent = false; - for (var tr in table.beforeupdate) { - var trigger = table.beforeupdate[tr]; - if (trigger) { - if (alasql.executeTrigger(trigger, databaseid, this.data[i], r) === false) { - prevent = prevent || true; + // Prevent trigger + var prevent = false; + for (var tr in table.beforeupdate) { + var trigger = table.beforeupdate[tr]; + if (trigger) { + if (alasql.executeTrigger(trigger, databaseid, this.data[i], r) === false) { + prevent = prevent || true; + } } } - } - if (prevent) return false; - - // Trigger prevent functionality - var escape = false; - for (var tr in table.insteadofupdate) { - escape = true; - var trigger = table.insteadofupdate[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, this.data[i], r); - } - } - if (escape) return; + if (prevent) return false; - // PART 2 - POST CHECK - if (table.checks && table.checks.length > 0) { - table.checks.forEach(function (check) { - // In SQL, CHECK constraints treat NULL (undefined) as passing - // Only fail if the check explicitly returns false - if (check.fn(r, params, alasql) === false) { - throw new Error('Violation of CHECK constraint ' + (check.id || '')); + // Trigger prevent functionality + var escape = false; + for (var tr in table.insteadofupdate) { + escape = true; + var trigger = table.insteadofupdate[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, this.data[i], r); } - }); - } - - table.columns.forEach(function (column) { - if (column.notnull && typeof r[column.columnid] === 'undefined') { - throw new Error('Wrong NULL value in NOT NULL column ' + column.columnid); } - }); - if (this.pk) { - pk.newpkaddr = pk.onrightfn(r); - if (typeof this.uniqs[pk.hh][pk.newpkaddr] !== 'undefined' && pk.newpkaddr !== pk.pkaddr) { - throw new Error('Record already exists'); + if (escape) return; + + // PART 2 - POST CHECK + if (table.checks && table.checks.length > 0) { + table.checks.forEach(function (check) { + // In SQL, CHECK constraints treat NULL (undefined) as passing + // Only fail if the check explicitly returns false + if (check.fn(r, params, alasql) === false) { + throw new Error('Violation of CHECK constraint ' + (check.id || '')); + } + }); } - } - if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { - uk.newukaddr = uk.onrightfn(r); - if (typeof table.uniqs[uk.hh][uk.newukaddr] !== 'undefined' && uk.newukaddr !== uk.ukaddr) { - throw new Error('Record already exists'); + table.columns.forEach(function (column) { + if (column.notnull && typeof r[column.columnid] === 'undefined') { + throw new Error('Wrong NULL value in NOT NULL column ' + column.columnid); } }); - } + if (this.pk) { + pk.newpkaddr = pk.onrightfn(r); + if (typeof this.uniqs[pk.hh][pk.newpkaddr] !== 'undefined' && pk.newpkaddr !== pk.pkaddr) { + throw new Error('Record already exists'); + } + } - // PART 3 UPDATE - if (this.pk) { - this.uniqs[pk.hh][pk.pkaddr] = undefined; - this.uniqs[pk.hh][pk.newpkaddr] = r; - } - if (table.uk && table.uk.length) { - table.uk.forEach(function (uk) { - table.uniqs[uk.hh][uk.ukaddr] = undefined; - table.uniqs[uk.hh][uk.newukaddr] = r; - }); - } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + uk.newukaddr = uk.onrightfn(r); + if ( + typeof table.uniqs[uk.hh][uk.newukaddr] !== 'undefined' && + uk.newukaddr !== uk.ukaddr + ) { + throw new Error('Record already exists'); + } + }); + } - this.data[i] = r; + // PART 3 UPDATE + if (this.pk) { + this.uniqs[pk.hh][pk.pkaddr] = undefined; + this.uniqs[pk.hh][pk.newpkaddr] = r; + } + if (table.uk && table.uk.length) { + table.uk.forEach(function (uk) { + table.uniqs[uk.hh][uk.ukaddr] = undefined; + table.uniqs[uk.hh][uk.newukaddr] = r; + }); + } + + this.data[i] = r; - // Trigger prevent functionality - for (var tr in table.afterupdate) { - var trigger = table.afterupdate[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, this.data[i], r); + // Trigger prevent functionality + for (var tr in table.afterupdate) { + var trigger = table.afterupdate[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, this.data[i], r); + } } - } - }; + }; - var res; + var res; - if (!alasql.options.nocount) { - res = 1; - } + if (!alasql.options.nocount) { + res = 1; + } - if (cb) res = cb(res); - return res; -}; + if (cb) res = cb(res); + return res; + }; internal.indexColumnToJS = indexColumnToJS; } diff --git a/src/statements/databases.js b/src/statements/databases.js index 2a2ccf8559..2eb7709425 100755 --- a/src/statements/databases.js +++ b/src/statements/databases.js @@ -1,7 +1,7 @@ export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var yy = alasql.yy; -/* + /* // // UPDATE for Alasql.js // Date: 03.11.2014 @@ -10,210 +10,213 @@ export default function install(alasql, internal) { // */ -/* global yy alasql */ - -// CREATE DATABASE databaseid -yy.CreateDatabase = function (params) { - return Object.assign(this, params); -}; -yy.CreateDatabase.prototype.toString = function () { - let s = 'CREATE '; // Ensure there's a space after CREATE - if (this.engineid) s += `${this.engineid} `; - s += 'DATABASE '; - if (this.ifnotexists) s += 'IF NOT EXISTS '; - s += `${this.databaseid} `; - - if (this.args && this.args.length > 0) { - s += `(${this.args.map(arg => arg.toString()).join(', ')}) `; - } - if (this.as) s += `AS ${this.as}`; - return s; -}; - -yy.CreateDatabase.prototype.execute = function (databaseid, params, cb) { - var args; - if (this.args && this.args.length > 0) { - args = this.args.map(function (arg) { - // console.log(346235, arg.toJS()); - return new Function('params,alasql', 'var y;return ' + arg.toJS())(params, alasql); - }); - } - if (this.engineid) { - var res = alasql.engines[this.engineid].createDatabase( + /* global yy alasql */ + + // CREATE DATABASE databaseid + yy.CreateDatabase = function (params) { + return Object.assign(this, params); + }; + yy.CreateDatabase.prototype.toString = function () { + let s = 'CREATE '; // Ensure there's a space after CREATE + if (this.engineid) s += `${this.engineid} `; + s += 'DATABASE '; + if (this.ifnotexists) s += 'IF NOT EXISTS '; + s += `${this.databaseid} `; + + if (this.args && this.args.length > 0) { + s += `(${this.args.map(arg => arg.toString()).join(', ')}) `; + } + if (this.as) s += `AS ${this.as}`; + return s; + }; + + yy.CreateDatabase.prototype.execute = function (databaseid, params, cb) { + var args; + if (this.args && this.args.length > 0) { + args = this.args.map(function (arg) { + // console.log(346235, arg.toJS()); + return new Function('params,alasql', 'var y;return ' + arg.toJS())(params, alasql); + }); + } + if (this.engineid) { + var res = alasql.engines[this.engineid].createDatabase( + this.databaseid, + this.args, + this.ifnotexists, + this.as, + cb + ); + return res; + } else { + var dbid = this.databaseid; + if (alasql.databases[dbid]) { + throw new Error("Database '" + dbid + "' already exists"); + } + var a = new alasql.Database(dbid); + var res = 1; + if (cb) return cb(res); + return res; + } + }; + + // CREATE DATABASE databaseid + yy.AttachDatabase = function (params) { + return Object.assign(this, params); + }; + yy.AttachDatabase.prototype.toString = function (args) { + let s = 'ATTACH'; + if (this.engineid) s += ` ${this.engineid}`; + s += ` DATABASE ${this.databaseid}`; + // TODO add params + if (args) { + s += '('; + if (args.length > 0) { + s += args.map(arg => arg.toString()).join(', '); + } + s += ')'; + } + if (this.as) s += ` AS ${this.as}`; + return s; + }; + + yy.AttachDatabase.prototype.execute = function (databaseid, params, cb) { + if (!alasql.engines[this.engineid]) { + throw new Error('Engine "' + this.engineid + '" is not defined.'); + } + schemaChanged(); + var res = alasql.engines[this.engineid].attachDatabase( this.databaseid, - this.args, - this.ifnotexists, this.as, + this.args, + params, cb ); return res; - } else { + }; + + // CREATE DATABASE databaseid + yy.DetachDatabase = function (params) { + return Object.assign(this, params); + }; + yy.DetachDatabase.prototype.toString = function () { + var s = 'DETACH'; + s += ' DATABASE' + ' ' + this.databaseid; + return s; + }; + //yy.CreateDatabase.prototype.compile = returnUndefined; + yy.DetachDatabase.prototype.execute = function (databaseid, params, cb) { + if (!alasql.databases[this.databaseid].engineid) { + throw new Error( + 'Cannot detach database "' + this.engineid + '", because it was not attached.' + ); + } + var res; + var dbid = this.databaseid; - if (alasql.databases[dbid]) { - throw new Error("Database '" + dbid + "' already exists"); + + if (dbid === alasql.DEFAULTDATABASEID) { + throw new Error('Drop of default database is prohibited'); } - var a = new alasql.Database(dbid); - var res = 1; - if (cb) return cb(res); - return res; - } -}; - -// CREATE DATABASE databaseid -yy.AttachDatabase = function (params) { - return Object.assign(this, params); -}; -yy.AttachDatabase.prototype.toString = function (args) { - let s = 'ATTACH'; - if (this.engineid) s += ` ${this.engineid}`; - s += ` DATABASE ${this.databaseid}`; - // TODO add params - if (args) { - s += '('; - if (args.length > 0) { - s += args.map(arg => arg.toString()).join(', '); + + if (!alasql.databases[dbid]) { + if (!this.ifexists) { + throw new Error("Database '" + dbid + "' does not exist"); + } else { + res = 0; + } + } else { + // Usually databases are detached and then dropped. Detaching will delete + // the database object from memory. While this is OK for in-memory and + // other persistent databases, for FileStorage DBs, we will + // not be able to delete the DB file (.json) since we would have lost + // the filename by deleting the in-memory database object here. + // For this reason, to delete the associated JSON file, + // keeping the name of the file alone as a property inside the db object + // until it gets DROPped subsequently (only for FileStorage DBs) + var isFS = + alasql.databases[dbid].engineid && alasql.databases[dbid].engineid == 'FILESTORAGE', + filename = alasql.databases[dbid].filename || ''; + + delete alasql.databases[dbid]; + schemaChanged(); + + if (isFS) { + // Create a detached FS database + alasql.databases[dbid] = {}; + alasql.databases[dbid].isDetached = true; + alasql.databases[dbid].filename = filename; + } + + if (dbid === alasql.useid) { + alasql.use(); + } + res = 1; } - s += ')'; - } - if (this.as) s += ` AS ${this.as}`; - return s; -}; - -yy.AttachDatabase.prototype.execute = function (databaseid, params, cb) { - if (!alasql.engines[this.engineid]) { - throw new Error('Engine "' + this.engineid + '" is not defined.'); - } - schemaChanged(); - var res = alasql.engines[this.engineid].attachDatabase( - this.databaseid, - this.as, - this.args, - params, - cb - ); - return res; -}; - -// CREATE DATABASE databaseid -yy.DetachDatabase = function (params) { - return Object.assign(this, params); -}; -yy.DetachDatabase.prototype.toString = function () { - var s = 'DETACH'; - s += ' DATABASE' + ' ' + this.databaseid; - return s; -}; -//yy.CreateDatabase.prototype.compile = returnUndefined; -yy.DetachDatabase.prototype.execute = function (databaseid, params, cb) { - if (!alasql.databases[this.databaseid].engineid) { - throw new Error('Cannot detach database "' + this.engineid + '", because it was not attached.'); - } - var res; - - var dbid = this.databaseid; - - if (dbid === alasql.DEFAULTDATABASEID) { - throw new Error('Drop of default database is prohibited'); - } - - if (!alasql.databases[dbid]) { - if (!this.ifexists) { + if (cb) cb(res); + return res; + }; + + // USE DATABSE databaseid + // USE databaseid + yy.UseDatabase = function (params) { + return Object.assign(this, params); + }; + yy.UseDatabase.prototype.toString = function () { + return 'USE' + ' ' + 'DATABASE' + ' ' + this.databaseid; + }; + //yy.UseDatabase.prototype.compile = returnUndefined; + yy.UseDatabase.prototype.execute = function (databaseid, params, cb) { + var dbid = this.databaseid; + if (!alasql.databases[dbid]) { throw new Error("Database '" + dbid + "' does not exist"); - } else { - res = 0; } - } else { - // Usually databases are detached and then dropped. Detaching will delete - // the database object from memory. While this is OK for in-memory and - // other persistent databases, for FileStorage DBs, we will - // not be able to delete the DB file (.json) since we would have lost - // the filename by deleting the in-memory database object here. - // For this reason, to delete the associated JSON file, - // keeping the name of the file alone as a property inside the db object - // until it gets DROPped subsequently (only for FileStorage DBs) - var isFS = alasql.databases[dbid].engineid && alasql.databases[dbid].engineid == 'FILESTORAGE', - filename = alasql.databases[dbid].filename || ''; - - delete alasql.databases[dbid]; - schemaChanged(); - - if (isFS) { - // Create a detached FS database - alasql.databases[dbid] = {}; - alasql.databases[dbid].isDetached = true; - alasql.databases[dbid].filename = filename; + alasql.use(dbid); + var res = 1; + if (cb) cb(res); + return res; + }; + + // DROP DATABASE databaseid + yy.DropDatabase = function (params) { + return Object.assign(this, params); + }; + yy.DropDatabase.prototype.toString = function () { + var s = 'DROP'; + if (this.ifexists) s += ' IF EXISTS'; + s += ' DATABASE ' + this.databaseid; + return s; + }; + //yy.DropDatabase.prototype.compile = returnUndefined; + yy.DropDatabase.prototype.execute = function (databaseid, params, cb) { + if (this.engineid) { + return alasql.engines[this.engineid].dropDatabase(this.databaseid, this.ifexists, cb); } + let res; + + const dbid = this.databaseid; - if (dbid === alasql.useid) { - alasql.use(); + if (dbid === alasql.DEFAULTDATABASEID) { + throw new Error('Drop of default database is prohibited'); } - res = 1; - } - if (cb) cb(res); - return res; -}; - -// USE DATABSE databaseid -// USE databaseid -yy.UseDatabase = function (params) { - return Object.assign(this, params); -}; -yy.UseDatabase.prototype.toString = function () { - return 'USE' + ' ' + 'DATABASE' + ' ' + this.databaseid; -}; -//yy.UseDatabase.prototype.compile = returnUndefined; -yy.UseDatabase.prototype.execute = function (databaseid, params, cb) { - var dbid = this.databaseid; - if (!alasql.databases[dbid]) { - throw new Error("Database '" + dbid + "' does not exist"); - } - alasql.use(dbid); - var res = 1; - if (cb) cb(res); - return res; -}; - -// DROP DATABASE databaseid -yy.DropDatabase = function (params) { - return Object.assign(this, params); -}; -yy.DropDatabase.prototype.toString = function () { - var s = 'DROP'; - if (this.ifexists) s += ' IF EXISTS'; - s += ' DATABASE ' + this.databaseid; - return s; -}; -//yy.DropDatabase.prototype.compile = returnUndefined; -yy.DropDatabase.prototype.execute = function (databaseid, params, cb) { - if (this.engineid) { - return alasql.engines[this.engineid].dropDatabase(this.databaseid, this.ifexists, cb); - } - let res; - - const dbid = this.databaseid; - - if (dbid === alasql.DEFAULTDATABASEID) { - throw new Error('Drop of default database is prohibited'); - } - if (!alasql.databases[dbid]) { - if (!this.ifexists) { - throw new Error(`Database '${dbid}' does not exist`); + if (!alasql.databases[dbid]) { + if (!this.ifexists) { + throw new Error(`Database '${dbid}' does not exist`); + } else { + res = 0; + } } else { - res = 0; + if (alasql.databases[dbid].engineid) { + throw new Error(`Cannot drop database '${dbid}', because it is attached. Detach it.`); + } + + delete alasql.databases[dbid]; + schemaChanged(); + if (dbid === alasql.useid) { + alasql.use(); + } + res = 1; } - } else { - if (alasql.databases[dbid].engineid) { - throw new Error(`Cannot drop database '${dbid}', because it is attached. Detach it.`); - } - - delete alasql.databases[dbid]; - schemaChanged(); - if (dbid === alasql.useid) { - alasql.use(); - } - res = 1; - } - if (cb) cb(res); - return res; -}; + if (cb) cb(res); + return res; + }; } diff --git a/src/statements/declare.js b/src/statements/declare.js index 868ad00e52..d259d36e34 100755 --- a/src/statements/declare.js +++ b/src/statements/declare.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // SET for Alasql.js // Date: 01.12.2014 @@ -8,66 +8,66 @@ export default function install(alasql) { // */ -yy.Declare = function (params) { - return Object.assign(this, params); -}; -yy.Declare.prototype.toString = function () { - let s = 'DECLARE '; - if (this.declares && this.declares.length > 0) { - s += this.declares - .map(declare => { - let declareStr = `@${declare.variable} ${declare.dbtypeid}`; - if (declare.dbsize) { - declareStr += `(${declare.dbsize}`; - if (declare.dbprecision) { - declareStr += `,${declare.dbprecision}`; + yy.Declare = function (params) { + return Object.assign(this, params); + }; + yy.Declare.prototype.toString = function () { + let s = 'DECLARE '; + if (this.declares && this.declares.length > 0) { + s += this.declares + .map(declare => { + let declareStr = `@${declare.variable} ${declare.dbtypeid}`; + if (declare.dbsize) { + declareStr += `(${declare.dbsize}`; + if (declare.dbprecision) { + declareStr += `,${declare.dbprecision}`; + } + declareStr += ')'; } - declareStr += ')'; - } - if (declare.expression) { - declareStr += ` = ${declare.expression.toString()}`; - } - return declareStr; - }) - .join(','); - } - return s; -}; + if (declare.expression) { + declareStr += ` = ${declare.expression.toString()}`; + } + return declareStr; + }) + .join(','); + } + return s; + }; -yy.Declare.prototype.execute = function (databaseid, params, cb) { - var res = 1; - var that = this; // without this assigned to a variable, inside the forEach, the reference to `this` is lost. It is needed for the Function statement for binding - if (that.declares && that.declares.length > 0) { - that.declares.forEach(function (declare) { - var dbtypeid = declare.dbtypeid; - if (!alasql.fn[dbtypeid]) { - dbtypeid = dbtypeid.toUpperCase(); - } - alasql.declares[declare.variable] = { - dbtypeid: dbtypeid, - dbsize: declare.dbsize, - dbprecision: declare.dbprecision, - }; + yy.Declare.prototype.execute = function (databaseid, params, cb) { + var res = 1; + var that = this; // without this assigned to a variable, inside the forEach, the reference to `this` is lost. It is needed for the Function statement for binding + if (that.declares && that.declares.length > 0) { + that.declares.forEach(function (declare) { + var dbtypeid = declare.dbtypeid; + if (!alasql.fn[dbtypeid]) { + dbtypeid = dbtypeid.toUpperCase(); + } + alasql.declares[declare.variable] = { + dbtypeid: dbtypeid, + dbsize: declare.dbsize, + dbprecision: declare.dbprecision, + }; - // Set value - if (declare.expression) { - // console.log(7547654, declare.expression.toJS('', '', null)); - alasql.vars[declare.variable] = new Function( - 'params,alasql', - 'return ' + declare.expression.toJS('({})', '', null) - ).bind(that)(params, alasql); - if (alasql.declares[declare.variable]) { - alasql.vars[declare.variable] = alasql.stdfn.CONVERT( - alasql.vars[declare.variable], - alasql.declares[declare.variable] - ); + // Set value + if (declare.expression) { + // console.log(7547654, declare.expression.toJS('', '', null)); + alasql.vars[declare.variable] = new Function( + 'params,alasql', + 'return ' + declare.expression.toJS('({})', '', null) + ).bind(that)(params, alasql); + if (alasql.declares[declare.variable]) { + alasql.vars[declare.variable] = alasql.stdfn.CONVERT( + alasql.vars[declare.variable], + alasql.declares[declare.variable] + ); + } } - } - }); - } - if (cb) { - res = cb(res); - } - return res; -}; + }); + } + if (cb) { + res = cb(res); + } + return res; + }; } diff --git a/src/statements/delete.js b/src/statements/delete.js index a5449abd9f..1ced89dcc8 100755 --- a/src/statements/delete.js +++ b/src/statements/delete.js @@ -2,7 +2,7 @@ import {cloneDeep} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // DELETE for Alasql.js // Date: 03.11.2014 @@ -10,224 +10,224 @@ export default function install(alasql) { // */ -yy.Delete = function (params) { - return Object.assign(this, params); -}; -yy.Delete.prototype.toString = function () { - var s = 'DELETE FROM ' + this.table.toString(); - if (this.where) s += ' WHERE ' + this.where.toString(); - if (this.output) { - s += ' OUTPUT '; - s += this.output.columns.map(col => col.toString()).join(', '); - if (this.output.intovar) { - s += ' INTO ' + this.output.method + this.output.intovar; - } else if (this.output.intotable) { - s += ' INTO ' + this.output.intotable.toString(); - if (this.output.intocolumns) { - s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + yy.Delete = function (params) { + return Object.assign(this, params); + }; + yy.Delete.prototype.toString = function () { + var s = 'DELETE FROM ' + this.table.toString(); + if (this.where) s += ' WHERE ' + this.where.toString(); + if (this.output) { + s += ' OUTPUT '; + s += this.output.columns.map(col => col.toString()).join(', '); + if (this.output.intovar) { + s += ' INTO ' + this.output.method + this.output.intovar; + } else if (this.output.intotable) { + s += ' INTO ' + this.output.intotable.toString(); + if (this.output.intocolumns) { + s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + } } } - } - return s; -}; - -yy.Delete.prototype.compile = function (databaseid) { - var self = this; - - // Handle ParamValue (anonymous data table) - wrap execution - if (this.table instanceof yy.ParamValue) { - return yy.compileParamValue(this.table.param, 'DELETE', true, databaseid, self, 'table'); - } - - databaseid = this.table.databaseid || databaseid; - var tableid = this.table.tableid; - var statement; - var db = alasql.databases[databaseid]; - - if (this.where) { - if (this.exists) { - this.existsfn = this.exists.map(function (ex) { - var nq = ex.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } + return s; + }; + + yy.Delete.prototype.compile = function (databaseid) { + var self = this; - if (this.queries) { - this.queriesfn = this.queries.map(function (q) { - var nq = q.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); + // Handle ParamValue (anonymous data table) - wrap execution + if (this.table instanceof yy.ParamValue) { + return yy.compileParamValue(this.table.param, 'DELETE', true, databaseid, self, 'table'); } - var wherefn = new Function( - 'r,params,alasql', - 'var y;return (' + this.where.toJS('r', '') + ')' - ).bind(this); - - statement = function (params, cb) { - if (db.engineid && alasql.engines[db.engineid].deleteFromTable) { - return alasql.engines[db.engineid].deleteFromTable( - databaseid, - tableid, - wherefn, - params, - cb - ); + databaseid = this.table.databaseid || databaseid; + var tableid = this.table.tableid; + var statement; + var db = alasql.databases[databaseid]; + + if (this.where) { + if (this.exists) { + this.existsfn = this.exists.map(function (ex) { + var nq = ex.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); } - if ( - alasql.options.autocommit && - db.engineid && - (db.engineid == 'LOCALSTORAGE' || db.engineid == 'FILESTORAGE') - ) { - alasql.engines[db.engineid].loadTableData(databaseid, tableid); + if (this.queries) { + this.queriesfn = this.queries.map(function (q) { + var nq = q.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); } - var table = db.tables[tableid]; - var orignum = table.data.length; - - var newtable = []; - var deletedRows = []; - // Track deleted rows for the OUTPUT clause and AFTER DELETE triggers - var keepDeleted = self.output || Object.keys(table.afterdelete || {}).length > 0; - for (var i = 0, ilen = table.data.length; i < ilen; i++) { - if (wherefn(table.data[i], params, alasql)) { - if (keepDeleted) { - deletedRows.push(cloneDeep(table.data[i])); - } - // Check for transaction - if it is not possible then return all back - if (table.delete) { - table.delete(i, params, alasql); + var wherefn = new Function( + 'r,params,alasql', + 'var y;return (' + this.where.toJS('r', '') + ')' + ).bind(this); + + statement = function (params, cb) { + if (db.engineid && alasql.engines[db.engineid].deleteFromTable) { + return alasql.engines[db.engineid].deleteFromTable( + databaseid, + tableid, + wherefn, + params, + cb + ); + } + + if ( + alasql.options.autocommit && + db.engineid && + (db.engineid == 'LOCALSTORAGE' || db.engineid == 'FILESTORAGE') + ) { + alasql.engines[db.engineid].loadTableData(databaseid, tableid); + } + + var table = db.tables[tableid]; + var orignum = table.data.length; + + var newtable = []; + var deletedRows = []; + // Track deleted rows for the OUTPUT clause and AFTER DELETE triggers + var keepDeleted = self.output || Object.keys(table.afterdelete || {}).length > 0; + for (var i = 0, ilen = table.data.length; i < ilen; i++) { + if (wherefn(table.data[i], params, alasql)) { + if (keepDeleted) { + deletedRows.push(cloneDeep(table.data[i])); + } + // Check for transaction - if it is not possible then return all back + if (table.delete) { + table.delete(i, params, alasql); + } else { + // Simply do not push + } } else { - // Simply do not push + newtable.push(table.data[i]); } - } else { - newtable.push(table.data[i]); } - } - table.data = newtable; - - // AFTER DELETE triggers - call for each deleted row - // Note: Triggers are called once per row per trigger (row-level triggers) - // For N deleted rows and M triggers, this results in N×M trigger calls - if (table.afterdelete) { - for (var i = 0; i < deletedRows.length; i++) { - for (var tr in table.afterdelete) { - var trigger = table.afterdelete[tr]; - if (trigger) { - alasql.executeTrigger(trigger, databaseid, deletedRows[i]); + table.data = newtable; + + // AFTER DELETE triggers - call for each deleted row + // Note: Triggers are called once per row per trigger (row-level triggers) + // For N deleted rows and M triggers, this results in N×M trigger calls + if (table.afterdelete) { + for (var i = 0; i < deletedRows.length; i++) { + for (var tr in table.afterdelete) { + var trigger = table.afterdelete[tr]; + if (trigger) { + alasql.executeTrigger(trigger, databaseid, deletedRows[i]); + } } } } - } - var res = orignum - table.data.length; - - // Handle OUTPUT clause - if (self.output) { - var output = []; - for (var i = 0; i < deletedRows.length; i++) { - var r = deletedRows[i]; - var outputRow = {}; - self.output.columns.forEach(function (col) { - if (col.columnid === '*') { - // For *, expand all properties - for (var key in r) { - outputRow[key] = r[key]; + var res = orignum - table.data.length; + + // Handle OUTPUT clause + if (self.output) { + var output = []; + for (var i = 0; i < deletedRows.length; i++) { + var r = deletedRows[i]; + var outputRow = {}; + self.output.columns.forEach(function (col) { + if (col.columnid === '*') { + // For *, expand all properties + for (var key in r) { + outputRow[key] = r[key]; + } + } else { + var colname = col.as || col.columnid; + // Direct property access + outputRow[colname] = r[col.columnid]; } - } else { - var colname = col.as || col.columnid; - // Direct property access - outputRow[colname] = r[col.columnid]; - } - }); - output.push(outputRow); + }); + output.push(outputRow); + } + res = output; } - res = output; - } - if ( - alasql.options.autocommit && - db.engineid && - (db.engineid == 'LOCALSTORAGE' || db.engineid == 'FILESTORAGE') - ) { - alasql.engines[db.engineid].saveTableData(databaseid, tableid); - } + if ( + alasql.options.autocommit && + db.engineid && + (db.engineid == 'LOCALSTORAGE' || db.engineid == 'FILESTORAGE') + ) { + alasql.engines[db.engineid].saveTableData(databaseid, tableid); + } - if (cb) res = cb(res); + if (cb) res = cb(res); - return res; - }; - } else { - statement = function (params, cb) { - if (alasql.options.autocommit && db.engineid) { - alasql.engines[db.engineid].loadTableData(databaseid, tableid); - } + return res; + }; + } else { + statement = function (params, cb) { + if (alasql.options.autocommit && db.engineid) { + alasql.engines[db.engineid].loadTableData(databaseid, tableid); + } - var table = db.tables[tableid]; - table.dirty = true; - var orignum = db.tables[tableid].data.length; + var table = db.tables[tableid]; + table.dirty = true; + var orignum = db.tables[tableid].data.length; - // Track deleted rows for OUTPUT clause - var deletedRows = []; - if (self.output) { - deletedRows = table.data.map(function (row) { - return cloneDeep(row); - }); - } + // Track deleted rows for OUTPUT clause + var deletedRows = []; + if (self.output) { + deletedRows = table.data.map(function (row) { + return cloneDeep(row); + }); + } - // Delete all records from the array - db.tables[tableid].data.length = 0; + // Delete all records from the array + db.tables[tableid].data.length = 0; - // Reset PRIMARY KEY and indexes - for (var ix in db.tables[tableid].uniqs) { - db.tables[tableid].uniqs[ix] = {}; - } + // Reset PRIMARY KEY and indexes + for (var ix in db.tables[tableid].uniqs) { + db.tables[tableid].uniqs[ix] = {}; + } - for (var ix in db.tables[tableid].indices) { - db.tables[tableid].indices[ix] = {}; - } + for (var ix in db.tables[tableid].indices) { + db.tables[tableid].indices[ix] = {}; + } - if (alasql.options.autocommit && db.engineid) { - alasql.engines[db.engineid].saveTableData(databaseid, tableid); - } + if (alasql.options.autocommit && db.engineid) { + alasql.engines[db.engineid].saveTableData(databaseid, tableid); + } - var res = orignum; - - // Handle OUTPUT clause - if (self.output) { - var output = []; - for (var i = 0; i < deletedRows.length; i++) { - var r = deletedRows[i]; - var outputRow = {}; - self.output.columns.forEach(function (col) { - if (col.columnid === '*') { - // For *, expand all properties - for (var key in r) { - outputRow[key] = r[key]; + var res = orignum; + + // Handle OUTPUT clause + if (self.output) { + var output = []; + for (var i = 0; i < deletedRows.length; i++) { + var r = deletedRows[i]; + var outputRow = {}; + self.output.columns.forEach(function (col) { + if (col.columnid === '*') { + // For *, expand all properties + for (var key in r) { + outputRow[key] = r[key]; + } + } else { + var colname = col.as || col.columnid; + // Direct property access + outputRow[colname] = r[col.columnid]; } - } else { - var colname = col.as || col.columnid; - // Direct property access - outputRow[colname] = r[col.columnid]; - } - }); - output.push(outputRow); + }); + output.push(outputRow); + } + res = output; } - res = output; - } - if (cb) cb(res); - return res; - }; - } + if (cb) cb(res); + return res; + }; + } - return statement; -}; + return statement; + }; -yy.Delete.prototype.execute = function (databaseid, params, cb) { - return this.compile(databaseid)(params, cb); -}; + yy.Delete.prototype.execute = function (databaseid, params, cb) { + return this.compile(databaseid)(params, cb); + }; } diff --git a/src/statements/dropindex.js b/src/statements/dropindex.js index ed7a1ad7da..43b78e3876 100755 --- a/src/statements/dropindex.js +++ b/src/statements/dropindex.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // DROP TABLE for Alasql.js // Date: 03.11.2014 @@ -8,18 +8,18 @@ export default function install(alasql) { // */ -yy.DropIndex = function (params) { - return Object.assign(this, params); -}; -yy.DropIndex.prototype.toString = function () { - return 'DROP INDEX' + this.indexid; -}; + yy.DropIndex = function (params) { + return Object.assign(this, params); + }; + yy.DropIndex.prototype.toString = function () { + return 'DROP INDEX' + this.indexid; + }; -// DROP TABLE -yy.DropIndex.prototype.compile = function (db) { - var indexid = this.indexid; - return function () { - return 1; + // DROP TABLE + yy.DropIndex.prototype.compile = function (db) { + var indexid = this.indexid; + return function () { + return 1; + }; }; -}; } diff --git a/src/statements/droptable.js b/src/statements/droptable.js index 86bd9746e0..1584d16e9a 100755 --- a/src/statements/droptable.js +++ b/src/statements/droptable.js @@ -1,7 +1,7 @@ export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var yy = alasql.yy; -/* + /* // // DROP TABLE for Alasql.js // Date: 03.11.2014 @@ -9,20 +9,20 @@ export default function install(alasql, internal) { // */ -yy.DropTable = function (params) { - return Object.assign(this, params); -}; -yy.DropTable.prototype.toString = function () { - var s = 'DROP' + ' '; - if (this.view) s += 'VIEW'; - else s += 'TABLE'; - if (this.ifexists) s += ' IF EXISTS'; - s += ' ' + this.tables.toString(); - return s; -}; + yy.DropTable = function (params) { + return Object.assign(this, params); + }; + yy.DropTable.prototype.toString = function () { + var s = 'DROP' + ' '; + if (this.view) s += 'VIEW'; + else s += 'TABLE'; + if (this.ifexists) s += ' IF EXISTS'; + s += ' ' + this.tables.toString(); + return s; + }; -// DROP TABLE -/** + // DROP TABLE + /** Drop tables @param {string} databaseid Database id @param {object} params Parameters @@ -32,86 +32,86 @@ yy.DropTable.prototype.toString = function () { DROP TABLE one; DROP TABLE IF NOT EXISTS two, three; */ -yy.DropTable.prototype.execute = function (databaseid, params, cb) { - var ifexists = this.ifexists; - var res = 0; // No tables removed - var count = 0; - var tlen = this.tables.length; + yy.DropTable.prototype.execute = function (databaseid, params, cb) { + var ifexists = this.ifexists; + var res = 0; // No tables removed + var count = 0; + var tlen = this.tables.length; - // For each table in the list - this.tables.forEach(function (table) { - var db = alasql.databases[table.databaseid || databaseid]; - var tableid = table.tableid; + // For each table in the list + this.tables.forEach(function (table) { + var db = alasql.databases[table.databaseid || databaseid]; + var tableid = table.tableid; - /** @todo Test with AUTOCOMMIT flag is ON */ - /** @todo Test with IndexedDB and multiple tables */ + /** @todo Test with AUTOCOMMIT flag is ON */ + /** @todo Test with IndexedDB and multiple tables */ - if (!ifexists || (ifexists && db.tables[tableid])) { - if (!db.tables[tableid]) { - if (!alasql.options.dropifnotexists) { - throw new Error( - `Can not drop table ${JSON.stringify( - table.tableid - )} because it does not exist in the database.` - ); - } - } else { - if (db.engineid /*&& alasql.options.autocommit*/) { - alasql.engines[db.engineid].dropTable( - table.databaseid || databaseid, - tableid, - ifexists, - function (res1) { - delete db.tables[tableid]; - schemaChanged(db); - res += res1; - count++; - if (count == tlen && cb) cb(res); - } - ); + if (!ifexists || (ifexists && db.tables[tableid])) { + if (!db.tables[tableid]) { + if (!alasql.options.dropifnotexists) { + throw new Error( + `Can not drop table ${JSON.stringify( + table.tableid + )} because it does not exist in the database.` + ); + } } else { - delete db.tables[tableid]; - // The schema changed, so statements in the statement cache must be compiled again - schemaChanged(db); - res++; - count++; - if (count == tlen && cb) cb(res); + if (db.engineid /*&& alasql.options.autocommit*/) { + alasql.engines[db.engineid].dropTable( + table.databaseid || databaseid, + tableid, + ifexists, + function (res1) { + delete db.tables[tableid]; + schemaChanged(db); + res += res1; + count++; + if (count == tlen && cb) cb(res); + } + ); + } else { + delete db.tables[tableid]; + // The schema changed, so statements in the statement cache must be compiled again + schemaChanged(db); + res++; + count++; + if (count == tlen && cb) cb(res); + } } + } else { + count++; + if (count == tlen && cb) cb(res); } - } else { - count++; - if (count == tlen && cb) cb(res); - } - }); - // if(cb) res = cb(res); - return res; -}; + }); + // if(cb) res = cb(res); + return res; + }; -yy.TruncateTable = function (params) { - return Object.assign(this, params); -}; -yy.TruncateTable.prototype.toString = function () { - var s = 'TRUNCATE TABLE'; - s += ' ' + this.table.toString(); - return s; -}; + yy.TruncateTable = function (params) { + return Object.assign(this, params); + }; + yy.TruncateTable.prototype.toString = function () { + var s = 'TRUNCATE TABLE'; + s += ' ' + this.table.toString(); + return s; + }; -yy.TruncateTable.prototype.execute = function (databaseid, params, cb) { - var db = alasql.databases[this.table.databaseid || databaseid]; - var tableid = this.table.tableid; - if (db.engineid) { - return alasql.engines[db.engineid].truncateTable( - this.table.databaseid || databaseid, - tableid, - this.ifexists, - cb - ); - } - if (db.tables[tableid]) { - db.tables[tableid].data = []; - } else { - throw new Error('Cannot truncate table becaues it does not exist'); - } - return cb ? cb(0) : 0; -}; + yy.TruncateTable.prototype.execute = function (databaseid, params, cb) { + var db = alasql.databases[this.table.databaseid || databaseid]; + var tableid = this.table.tableid; + if (db.engineid) { + return alasql.engines[db.engineid].truncateTable( + this.table.databaseid || databaseid, + tableid, + this.ifexists, + cb + ); + } + if (db.tables[tableid]) { + db.tables[tableid].data = []; + } else { + throw new Error('Cannot truncate table becaues it does not exist'); + } + return cb ? cb(0) : 0; + }; } diff --git a/src/statements/if.js b/src/statements/if.js index 9cf483c0de..4c2d6c1574 100755 --- a/src/statements/if.js +++ b/src/statements/if.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // IF for Alasql.js // Date: 03.11.2014 @@ -8,37 +8,37 @@ export default function install(alasql) { // */ -yy.If = function (params) { - return Object.assign(this, params); -}; -yy.If.prototype.toString = function () { - var s = 'IF' + ' '; - s += this.expression.toString(); - s += ' ' + this.thenstat.toString(); - if (this.elsestat) s += ' ELSE ' + this.thenstat.toString(); - return s; -}; + yy.If = function (params) { + return Object.assign(this, params); + }; + yy.If.prototype.toString = function () { + var s = 'IF' + ' '; + s += this.expression.toString(); + s += ' ' + this.thenstat.toString(); + if (this.elsestat) s += ' ELSE ' + this.thenstat.toString(); + return s; + }; -// CREATE TABLE -// yy.CreateTable.prototype.compile = returnUndefined; -yy.If.prototype.execute = function (databaseid, params, cb) { - var res; - // console.log(this); - // console.log(this.expression.toJS('{}','',null)); - // console.log(); - var fn = new Function( - 'params,alasql,p', - 'var y;return ' + this.expression.toJS('({})', '', null) - ).bind(this); - // var fn = new Function('params,alasql,p','console.log(this.thenstat);return '+this.expression.toJS('({})','',null)).bind(this); - if (fn(params, alasql)) res = this.thenstat.execute(databaseid, params, cb); - else { - if (this.elsestat) res = this.elsestat.execute(databaseid, params, cb); + // CREATE TABLE + // yy.CreateTable.prototype.compile = returnUndefined; + yy.If.prototype.execute = function (databaseid, params, cb) { + var res; + // console.log(this); + // console.log(this.expression.toJS('{}','',null)); + // console.log(); + var fn = new Function( + 'params,alasql,p', + 'var y;return ' + this.expression.toJS('({})', '', null) + ).bind(this); + // var fn = new Function('params,alasql,p','console.log(this.thenstat);return '+this.expression.toJS('({})','',null)).bind(this); + if (fn(params, alasql)) res = this.thenstat.execute(databaseid, params, cb); else { - if (cb) res = cb(res); + if (this.elsestat) res = this.elsestat.execute(databaseid, params, cb); + else { + if (cb) res = cb(res); + } } - } - // else res = this.elsestat.execute(databaseid,params,cb,scope); - return res; -}; + // else res = this.elsestat.execute(databaseid,params,cb,scope); + return res; + }; } diff --git a/src/statements/insert.js b/src/statements/insert.js index 0976886141..80cd948a73 100755 --- a/src/statements/insert.js +++ b/src/statements/insert.js @@ -3,7 +3,7 @@ import {cloneDeep} from '../utils/helpers.js'; export default function install(alasql, internal) { var yy = alasql.yy; var JSONtoJS = internal.JSONtoJS; -/* + /* // // INSERT for Alasql.js // Date: 03.11.2014 @@ -11,177 +11,178 @@ export default function install(alasql, internal) { // */ -/* global yy alasql*/ -yy.Insert = function (params) { - return Object.assign(this, params); -}; -yy.Insert.prototype.toString = function () { - var s = 'INSERT '; - if (this.orreplace) s += 'OR REPLACE '; - if (this.replaceonly) s = 'REPLACE '; - if (this.ignore) s += 'IGNORE '; - s += 'INTO ' + this.into.toString(); - if (this.columns) s += '(' + this.columns.toString() + ')'; - if (this.values) { - var values = this.values.map(function (value) { - return '(' + value.toString() + ')'; - }); - s += ' VALUES ' + values.join(','); - } - if (this.select) s += ' ' + this.select.toString(); - if (this.setcolumns) { - s += ' SET '; - s += this.setcolumns.map(col => col.toString()).join(', '); - } - if (this.output) { - s += ' OUTPUT '; - s += this.output.columns.map(col => col.toString()).join(', '); - if (this.output.intovar) { - s += ' INTO ' + this.output.method + this.output.intovar; - } else if (this.output.intotable) { - s += ' INTO ' + this.output.intotable.toString(); - if (this.output.intocolumns) { - s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + /* global yy alasql*/ + yy.Insert = function (params) { + return Object.assign(this, params); + }; + yy.Insert.prototype.toString = function () { + var s = 'INSERT '; + if (this.orreplace) s += 'OR REPLACE '; + if (this.replaceonly) s = 'REPLACE '; + if (this.ignore) s += 'IGNORE '; + s += 'INTO ' + this.into.toString(); + if (this.columns) s += '(' + this.columns.toString() + ')'; + if (this.values) { + var values = this.values.map(function (value) { + return '(' + value.toString() + ')'; + }); + s += ' VALUES ' + values.join(','); + } + if (this.select) s += ' ' + this.select.toString(); + if (this.setcolumns) { + s += ' SET '; + s += this.setcolumns.map(col => col.toString()).join(', '); + } + if (this.output) { + s += ' OUTPUT '; + s += this.output.columns.map(col => col.toString()).join(', '); + if (this.output.intovar) { + s += ' INTO ' + this.output.method + this.output.intovar; + } else if (this.output.intotable) { + s += ' INTO ' + this.output.intotable.toString(); + if (this.output.intocolumns) { + s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + } } } - } - return s; -}; - -yy.Insert.prototype.toJS = function (context, tableid, defcols) { - // console.log('Expression',this); - // if(this.expression.reduced) return 'true'; - // return this.expression.toJS(context, tableid, defcols); - // console.log('Select.toJS', 81, this.queriesidx); - // var s = 'this.queriesdata['+(this.queriesidx-1)+'][0]'; - - var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; - // s = '(console.log(this.queriesfn[0]),'+s+')'; - // console.log(this,s); - - return s; -}; - -yy.Insert.prototype.compile = function (databaseid) { - var self = this; - - // Handle ParamValue (anonymous data table) - wrap execution - if (self.into instanceof yy.ParamValue) { - return yy.compileParamValue(self.into.param, 'INSERT', true, databaseid, self, 'into'); - } - - databaseid = self.into.databaseid || databaseid; - var db = alasql.databases[databaseid]; - // console.log(self); - var tableid = self.into.tableid; - var table = db.tables[tableid]; - - if (!table) { - throw "Table '" + tableid + "' could not be found"; - } - - // Helper function to create error message for value/column count mismatch - var createValueCountMismatchError = function (valueCount, columnCount, columnType) { - return ( - `The number of values (${valueCount}) does not match the number of ${columnType} (${columnCount}). ` + - 'If using a subquery, use INSERT INTO ... SELECT instead of INSERT INTO ... VALUES (SELECT ...)' - ); + return s; }; - // Check, if this dirty flag is required - var s = ''; - var sw = ''; - var s = "db.tables['" + tableid + "'].dirty=true;"; - // aa = array to accumulate inserted rows (used for OUTPUT clause and concat to table.data) - var s3 = 'var a,aa=[],x;'; - - var s33; - - // INSERT INTO table VALUES - if (this.values) { - if (this.exists) { - this.existsfn = this.exists.map(function (ex) { - var nq = ex.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } - if (this.queries) { - this.queriesfn = this.queries.map(function (q) { - var nq = q.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); + yy.Insert.prototype.toJS = function (context, tableid, defcols) { + // console.log('Expression',this); + // if(this.expression.reduced) return 'true'; + // return this.expression.toJS(context, tableid, defcols); + // console.log('Select.toJS', 81, this.queriesidx); + // var s = 'this.queriesdata['+(this.queriesidx-1)+'][0]'; + + var s = 'this.queriesfn[' + (this.queriesidx - 1) + '](this.params,null,' + context + ')'; + // s = '(console.log(this.queriesfn[0]),'+s+')'; + // console.log(this,s); + + return s; + }; + + yy.Insert.prototype.compile = function (databaseid) { + var self = this; + + // Handle ParamValue (anonymous data table) - wrap execution + if (self.into instanceof yy.ParamValue) { + return yy.compileParamValue(self.into.param, 'INSERT', true, databaseid, self, 'into'); } - // console.log(1); - self.values.forEach(function (values) { - var ss = []; + databaseid = self.into.databaseid || databaseid; + var db = alasql.databases[databaseid]; + // console.log(self); + var tableid = self.into.tableid; + var table = db.tables[tableid]; - // s += 'db.tables[\''+tableid+'\'].data.push({'; + if (!table) { + throw "Table '" + tableid + "' could not be found"; + } - // s += ''; - if (self.columns) { - // Validate that we have the right number of values for the columns - if (values.length !== self.columns.length) { - throw new Error( - createValueCountMismatchError(values.length, self.columns.length, 'columns') - ); - } - self.columns.forEach(function (col, idx) { - //console.log(db.tables, tableid, table); - // ss.push(col.columnid +':'+ self.values[idx].value.toString()); - // console.log(rec[f.name.value]); - // if(rec[f.name.value] == "NULL") rec[f.name.value] = undefined; - - // if(table.xflds[f.name.value].dbtypeid == "INT") rec[f.name.value] = +rec[f.name.value]|0; - // else if(table.xflds[f.name.value].dbtypeid == "FLOAT") rec[f.name.value] = +rec[f.name.value]; - var q = "'" + col.columnid + "':"; - if (table.xcolumns && table.xcolumns[col.columnid]) { - if ( - ['INT', 'FLOAT', 'NUMBER', 'MONEY'].indexOf(table.xcolumns[col.columnid].dbtypeid) >= - 0 - ) { - //q += '' - q += '(x=' + values[idx].toJS() + ',x==undefined?undefined:+x)'; - } else if (alasql.fn[table.xcolumns[col.columnid].dbtypeid]) { - q += '(new ' + table.xcolumns[col.columnid].dbtypeid + '('; - q += values[idx].toJS(); - q += '))'; - } else { - q += values[idx].toJS(); - } - } else { - q += values[idx].toJS(); - } - ss.push(q); + // Helper function to create error message for value/column count mismatch + var createValueCountMismatchError = function (valueCount, columnCount, columnType) { + return ( + `The number of values (${valueCount}) does not match the number of ${columnType} (${columnCount}). ` + + 'If using a subquery, use INSERT INTO ... SELECT instead of INSERT INTO ... VALUES (SELECT ...)' + ); + }; + + // Check, if this dirty flag is required + var s = ''; + var sw = ''; + var s = "db.tables['" + tableid + "'].dirty=true;"; + // aa = array to accumulate inserted rows (used for OUTPUT clause and concat to table.data) + var s3 = 'var a,aa=[],x;'; + + var s33; + + // INSERT INTO table VALUES + if (this.values) { + if (this.exists) { + this.existsfn = this.exists.map(function (ex) { + var nq = ex.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; }); - } else { - // var table = db.tables[tableid]; - // console.log('table1', db, self); - //console.log(111, table.columns); - //console.log(74,table); - if (Array.isArray(values) && table.columns && table.columns.length > 0) { - // Validate that we have the right number of values for the table columns - if (values.length !== table.columns.length) { + } + if (this.queries) { + this.queriesfn = this.queries.map(function (q) { + var nq = q.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); + } + + // console.log(1); + self.values.forEach(function (values) { + var ss = []; + + // s += 'db.tables[\''+tableid+'\'].data.push({'; + + // s += ''; + if (self.columns) { + // Validate that we have the right number of values for the columns + if (values.length !== self.columns.length) { throw new Error( - createValueCountMismatchError(values.length, table.columns.length, 'table columns') + createValueCountMismatchError(values.length, self.columns.length, 'columns') ); } - table.columns.forEach(function (col, idx) { + self.columns.forEach(function (col, idx) { + //console.log(db.tables, tableid, table); + // ss.push(col.columnid +':'+ self.values[idx].value.toString()); + // console.log(rec[f.name.value]); + // if(rec[f.name.value] == "NULL") rec[f.name.value] = undefined; + + // if(table.xflds[f.name.value].dbtypeid == "INT") rec[f.name.value] = +rec[f.name.value]|0; + // else if(table.xflds[f.name.value].dbtypeid == "FLOAT") rec[f.name.value] = +rec[f.name.value]; var q = "'" + col.columnid + "':"; - // var val = values[idx].toJS(); - - if (['INT', 'FLOAT', 'NUMBER', 'MONEY'].indexOf(col.dbtypeid) >= 0) { - q += '+' + values[idx].toJS(); - } else if (alasql.fn[col.dbtypeid]) { - q += '(new ' + col.dbtypeid + '('; - q += values[idx].toJS(); - q += '))'; + if (table.xcolumns && table.xcolumns[col.columnid]) { + if ( + ['INT', 'FLOAT', 'NUMBER', 'MONEY'].indexOf( + table.xcolumns[col.columnid].dbtypeid + ) >= 0 + ) { + //q += '' + q += '(x=' + values[idx].toJS() + ',x==undefined?undefined:+x)'; + } else if (alasql.fn[table.xcolumns[col.columnid].dbtypeid]) { + q += '(new ' + table.xcolumns[col.columnid].dbtypeid + '('; + q += values[idx].toJS(); + q += '))'; + } else { + q += values[idx].toJS(); + } } else { q += values[idx].toJS(); } - /*/* + ss.push(q); + }); + } else { + // var table = db.tables[tableid]; + // console.log('table1', db, self); + //console.log(111, table.columns); + //console.log(74,table); + if (Array.isArray(values) && table.columns && table.columns.length > 0) { + // Validate that we have the right number of values for the table columns + if (values.length !== table.columns.length) { + throw new Error( + createValueCountMismatchError(values.length, table.columns.length, 'table columns') + ); + } + table.columns.forEach(function (col, idx) { + var q = "'" + col.columnid + "':"; + // var val = values[idx].toJS(); + + if (['INT', 'FLOAT', 'NUMBER', 'MONEY'].indexOf(col.dbtypeid) >= 0) { + q += '+' + values[idx].toJS(); + } else if (alasql.fn[col.dbtypeid]) { + q += '(new ' + col.dbtypeid + '('; + q += values[idx].toJS(); + q += '))'; + } else { + q += values[idx].toJS(); + } + /*/* // if(table.xcolumns && table.xcolumns[col.columnid] && // (table.xcolumns[col.columnid].dbtypeid == "DATE" || // table.xcolumns[col.columnid].dbtypeid == "DATETIME" @@ -199,8 +200,8 @@ yy.Insert.prototype.compile = function (databaseid) { // if(table.xcolumns && table.xcolumns[col.columnid] && table.xcolumns[col.columnid].dbtypeid == "INT") q += '|0'; */ - ss.push(q); - /*/* + ss.push(q); + /*/* // console.log(fld); // TODO: type checking and conversions // rec[fld.fldid] = eval(self.insertExpression[idx].toJS('','')); @@ -211,102 +212,110 @@ yy.Insert.prototype.compile = function (databaseid) { // else if(table.xflds[fld.fldid].dbtypeid == "FLOAT" || table.xflds[fld.fldid].dbtypeid == "MONEY" ) // rec[fld.fldid] = +rec[fld.fldid]; */ - }); - } else { - // console.log(222,values); - // sw = 'var w='+JSONtoJS(values)+';for(var k in w){r[k]=w[k]};'; - sw = JSONtoJS(values); + }); + } else { + // console.log(222,values); + // sw = 'var w='+JSONtoJS(values)+';for(var k in w){r[k]=w[k]};'; + sw = JSONtoJS(values); + } } - } - //console.log(ss); + //console.log(ss); - if (db.tables[tableid].defaultfns) { - ss.unshift(db.tables[tableid].defaultfns); - } - if (sw) { - s += 'a=' + sw + ';'; - } else { - s += 'a={' + ss.join(',') + '};'; - } + if (db.tables[tableid].defaultfns) { + ss.unshift(db.tables[tableid].defaultfns); + } + if (sw) { + s += 'a=' + sw + ';'; + } else { + s += 'a={' + ss.join(',') + '};'; + } - // If this is a class - if (db.tables[tableid].isclass) { - s += "var db=alasql.databases['" + databaseid + "'];"; - s += 'a.$class="' + tableid + '";'; - s += 'a.$id=db.counter++;'; - s += 'db.objects[a.$id]=a;'; - } - // s += 'db.tables[\''+tableid+'\'].insert(r);'; - if (db.tables[tableid].insert) { - s += "var db=alasql.databases['" + databaseid + "'];"; - s += - "var inserted=db.tables['" + - tableid + - "'].insert(a," + - (self.orreplace ? 'true' : 'false') + - ',' + - (self.ignore ? 'true' : 'false') + - ');'; - // Track successful inserts (insert returns false when ignored) - if (self.ignore) { - s += 'if(inserted!==false){'; + // If this is a class + if (db.tables[tableid].isclass) { + s += "var db=alasql.databases['" + databaseid + "'];"; + s += 'a.$class="' + tableid + '";'; + s += 'a.$id=db.counter++;'; + s += 'db.objects[a.$id]=a;'; } - // Also push to aa for OUTPUT clause - if (self.output) { - s += 'aa.push(a);'; - } else if (self.ignore) { - // For ignore mode without output, track successful insertions + // s += 'db.tables[\''+tableid+'\'].insert(r);'; + if (db.tables[tableid].insert) { + s += "var db=alasql.databases['" + databaseid + "'];"; + s += + "var inserted=db.tables['" + + tableid + + "'].insert(a," + + (self.orreplace ? 'true' : 'false') + + ',' + + (self.ignore ? 'true' : 'false') + + ');'; + // Track successful inserts (insert returns false when ignored) + if (self.ignore) { + s += 'if(inserted!==false){'; + } + // Also push to aa for OUTPUT clause + if (self.output) { + s += 'aa.push(a);'; + } else if (self.ignore) { + // For ignore mode without output, track successful insertions + s += 'aa.push(a);'; + } + if (self.ignore) { + s += '}'; + } + } else { s += 'aa.push(a);'; } - if (self.ignore) { - s += '}'; - } - } else { - s += 'aa.push(a);'; - } - }); + }); - s33 = s3 + s; + s33 = s3 + s; - if (db.tables[tableid].insert) { - // s += 'alasql.databases[\''+databaseid+'\'].tables[\''+tableid+'\'].insert(r);'; - } else { - s += - "alasql.databases['" + - databaseid + - "'].tables['" + - tableid + - "'].data=" + - "alasql.databases['" + - databaseid + - "'].tables['" + - tableid + - "'].data.concat(aa);"; - } + if (db.tables[tableid].insert) { + // s += 'alasql.databases[\''+databaseid+'\'].tables[\''+tableid+'\'].insert(r);'; + } else { + s += + "alasql.databases['" + + databaseid + + "'].tables['" + + tableid + + "'].data=" + + "alasql.databases['" + + databaseid + + "'].tables['" + + tableid + + "'].data.concat(aa);"; + } - // Handle OUTPUT clause - if (self.output) { - s += 'var output = [];'; - s += 'for(var i=0;i { - s += 'WHEN '; - if (!m.matched) s += 'NOT '; - s += 'MATCHED '; - if (m.bytarget) s += 'BY TARGET '; - if (m.bysource) s += 'BY SOURCE '; - if (m.expr) s += `AND ${m.expr.toString()} `; - s += 'THEN '; - if (m.action.delete) s += 'DELETE '; - if (m.action.insert) { - s += 'INSERT '; - if (m.action.columns) s += `(${m.action.columns.toString()}) `; - if (m.action.values) s += `VALUES (${m.action.values.toString()}) `; - if (m.action.defaultvalues) s += 'DEFAULT VALUES '; - } - if (m.action.update) { - s += 'UPDATE '; - s += m.action.update.map(u => u.toString()).join(', ') + ' '; - } - }); + this.matches.forEach(m => { + s += 'WHEN '; + if (!m.matched) s += 'NOT '; + s += 'MATCHED '; + if (m.bytarget) s += 'BY TARGET '; + if (m.bysource) s += 'BY SOURCE '; + if (m.expr) s += `AND ${m.expr.toString()} `; + s += 'THEN '; + if (m.action.delete) s += 'DELETE '; + if (m.action.insert) { + s += 'INSERT '; + if (m.action.columns) s += `(${m.action.columns.toString()}) `; + if (m.action.values) s += `VALUES (${m.action.values.toString()}) `; + if (m.action.defaultvalues) s += 'DEFAULT VALUES '; + } + if (m.action.update) { + s += 'UPDATE '; + s += m.action.update.map(u => u.toString()).join(', ') + ' '; + } + }); - return s; -}; + return s; + }; -yy.Merge.prototype.execute = function (databaseid, params, cb) { - var res = 1; + yy.Merge.prototype.execute = function (databaseid, params, cb) { + var res = 1; - if (cb) res = cb(res); - return res; -}; + if (cb) res = cb(res); + return res; + }; } diff --git a/src/statements/print.js b/src/statements/print.js index 2c78a62c0a..0a2f6ee676 100755 --- a/src/statements/print.js +++ b/src/statements/print.js @@ -1,7 +1,7 @@ export default function install(alasql, internal) { var yy = alasql.yy; var JSONtoString = internal.JSONtoString; -/* + /* // // HELP for Alasql.js // Date: 03.11.2014 @@ -9,63 +9,63 @@ export default function install(alasql, internal) { // */ -/* globals: alasql, yy */ + /* globals: alasql, yy */ -/** + /** Print statement @class @param {object} params Initial setup properties */ -/* global alasql, yy */ + /* global alasql, yy */ -yy.Print = function (params) { - return Object.assign(this, params); -}; + yy.Print = function (params) { + return Object.assign(this, params); + }; -/** + /** Generate SQL string @this Print statement object */ -yy.Print.prototype.toString = function () { - var s = 'PRINT'; - if (this.statement) s += ' ' + this.statement.toString(); - return s; -}; + yy.Print.prototype.toString = function () { + var s = 'PRINT'; + if (this.statement) s += ' ' + this.statement.toString(); + return s; + }; -/** + /** Print result of select statement or expression @param {string} databaseid Database identificator @param {object} params Query parameters @param {statement-callback} cb Callback function @this Print statement object */ -yy.Print.prototype.execute = function (databaseid, params, cb) { - // console.log(this.url); - var self = this; - var res = 1; - //console.log(this); - alasql.precompile(this, databaseid, params); /** @todo Change from alasql to this */ + yy.Print.prototype.execute = function (databaseid, params, cb) { + // console.log(this.url); + var self = this; + var res = 1; + //console.log(this); + alasql.precompile(this, databaseid, params); /** @todo Change from alasql to this */ - if (this.exprs && this.exprs.length > 0) { - var rs = this.exprs.map(function (expr) { - // console.log(48748747654, 'var y;return ' + expr.toJS('({})', '', null)); - var exprfn = new Function( - 'params,alasql,p', - 'var y;return ' + expr.toJS('({})', '', null) - ).bind(self); - var r = exprfn(params, alasql); - return JSONtoString(r); - }); - console.log.apply(console, rs); - } else if (this.select) { - var r = this.select.execute(databaseid, params); - console.log(JSONtoString(r)); - } else { - console.log(); - } + if (this.exprs && this.exprs.length > 0) { + var rs = this.exprs.map(function (expr) { + // console.log(48748747654, 'var y;return ' + expr.toJS('({})', '', null)); + var exprfn = new Function( + 'params,alasql,p', + 'var y;return ' + expr.toJS('({})', '', null) + ).bind(self); + var r = exprfn(params, alasql); + return JSONtoString(r); + }); + console.log.apply(console, rs); + } else if (this.select) { + var r = this.select.execute(databaseid, params); + console.log(JSONtoString(r)); + } else { + console.log(); + } - if (cb) res = cb(res); - return res; -}; + if (cb) res = cb(res); + return res; + }; } diff --git a/src/statements/require.js b/src/statements/require.js index 401d6f46c4..102a233be2 100755 --- a/src/statements/require.js +++ b/src/statements/require.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // HELP for Alasql.js // Date: 03.11.2014 @@ -8,69 +8,69 @@ export default function install(alasql) { // */ -/* global alasql, yy */ + /* global alasql, yy */ -yy.Require = function (params) { - return Object.assign(this, params); -}; -yy.Require.prototype.toString = function () { - var s = 'REQUIRE'; - if (this.paths && this.paths.length > 0) { - s += this.paths - .map(function (path) { - return path.toString(); - }) - .join(','); - } - if (this.plugins && this.plugins.length > 0) { - s += this.plugins - .map(function (plugin) { - return plugin.toUpperCase(); - }) - .join(','); - } - return s; -}; + yy.Require = function (params) { + return Object.assign(this, params); + }; + yy.Require.prototype.toString = function () { + var s = 'REQUIRE'; + if (this.paths && this.paths.length > 0) { + s += this.paths + .map(function (path) { + return path.toString(); + }) + .join(','); + } + if (this.plugins && this.plugins.length > 0) { + s += this.plugins + .map(function (plugin) { + return plugin.toUpperCase(); + }) + .join(','); + } + return s; + }; -/** + /** Attach plug-in for Alasql */ -yy.Require.prototype.execute = function (databaseid, params, cb) { - var self = this; - var res = 0; - var ss = ''; - if (this.paths && this.paths.length > 0) { - this.paths.forEach(function (path) { - alasql.utils.loadFile(path.value, !!cb, function (data) { - res++; - ss += data; - if (res < self.paths.length) return; - new Function('params,alasql', ss)(params, alasql); - if (cb) res = cb(res); + yy.Require.prototype.execute = function (databaseid, params, cb) { + var self = this; + var res = 0; + var ss = ''; + if (this.paths && this.paths.length > 0) { + this.paths.forEach(function (path) { + alasql.utils.loadFile(path.value, !!cb, function (data) { + res++; + ss += data; + if (res < self.paths.length) return; + new Function('params,alasql', ss)(params, alasql); + if (cb) res = cb(res); + }); }); - }); - } else if (this.plugins && this.plugins.length > 0) { - this.plugins.forEach(function (plugin) { - // If plugin is not loaded already - if (!alasql.plugins[plugin]) { - alasql.utils.loadFile( - alasql.path + '/alasql-' + plugin.toLowerCase() + '.js', - !!cb, - function (data) { - // Execute all plugins at the same time - res++; - ss += data; - if (res < self.plugins.length) return; - new Function('params,alasql', ss)(params, alasql); - alasql.plugins[plugin] = true; // Plugin is loaded - if (cb) res = cb(res); - } - ); - } - }); - } else { - if (cb) res = cb(res); - } - return res; -}; + } else if (this.plugins && this.plugins.length > 0) { + this.plugins.forEach(function (plugin) { + // If plugin is not loaded already + if (!alasql.plugins[plugin]) { + alasql.utils.loadFile( + alasql.path + '/alasql-' + plugin.toLowerCase() + '.js', + !!cb, + function (data) { + // Execute all plugins at the same time + res++; + ss += data; + if (res < self.plugins.length) return; + new Function('params,alasql', ss)(params, alasql); + alasql.plugins[plugin] = true; // Plugin is loaded + if (cb) res = cb(res); + } + ); + } + }); + } else { + if (cb) res = cb(res); + } + return res; + }; } diff --git a/src/statements/set.js b/src/statements/set.js index 686cc999e8..c061113694 100755 --- a/src/statements/set.js +++ b/src/statements/set.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // SET for Alasql.js // Date: 01.12.2014 @@ -8,78 +8,78 @@ export default function install(alasql) { // */ -yy.SetVariable = function (params) { - return Object.assign(this, params); -}; -yy.SetVariable.prototype.toString = function () { - var s = 'SET '; - if (typeof this.value != 'undefined') - s += this.variable.toUpperCase() + ' ' + (this.value ? 'ON' : 'OFF'); - if (this.expression) s += this.method + this.variable + ' = ' + this.expression.toString(); - return s; -}; + yy.SetVariable = function (params) { + return Object.assign(this, params); + }; + yy.SetVariable.prototype.toString = function () { + var s = 'SET '; + if (typeof this.value != 'undefined') + s += this.variable.toUpperCase() + ' ' + (this.value ? 'ON' : 'OFF'); + if (this.expression) s += this.method + this.variable + ' = ' + this.expression.toString(); + return s; + }; -yy.SetVariable.prototype.execute = function (databaseid, params, cb) { - if (typeof this.value !== 'undefined') { - let val = this.value; - if (val === 'ON') val = true; - else if (val === 'OFF') val = false; - alasql.options[this.variable] = val; - } else if (this.expression) { - if (this.exists) { - this.existsfn = this.exists.map(ex => { - let nq = ex.compile(databaseid); - if (nq.query && !nq.query.modifier) nq.query.modifier = 'RECORDSET'; - return nq; - }); - } - if (this.queries) { - this.queriesfn = this.queries.map(q => { - let nq = q.compile(databaseid); - if (nq.query && !nq.query.modifier) nq.query.modifier = 'RECORDSET'; - return nq; - }); - } - - let res = new Function( - 'params, alasql', - 'return ' + this.expression.toJS('({})', '', null) - ).bind(this)(params, alasql); + yy.SetVariable.prototype.execute = function (databaseid, params, cb) { + if (typeof this.value !== 'undefined') { + let val = this.value; + if (val === 'ON') val = true; + else if (val === 'OFF') val = false; + alasql.options[this.variable] = val; + } else if (this.expression) { + if (this.exists) { + this.existsfn = this.exists.map(ex => { + let nq = ex.compile(databaseid); + if (nq.query && !nq.query.modifier) nq.query.modifier = 'RECORDSET'; + return nq; + }); + } + if (this.queries) { + this.queriesfn = this.queries.map(q => { + let nq = q.compile(databaseid); + if (nq.query && !nq.query.modifier) nq.query.modifier = 'RECORDSET'; + return nq; + }); + } - if (alasql.declares[this.variable]) { - res = alasql.stdfn.CONVERT(res, alasql.declares[this.variable]); - } + let res = new Function( + 'params, alasql', + 'return ' + this.expression.toJS('({})', '', null) + ).bind(this)(params, alasql); - if (this.props && this.props.length > 0) { - let fs; - if (this.method === '@') { - fs = `alasql.vars['${this.variable}']`; - } else { - fs = `params['${this.variable}']`; + if (alasql.declares[this.variable]) { + res = alasql.stdfn.CONVERT(res, alasql.declares[this.variable]); } - this.props.forEach(prop => { - if (typeof prop === 'string') { - fs += `['${prop}']`; - } else if (typeof prop === 'number') { - fs += `[${prop}]`; + + if (this.props && this.props.length > 0) { + let fs; + if (this.method === '@') { + fs = `alasql.vars['${this.variable}']`; } else { - // Assuming prop.toJS() is a method that converts prop to a JavaScript expression. - fs += `[${prop.toJS()}]`; + fs = `params['${this.variable}']`; } - }); + this.props.forEach(prop => { + if (typeof prop === 'string') { + fs += `['${prop}']`; + } else if (typeof prop === 'number') { + fs += `[${prop}]`; + } else { + // Assuming prop.toJS() is a method that converts prop to a JavaScript expression. + fs += `[${prop.toJS()}]`; + } + }); - new Function('value, params, alasql', `${fs} = value`)(res, params, alasql); - } else { - if (this.method === '@') { - alasql.vars[this.variable] = res; + new Function('value, params, alasql', `${fs} = value`)(res, params, alasql); } else { - params[this.variable] = res; + if (this.method === '@') { + alasql.vars[this.variable] = res; + } else { + params[this.variable] = res; + } } } - } - let result = 1; - if (cb) result = cb(result); - return result; -}; + let result = 1; + if (cb) result = cb(result); + return result; + }; } diff --git a/src/statements/show.js b/src/statements/show.js index 28f7c0484b..73c57c9f81 100755 --- a/src/statements/show.js +++ b/src/statements/show.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // SHOW for Alasql.js // Date: 19.11.2014 @@ -8,148 +8,148 @@ export default function install(alasql) { // */ -yy.ShowDatabases = function (params) { - return Object.assign(this, params); -}; -yy.ShowDatabases.prototype.toString = function () { - var s = 'SHOW DATABASES'; - if (this.like) s += 'LIKE ' + this.like.toString(); - return s; -}; -yy.ShowDatabases.prototype.execute = function (databaseid, params, cb) { - if (this.engineid) { - return alasql.engines[this.engineid].showDatabases(this.like, cb); - } else { + yy.ShowDatabases = function (params) { + return Object.assign(this, params); + }; + yy.ShowDatabases.prototype.toString = function () { + var s = 'SHOW DATABASES'; + if (this.like) s += 'LIKE ' + this.like.toString(); + return s; + }; + yy.ShowDatabases.prototype.execute = function (databaseid, params, cb) { + if (this.engineid) { + return alasql.engines[this.engineid].showDatabases(this.like, cb); + } else { + var self = this; + var res = []; + for (var dbid in alasql.databases) { + res.push({databaseid: dbid}); + } + if (self.like && res && res.length > 0) { + res = res.filter(function (d) { + // return d.databaseid.match(new RegExp((self.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g')); + return alasql.utils.like(self.like.value, d.databaseid); + }); + } + if (cb) cb(res); + return res; + } + }; + + yy.ShowTables = function (params) { + return Object.assign(this, params); + }; + yy.ShowTables.prototype.toString = function () { + var s = 'SHOW TABLES'; + if (this.databaseid) s += ' FROM ' + this.databaseid; + if (this.like) s += ' LIKE ' + this.like.toString(); + return s; + }; + yy.ShowTables.prototype.execute = function (databaseid, params, cb) { + var db = alasql.databases[this.databaseid || databaseid]; + var self = this; var res = []; - for (var dbid in alasql.databases) { - res.push({databaseid: dbid}); + for (var tableid in db.tables) { + res.push({tableid: tableid}); } if (self.like && res && res.length > 0) { res = res.filter(function (d) { - // return d.databaseid.match(new RegExp((self.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g')); - return alasql.utils.like(self.like.value, d.databaseid); + //return d.tableid.match(new RegExp((self.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g')); + return alasql.utils.like(self.like.value, d.tableid); }); } if (cb) cb(res); return res; - } -}; + }; -yy.ShowTables = function (params) { - return Object.assign(this, params); -}; -yy.ShowTables.prototype.toString = function () { - var s = 'SHOW TABLES'; - if (this.databaseid) s += ' FROM ' + this.databaseid; - if (this.like) s += ' LIKE ' + this.like.toString(); - return s; -}; -yy.ShowTables.prototype.execute = function (databaseid, params, cb) { - var db = alasql.databases[this.databaseid || databaseid]; + yy.ShowColumns = function (params) { + return Object.assign(this, params); + }; + yy.ShowColumns.prototype.toString = function () { + var s = 'SHOW COLUMNS'; + if (this.table.tableid) s += ' FROM ' + this.table.tableid; + if (this.databaseid) s += ' FROM ' + this.databaseid; + return s; + }; - var self = this; - var res = []; - for (var tableid in db.tables) { - res.push({tableid: tableid}); - } - if (self.like && res && res.length > 0) { - res = res.filter(function (d) { - //return d.tableid.match(new RegExp((self.like.value||'').replace(/\%/g,'.*').replace(/\?|_/g,'.'),'g')); - return alasql.utils.like(self.like.value, d.tableid); - }); - } - if (cb) cb(res); - return res; -}; + yy.ShowColumns.prototype.execute = function (databaseid, params, cb) { + var dbid = this.table.databaseid || this.databaseid || databaseid; + var db = alasql.databases[dbid]; + // A table in a storage engine may not be loaded yet (#1107) + if (alasql.options.autocommit && db.engineid && db.tables[this.table.tableid]) { + var engine = alasql.engines[db.engineid]; + if (engine.loadTableData) engine.loadTableData(dbid, this.table.tableid); + } + var table = db.tables[this.table.tableid]; -yy.ShowColumns = function (params) { - return Object.assign(this, params); -}; -yy.ShowColumns.prototype.toString = function () { - var s = 'SHOW COLUMNS'; - if (this.table.tableid) s += ' FROM ' + this.table.tableid; - if (this.databaseid) s += ' FROM ' + this.databaseid; - return s; -}; + if (table && table.columns) { + var res = table.columns.map(function (col) { + return { + columnid: col.columnid, + dbtypeid: col.dbtypeid, + dbsize: col.dbsize, + }; + }); + if (cb) cb(res); + return res; + } else { + if (cb) cb([]); + return []; + } + }; -yy.ShowColumns.prototype.execute = function (databaseid, params, cb) { - var dbid = this.table.databaseid || this.databaseid || databaseid; - var db = alasql.databases[dbid]; - // A table in a storage engine may not be loaded yet (#1107) - if (alasql.options.autocommit && db.engineid && db.tables[this.table.tableid]) { - var engine = alasql.engines[db.engineid]; - if (engine.loadTableData) engine.loadTableData(dbid, this.table.tableid); - } - var table = db.tables[this.table.tableid]; + yy.ShowIndex = function (params) { + return Object.assign(this, params); + }; + yy.ShowIndex.prototype.toString = function () { + var s = 'SHOW INDEX'; + if (this.table.tableid) s += ' FROM ' + this.table.tableid; + if (this.databaseid) s += ' FROM ' + this.databaseid; + return s; + }; + yy.ShowIndex.prototype.execute = function (databaseid, params, cb) { + var db = alasql.databases[this.table.databaseid || this.databaseid || databaseid]; + var table = db.tables[this.table.tableid]; + var res = []; + if (table && table.indices) { + for (var ind in table.indices) { + res.push({hh: ind, len: Object.keys(table.indices[ind]).length}); + } + } - if (table && table.columns) { - var res = table.columns.map(function (col) { - return { - columnid: col.columnid, - dbtypeid: col.dbtypeid, - dbsize: col.dbsize, - }; - }); if (cb) cb(res); return res; - } else { - if (cb) cb([]); - return []; - } -}; - -yy.ShowIndex = function (params) { - return Object.assign(this, params); -}; -yy.ShowIndex.prototype.toString = function () { - var s = 'SHOW INDEX'; - if (this.table.tableid) s += ' FROM ' + this.table.tableid; - if (this.databaseid) s += ' FROM ' + this.databaseid; - return s; -}; -yy.ShowIndex.prototype.execute = function (databaseid, params, cb) { - var db = alasql.databases[this.table.databaseid || this.databaseid || databaseid]; - var table = db.tables[this.table.tableid]; - var res = []; - if (table && table.indices) { - for (var ind in table.indices) { - res.push({hh: ind, len: Object.keys(table.indices[ind]).length}); - } - } + }; - if (cb) cb(res); - return res; -}; - -yy.ShowCreateTable = function (params) { - return Object.assign(this, params); -}; -yy.ShowCreateTable.prototype.toString = function () { - var s = 'SHOW CREATE TABLE ' + this.table.tableid; - if (this.databaseid) s += ' FROM ' + this.databaseid; - return s; -}; -yy.ShowCreateTable.prototype.execute = function (databaseid) { - var db = alasql.databases[this.table.databaseid || this.databaseid || databaseid]; - var table = db.tables[this.table.tableid]; - if (table) { - var s = 'CREATE TABLE ' + this.table.tableid + ' ('; - var ss = []; - if (table.columns) { - table.columns.forEach(function (col) { - var a = col.columnid + ' ' + col.dbtypeid; - if (col.dbsize) a += '(' + col.dbsize + ')'; - if (col.primarykey) a += ' PRIMARY KEY'; - // TODO extend - ss.push(a); - }); - s += ss.join(', '); - } - s += ')'; + yy.ShowCreateTable = function (params) { + return Object.assign(this, params); + }; + yy.ShowCreateTable.prototype.toString = function () { + var s = 'SHOW CREATE TABLE ' + this.table.tableid; + if (this.databaseid) s += ' FROM ' + this.databaseid; return s; - } else { - throw new Error('There is no such table "' + this.table.tableid + '"'); - } -}; + }; + yy.ShowCreateTable.prototype.execute = function (databaseid) { + var db = alasql.databases[this.table.databaseid || this.databaseid || databaseid]; + var table = db.tables[this.table.tableid]; + if (table) { + var s = 'CREATE TABLE ' + this.table.tableid + ' ('; + var ss = []; + if (table.columns) { + table.columns.forEach(function (col) { + var a = col.columnid + ' ' + col.dbtypeid; + if (col.dbsize) a += '(' + col.dbsize + ')'; + if (col.primarykey) a += ' PRIMARY KEY'; + // TODO extend + ss.push(a); + }); + s += ss.join(', '); + } + s += ')'; + return s; + } else { + throw new Error('There is no such table "' + this.table.tableid + '"'); + } + }; } diff --git a/src/statements/source.js b/src/statements/source.js index 07e252c0d4..18392ff01c 100755 --- a/src/statements/source.js +++ b/src/statements/source.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // HELP for Alasql.js // Date: 03.11.2014 @@ -8,30 +8,30 @@ export default function install(alasql) { // */ -yy.Source = function (params) { - return Object.assign(this, params); -}; -yy.Source.prototype.toString = function () { - var s = 'SOURCE'; - if (this.url) s += " '" + this.url + " '"; - return s; -}; + yy.Source = function (params) { + return Object.assign(this, params); + }; + yy.Source.prototype.toString = function () { + var s = 'SOURCE'; + if (this.url) s += " '" + this.url + " '"; + return s; + }; -// SOURCE FILE -yy.Source.prototype.execute = function (databaseid, params, cb) { - var res; - alasql.utils.loadFile( - this.url, - !!cb, - function (data) { - res = alasql(data); - if (cb) res = cb(res); - return res; - }, - function (err) { - throw err; - } - ); - return res; -}; + // SOURCE FILE + yy.Source.prototype.execute = function (databaseid, params, cb) { + var res; + alasql.utils.loadFile( + this.url, + !!cb, + function (data) { + res = alasql(data); + if (cb) res = cb(res); + return res; + }, + function (err) { + throw err; + } + ); + return res; + }; } diff --git a/src/statements/trigger.js b/src/statements/trigger.js index 93cca7cdb0..2da2faa7ce 100755 --- a/src/statements/trigger.js +++ b/src/statements/trigger.js @@ -1,75 +1,75 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // TRIGGER for Alasql.js // Date: 29.12.2015 // */ -yy.CreateTrigger = function (params) { - return Object.assign(this, params); -}; -yy.CreateTrigger.prototype.toString = function () { - var s = 'CREATE TRIGGER ' + this.trigger + ' '; - if (this.when) s += this.when + ' '; - s += this.action + ' ON '; - if (this.table.databaseid) s += this.table.databaseid + '.'; - s += this.table.tableid + ' '; - s += this.statement.toString(); - return s; -}; - -const validTriggers = [ - 'beforeinsert', - 'afterinsert', - 'insteadofinsert', - 'beforedelete', - 'afterdelete', - 'insteadofdelete', - 'beforeupdate', - 'afterupdate', - 'insteadofupdate', -]; - -yy.CreateTrigger.prototype.execute = function (databaseid, params, cb) { - let res = 1; // No tables removed - const triggerid = this.trigger; - databaseid = this.table.databaseid || databaseid; - const db = alasql.databases[databaseid]; - const {tableid} = this.table; - - const trigger = { - action: this.action, - when: this.when, - statement: this.statement, - funcid: this.funcid, - tableid, + yy.CreateTrigger = function (params) { + return Object.assign(this, params); + }; + yy.CreateTrigger.prototype.toString = function () { + var s = 'CREATE TRIGGER ' + this.trigger + ' '; + if (this.when) s += this.when + ' '; + s += this.action + ' ON '; + if (this.table.databaseid) s += this.table.databaseid + '.'; + s += this.table.tableid + ' '; + s += this.statement.toString(); + return s; + }; + + const validTriggers = [ + 'beforeinsert', + 'afterinsert', + 'insteadofinsert', + 'beforedelete', + 'afterdelete', + 'insteadofdelete', + 'beforeupdate', + 'afterupdate', + 'insteadofupdate', + ]; + + yy.CreateTrigger.prototype.execute = function (databaseid, params, cb) { + let res = 1; // No tables removed + const triggerid = this.trigger; + databaseid = this.table.databaseid || databaseid; + const db = alasql.databases[databaseid]; + const {tableid} = this.table; + + const trigger = { + action: this.action, + when: this.when, + statement: this.statement, + funcid: this.funcid, + tableid, + }; + + db.triggers[triggerid] = trigger; + const actionKey = `${this.when}${this.action}`.toLowerCase(); + + if (validTriggers.includes(actionKey)) { + // Ensure the existence of db.tables[tableid] and db.tables[tableid][actionKey] + db.tables[tableid] = db.tables[tableid] || {}; + db.tables[tableid][actionKey] = db.tables[tableid][actionKey] || {}; + db.tables[tableid][actionKey][triggerid] = trigger; + } + + if (cb) res = cb(res); + return res; }; - db.triggers[triggerid] = trigger; - const actionKey = `${this.when}${this.action}`.toLowerCase(); - - if (validTriggers.includes(actionKey)) { - // Ensure the existence of db.tables[tableid] and db.tables[tableid][actionKey] - db.tables[tableid] = db.tables[tableid] || {}; - db.tables[tableid][actionKey] = db.tables[tableid][actionKey] || {}; - db.tables[tableid][actionKey][triggerid] = trigger; - } - - if (cb) res = cb(res); - return res; -}; - -yy.DropTrigger = function (params) { - return Object.assign(this, params); -}; -yy.DropTrigger.prototype.toString = function () { - var s = 'DROP TRIGGER ' + this.trigger; - return s; -}; - -/** + yy.DropTrigger = function (params) { + return Object.assign(this, params); + }; + yy.DropTrigger.prototype.toString = function () { + var s = 'DROP TRIGGER ' + this.trigger; + return s; + }; + + /** Drop trigger @param {string} databaseid Database id @param {object} params Parameters @@ -78,60 +78,60 @@ yy.DropTrigger.prototype.toString = function () { @example DROP TRIGGER one; */ -yy.DropTrigger.prototype.execute = function (databaseid, params, cb) { - let res = 0; // No tables removed - const db = alasql.databases[databaseid]; - const triggerid = this.trigger; - - // Get the trigger - const trigger = db.triggers[triggerid]; - - // If the trigger exists - if (trigger) { - const {tableid} = trigger; - - if (tableid) { - res = 1; - - // Delete the trigger from all trigger points - validTriggers.forEach(point => { - delete db.tables[tableid][point][triggerid]; - }); - - // Finally, delete the trigger itself - delete db.triggers[triggerid]; + yy.DropTrigger.prototype.execute = function (databaseid, params, cb) { + let res = 0; // No tables removed + const db = alasql.databases[databaseid]; + const triggerid = this.trigger; + + // Get the trigger + const trigger = db.triggers[triggerid]; + + // If the trigger exists + if (trigger) { + const {tableid} = trigger; + + if (tableid) { + res = 1; + + // Delete the trigger from all trigger points + validTriggers.forEach(point => { + delete db.tables[tableid][point][triggerid]; + }); + + // Finally, delete the trigger itself + delete db.triggers[triggerid]; + } else { + throw new Error('Trigger Table not found'); + } } else { - throw new Error('Trigger Table not found'); + throw new Error('Trigger not found'); } - } else { - throw new Error('Trigger not found'); - } - - if (cb) res = cb(res); - return res; -}; - -/** - * Helper function to execute a trigger with proper parameter passing - * @param {object} trigger - The trigger object - * @param {string} databaseid - Database ID for statement execution - * @param {...any} args - Arguments to pass to the trigger function - * @return {*} Result from trigger execution (typically boolean or undefined) - */ -alasql.executeTrigger = function (trigger, databaseid, ...args) { - if (!trigger) return; - - if (trigger.funcid) { - // Direct function ID (older syntax: CREATE TRIGGER ... tablename funcname) - return alasql.fn[trigger.funcid](...args); - } else if (trigger.statement) { - // CALL funcname() without arguments gets the row data, like the older syntax - const call = trigger.statement.expression; - if (call && call.funcid && !call.args?.length) { - return alasql.fn[call.funcid](...args); + + if (cb) res = cb(res); + return res; + }; + + /** + * Helper function to execute a trigger with proper parameter passing + * @param {object} trigger - The trigger object + * @param {string} databaseid - Database ID for statement execution + * @param {...any} args - Arguments to pass to the trigger function + * @return {*} Result from trigger execution (typically boolean or undefined) + */ + alasql.executeTrigger = function (trigger, databaseid, ...args) { + if (!trigger) return; + + if (trigger.funcid) { + // Direct function ID (older syntax: CREATE TRIGGER ... tablename funcname) + return alasql.fn[trigger.funcid](...args); + } else if (trigger.statement) { + // CALL funcname() without arguments gets the row data, like the older syntax + const call = trigger.statement.expression; + if (call && call.funcid && !call.args?.length) { + return alasql.fn[call.funcid](...args); + } + // Otherwise run the statement as written, with its own arguments + return trigger.statement.execute(databaseid); } - // Otherwise run the statement as written, with its own arguments - return trigger.statement.execute(databaseid); - } -}; + }; } diff --git a/src/statements/update.js b/src/statements/update.js index de398dd65a..072b72437d 100755 --- a/src/statements/update.js +++ b/src/statements/update.js @@ -2,7 +2,7 @@ import {cloneDeep} from '../utils/helpers.js'; export default function install(alasql) { var yy = alasql.yy; -/* + /* // // UPDATE for Alasql.js // Date: 03.11.2014 @@ -10,174 +10,175 @@ export default function install(alasql) { // */ -/* global yy alasql */ - -yy.Update = function (params) { - return Object.assign(this, params); -}; -yy.Update.prototype.toString = function () { - var s = 'UPDATE ' + this.table.toString(); - if (this.columns) s += ' SET ' + this.columns.toString(); - if (this.where) s += ' WHERE ' + this.where.toString(); - if (this.output) { - s += ' OUTPUT '; - s += this.output.columns.map(col => col.toString()).join(', '); - if (this.output.intovar) { - s += ' INTO ' + this.output.method + this.output.intovar; - } else if (this.output.intotable) { - s += ' INTO ' + this.output.intotable.toString(); - if (this.output.intocolumns) { - s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + /* global yy alasql */ + + yy.Update = function (params) { + return Object.assign(this, params); + }; + yy.Update.prototype.toString = function () { + var s = 'UPDATE ' + this.table.toString(); + if (this.columns) s += ' SET ' + this.columns.toString(); + if (this.where) s += ' WHERE ' + this.where.toString(); + if (this.output) { + s += ' OUTPUT '; + s += this.output.columns.map(col => col.toString()).join(', '); + if (this.output.intovar) { + s += ' INTO ' + this.output.method + this.output.intovar; + } else if (this.output.intotable) { + s += ' INTO ' + this.output.intotable.toString(); + if (this.output.intocolumns) { + s += '(' + this.output.intocolumns.map(col => col.toString()).join(', ') + ')'; + } } } - } - return s; -}; - -yy.SetColumn = function (params) { - return Object.assign(this, params); -}; -yy.SetColumn.prototype.toString = function () { - return this.column.toString() + '=' + this.expression.toString(); -}; - -yy.Update.prototype.compile = function (databaseid) { - var self = this; - // console.log(this); - - // Handle ParamValue (anonymous data table) - wrap execution - if (this.table instanceof yy.ParamValue) { - return yy.compileParamValue(this.table.param, 'UPDATE', false, databaseid, self, 'table'); - } - - databaseid = this.table.databaseid || databaseid; - var tableid = this.table.tableid; - - if (this.where) { - if (this.exists) { - this.existsfn = this.exists.map(function (ex) { - var nq = ex.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } - if (this.queries) { - this.queriesfn = this.queries.map(function (q) { - var nq = q.compile(databaseid); - nq.query.modifier = 'RECORDSET'; - return nq; - }); - } + return s; + }; - // console.log(73625, this.where.toJS('r', '')); - var wherefn = new Function('r,params,alasql', 'var y;return ' + this.where.toJS('r', '')).bind( - this - ); - } - - // Construct update function - var s = alasql.databases[databaseid].tables[tableid].onupdatefns || ''; - s += ';'; - this.columns.forEach(function (col) { - s += "r['" + col.column.columnid + "']=" + col.expression.toJS('r', '') + ';'; - }); - // console.log(423623, s); - var assignfn = new Function('r,params,alasql', 'var y;' + s); - - var statement = function (params, cb) { - var db = alasql.databases[databaseid]; - self.params = params; - - // console.log(db.engineid); - // console.log(db.engineid && alasql.engines[db.engineid].updateTable); - if (db.engineid && alasql.engines[db.engineid].updateTable) { - // console.log('updateTable'); - return alasql.engines[db.engineid].updateTable( - databaseid, - tableid, - assignfn, - wherefn, - params, - cb - ); - } + yy.SetColumn = function (params) { + return Object.assign(this, params); + }; + yy.SetColumn.prototype.toString = function () { + return this.column.toString() + '=' + this.expression.toString(); + }; - if (alasql.options.autocommit && db.engineid) { - alasql.engines[db.engineid].loadTableData(databaseid, tableid); - } + yy.Update.prototype.compile = function (databaseid) { + var self = this; + // console.log(this); - var table = db.tables[tableid]; - if (!table) { - throw new Error("Table '" + tableid + "' not exists"); + // Handle ParamValue (anonymous data table) - wrap execution + if (this.table instanceof yy.ParamValue) { + return yy.compileParamValue(this.table.param, 'UPDATE', false, databaseid, self, 'table'); } - // table.dirty = true; - var numrows = 0; - var updatedRows = []; - for (var i = 0, ilen = table.data.length; i < ilen; i++) { - if (!wherefn || wherefn(table.data[i], params, alasql)) { - // Track row state for OUTPUT clause (DELETED.*) - var oldRow = self.output ? cloneDeep(table.data[i]) : null; - - if (table.update) { - table.update(assignfn, i, params); - } else { - assignfn(table.data[i], params, alasql); - } - // Track updated row for OUTPUT clause (INSERTED.*) - if (self.output) { - updatedRows.push({ - deleted: oldRow, - inserted: cloneDeep(table.data[i]), - }); - } + databaseid = this.table.databaseid || databaseid; + var tableid = this.table.tableid; - numrows++; + if (this.where) { + if (this.exists) { + this.existsfn = this.exists.map(function (ex) { + var nq = ex.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); + } + if (this.queries) { + this.queriesfn = this.queries.map(function (q) { + var nq = q.compile(databaseid); + nq.query.modifier = 'RECORDSET'; + return nq; + }); } - } - if (alasql.options.autocommit && db.engineid) { - alasql.engines[db.engineid].saveTableData(databaseid, tableid); + // console.log(73625, this.where.toJS('r', '')); + var wherefn = new Function( + 'r,params,alasql', + 'var y;return ' + this.where.toJS('r', '') + ).bind(this); } - var res = numrows; - - // Handle OUTPUT clause - if (self.output) { - var output = []; - for (var i = 0; i < updatedRows.length; i++) { - var deleted = updatedRows[i].deleted; - var inserted = updatedRows[i].inserted; - var outputRow = {}; - self.output.columns.forEach(function (col) { - if (col.columnid === '*') { - // For *, use INSERTED values - for (var key in inserted) { - outputRow[key] = inserted[key]; - } + // Construct update function + var s = alasql.databases[databaseid].tables[tableid].onupdatefns || ''; + s += ';'; + this.columns.forEach(function (col) { + s += "r['" + col.column.columnid + "']=" + col.expression.toJS('r', '') + ';'; + }); + // console.log(423623, s); + var assignfn = new Function('r,params,alasql', 'var y;' + s); + + var statement = function (params, cb) { + var db = alasql.databases[databaseid]; + self.params = params; + + // console.log(db.engineid); + // console.log(db.engineid && alasql.engines[db.engineid].updateTable); + if (db.engineid && alasql.engines[db.engineid].updateTable) { + // console.log('updateTable'); + return alasql.engines[db.engineid].updateTable( + databaseid, + tableid, + assignfn, + wherefn, + params, + cb + ); + } + + if (alasql.options.autocommit && db.engineid) { + alasql.engines[db.engineid].loadTableData(databaseid, tableid); + } + + var table = db.tables[tableid]; + if (!table) { + throw new Error("Table '" + tableid + "' not exists"); + } + // table.dirty = true; + var numrows = 0; + var updatedRows = []; + for (var i = 0, ilen = table.data.length; i < ilen; i++) { + if (!wherefn || wherefn(table.data[i], params, alasql)) { + // Track row state for OUTPUT clause (DELETED.*) + var oldRow = self.output ? cloneDeep(table.data[i]) : null; + + if (table.update) { + table.update(assignfn, i, params); } else { - var colname = col.as || col.columnid; - // Check tableid to determine which version to use - if (col.tableid === 'DELETED') { - outputRow[colname] = deleted[col.columnid]; + assignfn(table.data[i], params, alasql); + } + + // Track updated row for OUTPUT clause (INSERTED.*) + if (self.output) { + updatedRows.push({ + deleted: oldRow, + inserted: cloneDeep(table.data[i]), + }); + } + + numrows++; + } + } + + if (alasql.options.autocommit && db.engineid) { + alasql.engines[db.engineid].saveTableData(databaseid, tableid); + } + + var res = numrows; + + // Handle OUTPUT clause + if (self.output) { + var output = []; + for (var i = 0; i < updatedRows.length; i++) { + var deleted = updatedRows[i].deleted; + var inserted = updatedRows[i].inserted; + var outputRow = {}; + self.output.columns.forEach(function (col) { + if (col.columnid === '*') { + // For *, use INSERTED values + for (var key in inserted) { + outputRow[key] = inserted[key]; + } } else { - // Default to INSERTED - outputRow[colname] = inserted[col.columnid]; + var colname = col.as || col.columnid; + // Check tableid to determine which version to use + if (col.tableid === 'DELETED') { + outputRow[colname] = deleted[col.columnid]; + } else { + // Default to INSERTED + outputRow[colname] = inserted[col.columnid]; + } } - } - }); - output.push(outputRow); + }); + output.push(outputRow); + } + res = output; } - res = output; - } - if (cb) cb(res); - return res; + if (cb) cb(res); + return res; + }; + return statement; }; - return statement; -}; -yy.Update.prototype.execute = function (databaseid, params, cb) { - return this.compile(databaseid)(params, cb); -}; + yy.Update.prototype.execute = function (databaseid, params, cb) { + return this.compile(databaseid)(params, cb); + }; } diff --git a/src/statements/while.js b/src/statements/while.js index 152a9963a4..b974e95bd7 100755 --- a/src/statements/while.js +++ b/src/statements/while.js @@ -1,6 +1,6 @@ export default function install(alasql) { var yy = alasql.yy; -/* + /* // // WHILE, BREAK, CONTINUE, and BEGIN...END for Alasql.js // Date: 03.11.2014 @@ -8,134 +8,134 @@ export default function install(alasql) { // */ -// Control flow exception types for BREAK and CONTINUE statements. -// A WHILE loop catches them; the message is only seen when there is no loop. -function createControlFlowException(name) { - var Exception = function () { - this.message = name + ' can only be used inside a WHILE loop'; - }; - Exception.prototype = Object.create(Error.prototype); - Exception.prototype.constructor = Exception; - return Exception; -} + // Control flow exception types for BREAK and CONTINUE statements. + // A WHILE loop catches them; the message is only seen when there is no loop. + function createControlFlowException(name) { + var Exception = function () { + this.message = name + ' can only be used inside a WHILE loop'; + }; + Exception.prototype = Object.create(Error.prototype); + Exception.prototype.constructor = Exception; + return Exception; + } -yy.BreakException = createControlFlowException('BREAK'); -yy.ContinueException = createControlFlowException('CONTINUE'); + yy.BreakException = createControlFlowException('BREAK'); + yy.ContinueException = createControlFlowException('CONTINUE'); -yy.While = function (params) { - return Object.assign(this, params); -}; -yy.While.prototype.toString = function () { - var s = 'WHILE '; - s += this.expression.toString(); - s += ' ' + this.loopstat.toString(); - return s; -}; + yy.While = function (params) { + return Object.assign(this, params); + }; + yy.While.prototype.toString = function () { + var s = 'WHILE '; + s += this.expression.toString(); + s += ' ' + this.loopstat.toString(); + return s; + }; -yy.While.prototype.execute = function (databaseid, params, cb) { - var self = this; - var res = []; - // console.log(this.expression.toJS()); - var fn = new Function('params,alasql,p', 'var y;return ' + this.expression.toJS()); - // console.log('cb',!!cb); - if (cb) { - var loop = function () { - setTimeout(function () { - if (!fn(params, alasql)) { - res = cb(res); - return; - } + yy.While.prototype.execute = function (databaseid, params, cb) { + var self = this; + var res = []; + // console.log(this.expression.toJS()); + var fn = new Function('params,alasql,p', 'var y;return ' + this.expression.toJS()); + // console.log('cb',!!cb); + if (cb) { + var loop = function () { + setTimeout(function () { + if (!fn(params, alasql)) { + res = cb(res); + return; + } + try { + self.loopstat.execute(databaseid, params, function (data) { + res.push(data); + loop(); + }); + } catch (err) { + if (err instanceof yy.BreakException) { + res = cb(res); + } else if (err instanceof yy.ContinueException) { + loop(); + } else { + throw err; + } + } + }, 0); + }; + loop(); + } else { + while (fn(params, alasql)) { try { - self.loopstat.execute(databaseid, params, function (data) { - res.push(data); - loop(); - }); + var res1 = self.loopstat.execute(databaseid, params); + res.push(res1); } catch (err) { if (err instanceof yy.BreakException) { - res = cb(res); + break; } else if (err instanceof yy.ContinueException) { - loop(); + continue; } else { throw err; } } - }, 0); - }; - loop(); - } else { - while (fn(params, alasql)) { - 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; -}; + return res; + }; -yy.Break = function (params) { - return Object.assign(this, params); -}; -yy.Break.prototype.toString = function () { - var s = 'BREAK'; - return s; -}; + yy.Break = function (params) { + return Object.assign(this, params); + }; + yy.Break.prototype.toString = function () { + var s = 'BREAK'; + return s; + }; -yy.Break.prototype.execute = function (databaseid, params, cb, scope) { - throw new yy.BreakException(); -}; + yy.Break.prototype.execute = function (databaseid, params, cb, scope) { + throw new yy.BreakException(); + }; -yy.Continue = function (params) { - return Object.assign(this, params); -}; -yy.Continue.prototype.toString = function () { - var s = 'CONTINUE'; - return s; -}; + yy.Continue = function (params) { + return Object.assign(this, params); + }; + yy.Continue.prototype.toString = function () { + var s = 'CONTINUE'; + return s; + }; -yy.Continue.prototype.execute = function (databaseid, params, cb, scope) { - throw new yy.ContinueException(); -}; + yy.Continue.prototype.execute = function (databaseid, params, cb, scope) { + throw new yy.ContinueException(); + }; -yy.BeginEnd = function (params) { - return Object.assign(this, params); -}; -yy.BeginEnd.prototype.toString = function () { - var s = 'BEGIN ' + this.statements.toString() + ' END'; - return s; -}; + yy.BeginEnd = function (params) { + return Object.assign(this, params); + }; + yy.BeginEnd.prototype.toString = function () { + var s = 'BEGIN ' + this.statements.toString() + ' END'; + return s; + }; -yy.BeginEnd.prototype.execute = function (databaseid, params, cb, scope) { - var self = this; - var res = []; + yy.BeginEnd.prototype.execute = function (databaseid, params, cb, scope) { + var self = this; + var 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); + 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; -}; + return res; + }; } diff --git a/src/statements/with.js b/src/statements/with.js index 8bedfaf615..00eb90b22d 100755 --- a/src/statements/with.js +++ b/src/statements/with.js @@ -1,7 +1,7 @@ export default function install(alasql) { var yy = alasql.yy; var Table = alasql.Table; -/* + /* // // WITH SELECT for Alasql.js // Date: 11.01.2015 @@ -9,228 +9,237 @@ export default function install(alasql) { // */ -yy.WithSelect = function (params) { - return Object.assign(this, params); -}; -yy.WithSelect.prototype.toString = function () { - var s = 'WITH '; - s += - this.withs - .map(function (w) { - var colStr = ''; - if (w.columns) { - colStr = - '(' + - w.columns - .map(function (c) { - return c.columnid; - }) - .join(', ') + - ')'; - } - return ( - (w.recursive ? 'RECURSIVE ' : '') + w.name + colStr + ' AS (' + w.select.toString() + ')' - ); - }) - .join(', ') + ' '; - s += this.select.toString(); - return s; -}; - -// Execute a recursive CTE -function executeRecursiveCTE(w, databaseid, params) { - var maxIterations = alasql.options.maxCteIterations || 1000; - var db = alasql.databases[databaseid]; - var tableName = w.name; - - // Create the CTE table - var tb = (db.tables[tableName] = new Table({ - tableid: tableName, - })); - tb.data = []; - - var select = w.select; - var anchorSelect, recursiveSelect; - - // Check if this is a UNION ALL structure - if (select.unionall) { - // The anchor is the first part, recursive is in unionall - anchorSelect = Object.assign({}, select); - delete anchorSelect.unionall; - recursiveSelect = select.unionall; - } else if (select.union) { - // UNION (without ALL) - less common for recursive CTEs but possible - anchorSelect = Object.assign({}, select); - delete anchorSelect.union; - recursiveSelect = select.union; - } else { - // No recursive part, just execute once - tb.data = select.execute(databaseid, params); - if (w.columns) { - tb.data = renameColumns(tb.data, w.columns); - } - return tb.data; - } + yy.WithSelect = function (params) { + return Object.assign(this, params); + }; + yy.WithSelect.prototype.toString = function () { + var s = 'WITH '; + s += + this.withs + .map(function (w) { + var colStr = ''; + if (w.columns) { + colStr = + '(' + + w.columns + .map(function (c) { + return c.columnid; + }) + .join(', ') + + ')'; + } + return ( + (w.recursive ? 'RECURSIVE ' : '') + + w.name + + colStr + + ' AS (' + + w.select.toString() + + ')' + ); + }) + .join(', ') + ' '; + s += this.select.toString(); + return s; + }; - // Determine column names - either from explicit column list or from anchor query - var columnNames; - if (w.columns && w.columns.length > 0) { - columnNames = w.columns.map(function (c) { - return c.columnid; - }); - } + // Execute a recursive CTE + function executeRecursiveCTE(w, databaseid, params) { + var maxIterations = alasql.options.maxCteIterations || 1000; + var db = alasql.databases[databaseid]; + var tableName = w.name; - // Execute anchor query using ALASQL_DETAILS format to get both data and column metadata in one call - var anchorSelectObj = new yy.Select(anchorSelect); - anchorSelectObj.modifier = 'ALASQL_DETAILS'; - var anchorDetails = anchorSelectObj.execute(databaseid, params); + // Create the CTE table + var tb = (db.tables[tableName] = new Table({ + tableid: tableName, + })); + tb.data = []; + + var select = w.select; + var anchorSelect, recursiveSelect; + + // Check if this is a UNION ALL structure + if (select.unionall) { + // The anchor is the first part, recursive is in unionall + anchorSelect = Object.assign({}, select); + delete anchorSelect.unionall; + recursiveSelect = select.unionall; + } else if (select.union) { + // UNION (without ALL) - less common for recursive CTEs but possible + anchorSelect = Object.assign({}, select); + delete anchorSelect.union; + recursiveSelect = select.union; + } else { + // No recursive part, just execute once + tb.data = select.execute(databaseid, params); + if (w.columns) { + tb.data = renameColumns(tb.data, w.columns); + } + return tb.data; + } - // Get anchor column names from the query result - var anchorColumnNames = anchorDetails.columns.map(function (c) { - return c.columnid; - }); + // Determine column names - either from explicit column list or from anchor query + var columnNames; + if (w.columns && w.columns.length > 0) { + columnNames = w.columns.map(function (c) { + return c.columnid; + }); + } - // If explicit column names provided, use them; otherwise use anchor column names - if (!columnNames) { - columnNames = anchorColumnNames; - } + // Execute anchor query using ALASQL_DETAILS format to get both data and column metadata in one call + var anchorSelectObj = new yy.Select(anchorSelect); + anchorSelectObj.modifier = 'ALASQL_DETAILS'; + var anchorDetails = anchorSelectObj.execute(databaseid, params); - // With UNION (not UNION ALL) a row already in the result is not added again, - // so the recursion stops on cycles in the data, as in SQLite - var seen = select.unionall ? null : new Set(); - var addNew = function (rows) { - if (!seen) return rows; - return rows.filter(function (row) { - var key = JSON.stringify( - columnNames.map(function (c) { - return row[c]; - }) - ); - if (seen.has(key)) return false; - seen.add(key); - return true; + // Get anchor column names from the query result + var anchorColumnNames = anchorDetails.columns.map(function (c) { + return c.columnid; }); - }; - // Map anchor data to target column names (handles both object format and column renaming) - var anchorData = addNew(mapColumnsToNames(anchorDetails.data, anchorColumnNames, columnNames)); - tb.data = anchorData.slice(); + // If explicit column names provided, use them; otherwise use anchor column names + if (!columnNames) { + columnNames = anchorColumnNames; + } - // Iterate with the recursive part - var newRows = anchorData; - var allData = anchorData.slice(); - var iteration = 0; + // With UNION (not UNION ALL) a row already in the result is not added again, + // so the recursion stops on cycles in the data, as in SQLite + var seen = select.unionall ? null : new Set(); + var addNew = function (rows) { + if (!seen) return rows; + return rows.filter(function (row) { + var key = JSON.stringify( + columnNames.map(function (c) { + return row[c]; + }) + ); + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + }; - while (newRows.length > 0 && iteration < maxIterations) { - iteration++; + // Map anchor data to target column names (handles both object format and column renaming) + var anchorData = addNew(mapColumnsToNames(anchorDetails.data, anchorColumnNames, columnNames)); + tb.data = anchorData.slice(); - // Recreate the CTE table with only the new rows to avoid query caching issues - delete db.tables[tableName]; - tb = db.tables[tableName] = new Table({ - tableid: tableName, - }); - tb.data = newRows; + // Iterate with the recursive part + var newRows = anchorData; + var allData = anchorData.slice(); + var iteration = 0; - // Execute recursive part using ALASQL_DETAILS format - var recursiveSelectObj = new yy.Select(recursiveSelect); - recursiveSelectObj.modifier = 'ALASQL_DETAILS'; - var recursiveDetails = recursiveSelectObj.execute(databaseid, params); + while (newRows.length > 0 && iteration < maxIterations) { + iteration++; - // Handle empty result - if (!recursiveDetails.data || recursiveDetails.data.length === 0) { - break; - } + // Recreate the CTE table with only the new rows to avoid query caching issues + delete db.tables[tableName]; + tb = db.tables[tableName] = new Table({ + tableid: tableName, + }); + tb.data = newRows; - // Get recursive column names and map to target column names by position - var recursiveColumnNames = recursiveDetails.columns.map(function (c) { - return c.columnid; - }); - var recursiveData = mapColumnsToNames(recursiveDetails.data, recursiveColumnNames, columnNames); + // Execute recursive part using ALASQL_DETAILS format + var recursiveSelectObj = new yy.Select(recursiveSelect); + recursiveSelectObj.modifier = 'ALASQL_DETAILS'; + var recursiveDetails = recursiveSelectObj.execute(databaseid, params); - // Add new rows to the result - newRows = addNew(recursiveData); - if (newRows.length === 0) break; - for (var i = 0; i < newRows.length; i++) { - allData.push(newRows[i]); - } - } + // Handle empty result + if (!recursiveDetails.data || recursiveDetails.data.length === 0) { + break; + } - // Set final table data - tb.data = allData; - return allData; -} + // Get recursive column names and map to target column names by position + var recursiveColumnNames = recursiveDetails.columns.map(function (c) { + return c.columnid; + }); + var recursiveData = mapColumnsToNames( + recursiveDetails.data, + recursiveColumnNames, + columnNames + ); -// Helper function to map data from source column names to target column names by position -function mapColumnsToNames(data, sourceColumns, targetColumns) { - return data.map(function (row) { - var newRow = {}; - for (var i = 0; i < targetColumns.length && i < sourceColumns.length; i++) { - newRow[targetColumns[i]] = row[sourceColumns[i]]; + // Add new rows to the result + newRows = addNew(recursiveData); + if (newRows.length === 0) break; + for (var i = 0; i < newRows.length; i++) { + allData.push(newRows[i]); + } } - return newRow; - }); -} -yy.WithSelect.prototype.execute = function (databaseid, params, cb) { - var self = this; - var savedTables = []; + // Set final table data + tb.data = allData; + return allData; + } - // Restore tables shadowed by the temporary CTE tables - function restoreTables() { - self.withs.forEach(function (w, idx) { - if (savedTables[idx]) alasql.databases[databaseid].tables[w.name] = savedTables[idx]; - else delete alasql.databases[databaseid].tables[w.name]; + // Helper function to map data from source column names to target column names by position + function mapColumnsToNames(data, sourceColumns, targetColumns) { + return data.map(function (row) { + var newRow = {}; + for (var i = 0; i < targetColumns.length && i < sourceColumns.length; i++) { + newRow[targetColumns[i]] = row[sourceColumns[i]]; + } + return newRow; }); } - // Create temporary tables one after another. Each non-recursive CTE is - // executed with a callback, so sources that load data asynchronously - // (like CSV() or XLSX() reading a file) are fully loaded before the - // next CTE or the main select reads the temporary table. - function executeWith(idx) { - if (idx >= self.withs.length) { - return self.select.execute(databaseid, params, function (data, err) { - restoreTables(); - if (cb) data = cb(data, err); - return data; + yy.WithSelect.prototype.execute = function (databaseid, params, cb) { + var self = this; + var savedTables = []; + + // Restore tables shadowed by the temporary CTE tables + function restoreTables() { + self.withs.forEach(function (w, idx) { + if (savedTables[idx]) alasql.databases[databaseid].tables[w.name] = savedTables[idx]; + else delete alasql.databases[databaseid].tables[w.name]; }); } - var w = self.withs[idx]; - savedTables.push(alasql.databases[databaseid].tables[w.name]); + // Create temporary tables one after another. Each non-recursive CTE is + // executed with a callback, so sources that load data asynchronously + // (like CSV() or XLSX() reading a file) are fully loaded before the + // next CTE or the main select reads the temporary table. + function executeWith(idx) { + if (idx >= self.withs.length) { + return self.select.execute(databaseid, params, function (data, err) { + restoreTables(); + if (cb) data = cb(data, err); + return data; + }); + } - if (w.recursive) { - // Execute recursive CTE - executeRecursiveCTE(w, databaseid, params); - return executeWith(idx + 1); - } + var w = self.withs[idx]; + savedTables.push(alasql.databases[databaseid].tables[w.name]); - // Non-recursive CTE - var tb = (alasql.databases[databaseid].tables[w.name] = new Table({ - tableid: w.name, - })); - // The statement returns its own result, so keep the result of the rest - // of the chain (available right away when all sources are synchronous) - var res; - w.select.execute(databaseid, params, function (data, err) { - if (err) { - restoreTables(); - if (cb) return cb(null, err); - throw err; - } - tb.data = data; - if (w.columns) { - tb.data = renameColumns(tb.data, w.columns); + if (w.recursive) { + // Execute recursive CTE + executeRecursiveCTE(w, databaseid, params); + return executeWith(idx + 1); } - res = executeWith(idx + 1); - }); - return res; - } - return executeWith(0); -}; + // Non-recursive CTE + var tb = (alasql.databases[databaseid].tables[w.name] = new Table({ + tableid: w.name, + })); + // The statement returns its own result, so keep the result of the rest + // of the chain (available right away when all sources are synchronous) + var res; + w.select.execute(databaseid, params, function (data, err) { + if (err) { + restoreTables(); + if (cb) return cb(null, err); + throw err; + } + tb.data = data; + if (w.columns) { + tb.data = renameColumns(tb.data, w.columns); + } + res = executeWith(idx + 1); + }); + return res; + } + + return executeWith(0); + }; -/*/* + /*/* // CREATE TABLE //yy.CreateTable.prototype.compile = returnUndefined; yy.CreateView.prototype.execute = function (databaseid) { diff --git a/src/storage/filestorage.js b/src/storage/filestorage.js index b772640a9d..de1eaf7e52 100755 --- a/src/storage/filestorage.js +++ b/src/storage/filestorage.js @@ -3,260 +3,261 @@ import {extend} from '../utils/helpers.js'; export default function install(alasql, internal) { var schemaChanged = internal.schemaChanged; var loadFile = alasql.utils.loadFile; -// -// 91localstorage.js -// localStorage and DOM-Storage engine -// Date: 09.12.2014 -// (c) Andrey Gershun -// + // + // 91localstorage.js + // localStorage and DOM-Storage engine + // Date: 09.12.2014 + // (c) Andrey Gershun + // -var FS = (alasql.engines.FILESTORAGE = alasql.engines.FILE = function () {}); + var FS = (alasql.engines.FILESTORAGE = alasql.engines.FILE = function () {}); -FS.createDatabase = function (fsdbid, args, ifnotexists, dbid, cb) { - var res = 1; - var filename = args[0].value; - alasql.utils.fileExists(filename, function (fex) { - if (fex) { - if (ifnotexists) { - res = 0; - if (cb) res = cb(res); - return res; + FS.createDatabase = function (fsdbid, args, ifnotexists, dbid, cb) { + var res = 1; + var filename = args[0].value; + alasql.utils.fileExists(filename, function (fex) { + if (fex) { + if (ifnotexists) { + res = 0; + if (cb) res = cb(res); + return res; + } else { + throw new Error('Cannot create new database file, because it already exists'); + } } else { - throw new Error('Cannot create new database file, because it already exists'); + var data = {tables: {}}; + alasql.utils.saveFile(filename, JSON.stringify(data), function (data) { + if (cb) res = cb(res); + }); } - } else { - var data = {tables: {}}; - alasql.utils.saveFile(filename, JSON.stringify(data), function (data) { - if (cb) res = cb(res); - }); - } - }); - return res; -}; + }); + return res; + }; -FS.dropDatabase = function (fsdbid, ifexists, cb) { - var res; - var filename = ''; + FS.dropDatabase = function (fsdbid, ifexists, cb) { + var res; + var filename = ''; - if (typeof fsdbid === 'object' && fsdbid.value) { - // Existing tests (test225.js) had DROP directly without DETACH and - // without a database id / name. It instead used the filename directly. - // This block will handle that - filename = fsdbid.value; - } else { - // When a database id / name is specified in DROP, it will be handled by this block. - // Note: Both DETACH + DROP and direct DROP without DETACH will be handled by this block - // We will be deleting the database object and the file either way. - // However, in the future, if we would like to have a stricter implementation - // where we cannot DROP without DETACHing it first, we can handle that case using - // the 'isDetached' property of the database object. - // (i.e) alasql.databases[fsdbid].isDetached will be set if it is - // has been detached first - var db = alasql.databases[fsdbid] || {}; + if (typeof fsdbid === 'object' && fsdbid.value) { + // Existing tests (test225.js) had DROP directly without DETACH and + // without a database id / name. It instead used the filename directly. + // This block will handle that + filename = fsdbid.value; + } else { + // When a database id / name is specified in DROP, it will be handled by this block. + // Note: Both DETACH + DROP and direct DROP without DETACH will be handled by this block + // We will be deleting the database object and the file either way. + // However, in the future, if we would like to have a stricter implementation + // where we cannot DROP without DETACHing it first, we can handle that case using + // the 'isDetached' property of the database object. + // (i.e) alasql.databases[fsdbid].isDetached will be set if it is + // has been detached first + var db = alasql.databases[fsdbid] || {}; - filename = db.filename || ''; - delete alasql.databases[fsdbid]; - } - alasql.utils.fileExists(filename, function (fex) { - if (fex) { - res = 1; - alasql.utils.deleteFile(filename, function () { + filename = db.filename || ''; + delete alasql.databases[fsdbid]; + } + alasql.utils.fileExists(filename, function (fex) { + if (fex) { res = 1; + alasql.utils.deleteFile(filename, function () { + res = 1; + if (cb) res = cb(res); + }); + } else { + if (!ifexists) { + throw new Error('Cannot drop database file, because it does not exist'); + } + res = 0; if (cb) res = cb(res); - }); - } else { - if (!ifexists) { - throw new Error('Cannot drop database file, because it does not exist'); } - res = 0; - if (cb) res = cb(res); - } - }); - return res; -}; + }); + return res; + }; -FS.attachDatabase = function (fsdbid, dbid, args, params, cb) { - var res = 1; - if (alasql.databases[dbid]) { - throw new Error('Unable to attach database as "' + dbid + '" because it already exists'); - } - var db = new alasql.Database(dbid || fsdbid); - db.engineid = 'FILESTORAGE'; - db.filename = args[0].value; - loadFile(db.filename, !!cb, function (s) { - try { - db.data = JSON.parse(s); - } catch (err) { - throw new Error('Data in FileStorage database are corrupted'); + FS.attachDatabase = function (fsdbid, dbid, args, params, cb) { + var res = 1; + if (alasql.databases[dbid]) { + throw new Error('Unable to attach database as "' + dbid + '" because it already exists'); } - db.tables = db.data.tables; - // IF AUTOCOMMIT IS OFF then copy data to memory - if (!alasql.options.autocommit) { - if (db.tables) { - for (var tbid in db.tables) { + var db = new alasql.Database(dbid || fsdbid); + db.engineid = 'FILESTORAGE'; + db.filename = args[0].value; + loadFile(db.filename, !!cb, function (s) { + try { + db.data = JSON.parse(s); + } catch (err) { + throw new Error('Data in FileStorage database are corrupted'); + } + // Live tables are kept apart from the schema stored in the file, so storing a + // schema does not drop the keys and checks of the live table (#1846) + db.tables = {}; + for (var tbid in db.data.tables) { + db.tables[tbid] = extend({}, db.data.tables[tbid]); + // IF AUTOCOMMIT IS OFF then copy data to memory + if (!alasql.options.autocommit) { db.tables[tbid].data = db.data[tbid]; } } - } - if (cb) res = cb(res); - }); - return res; -}; - -// The part of a table that is stored in the file, beside its rows -function fsTableSchema(table) { - return { - columns: table.columns, - defaultfns: table.defaultfns, - onupdatefns: table.onupdatefns, + if (cb) res = cb(res); + }); + return res; }; -} - -FS.createTable = function (databaseid, tableid, ifnotexists, cb) { - var db = alasql.databases[databaseid]; - var tb = db.data[tableid]; - var res = 1; - if (tb && !ifnotexists) { - throw new Error('Table "' + tableid + '" alsready exists in the database "' + fsdbid + '"'); + // The part of a table that is stored in the file, beside its rows + function fsTableSchema(table) { + return { + columns: table.columns, + defaultfns: table.defaultfns, + onupdatefns: table.onupdatefns, + }; } - var table = alasql.databases[databaseid].tables[tableid]; - db.data.tables[tableid] = fsTableSchema(table); - db.data[tableid] = []; - FS.updateFile(databaseid); + FS.createTable = function (databaseid, tableid, ifnotexists, cb) { + var db = alasql.databases[databaseid]; + var tb = db.data[tableid]; + var res = 1; - if (cb) cb(res); - return res; -}; + if (tb && !ifnotexists) { + throw new Error('Table "' + tableid + '" alsready exists in the database "' + fsdbid + '"'); + } + var table = alasql.databases[databaseid].tables[tableid]; + db.data.tables[tableid] = fsTableSchema(table); + db.data[tableid] = []; -FS.updateFile = function (databaseid) { - var db = alasql.databases[databaseid]; - if (db.issaving) { - db.postsave = true; - return; - } - db.issaving = true; - db.postsave = false; - alasql.utils.saveFile(db.filename, JSON.stringify(db.data), function () { - db.issaving = false; + FS.updateFile(databaseid); - if (db.postsave) { - setTimeout(function () { - FS.updateFile(databaseid); - }, 50); // TODO Test with different timeout parameters + if (cb) cb(res); + return res; + }; + + FS.updateFile = function (databaseid) { + var db = alasql.databases[databaseid]; + if (db.issaving) { + db.postsave = true; + return; } - }); -}; + db.issaving = true; + db.postsave = false; + alasql.utils.saveFile(db.filename, JSON.stringify(db.data), function () { + db.issaving = false; -FS.dropTable = function (databaseid, tableid, ifexists, cb) { - var res = 1; - var db = alasql.databases[databaseid]; - if (!ifexists && !db.tables[tableid]) { - throw new Error( - 'Cannot drop table "' + tableid + '" in fileStorage, because it does not exist' - ); - } - delete db.tables[tableid]; - delete db.data.tables[tableid]; - delete db.data[tableid]; - FS.updateFile(databaseid); - if (cb) cb(res); - return res; -}; + if (db.postsave) { + setTimeout(function () { + FS.updateFile(databaseid); + }, 50); // TODO Test with different timeout parameters + } + }); + }; -FS.fromTable = function (databaseid, tableid, cb, idx, query) { - var db = alasql.databases[databaseid]; - var res = db.data[tableid]; - if (cb) res = cb(res, idx, query); - return res; -}; + FS.dropTable = function (databaseid, tableid, ifexists, cb) { + var res = 1; + var db = alasql.databases[databaseid]; + if (!ifexists && !db.tables[tableid]) { + throw new Error( + 'Cannot drop table "' + tableid + '" in fileStorage, because it does not exist' + ); + } + delete db.tables[tableid]; + delete db.data.tables[tableid]; + delete db.data[tableid]; + FS.updateFile(databaseid); + if (cb) cb(res); + return res; + }; -FS.intoTable = function (databaseid, tableid, value, columns, cb) { - var db = alasql.databases[databaseid]; - var res = value.length; - var tb = db.data[tableid]; - if (!tb) tb = []; - db.data[tableid] = tb.concat(value); - FS.updateFile(databaseid); - if (cb) cb(res); - return res; -}; + FS.fromTable = function (databaseid, tableid, cb, idx, query) { + var db = alasql.databases[databaseid]; + var res = db.data[tableid]; + if (cb) res = cb(res, idx, query); + return res; + }; -FS.loadTableData = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var table = db.tables[tableid]; - // After ATTACH a table is only its stored schema: make it a table that statements can change (#1107) - if (!(table instanceof alasql.Table)) { - table = db.tables[tableid] = new alasql.Table(fsTableSchema(table)); - table.indexColumns(); - } - table.data = db.data[tableid]; -}; + FS.intoTable = function (databaseid, tableid, value, columns, cb) { + var db = alasql.databases[databaseid]; + var res = value.length; + var tb = db.data[tableid]; + if (!tb) tb = []; + db.data[tableid] = tb.concat(value); + FS.updateFile(databaseid); + if (cb) cb(res); + return res; + }; -FS.saveTableData = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var table = db.tables[tableid]; - // The schema is saved with the rows, so ALTER TABLE is kept (#1107) - db.data.tables[tableid] = fsTableSchema(table); - db.data[tableid] = table.data; - table.data = null; - FS.updateFile(databaseid); -}; + FS.loadTableData = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var table = db.tables[tableid]; + // After ATTACH a table is only its stored schema: make it a table that statements can change (#1107) + if (!(table instanceof alasql.Table)) { + table = db.tables[tableid] = new alasql.Table(fsTableSchema(table)); + table.indexColumns(); + } + table.data = db.data[tableid]; + }; -FS.commit = function (databaseid, cb) { - var db = alasql.databases[databaseid]; - var fsdb = {tables: {}}; - if (db.tables) { - for (var tbid in db.tables) { - db.data.tables[tbid] = { - columns: db.tables[tbid].columns, - defaultfns: db.tables[tbid].defaultfns, - onupdatefns: db.tables[tbid].onupdatefns, - }; - db.data[tbid] = db.tables[tbid].data; + FS.saveTableData = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var table = db.tables[tableid]; + // The schema is saved with the rows, so ALTER TABLE is kept (#1107) + db.data.tables[tableid] = fsTableSchema(table); + db.data[tableid] = table.data; + table.data = null; + FS.updateFile(databaseid); + }; + + FS.commit = function (databaseid, cb) { + var db = alasql.databases[databaseid]; + var fsdb = {tables: {}}; + if (db.tables) { + for (var tbid in db.tables) { + db.data.tables[tbid] = { + columns: db.tables[tbid].columns, + defaultfns: db.tables[tbid].defaultfns, + onupdatefns: db.tables[tbid].onupdatefns, + }; + db.data[tbid] = db.tables[tbid].data; + } } - } - FS.updateFile(databaseid); - return cb ? cb(1) : 1; -}; + FS.updateFile(databaseid); + return cb ? cb(1) : 1; + }; -FS.begin = FS.commit; + FS.begin = FS.commit; -FS.rollback = function (databaseid, cb) { - var res = 1; - var db = alasql.databases[databaseid]; - schemaChanged(db); - wait(); - function wait() { - setTimeout(function () { - if (db.issaving) { - return wait(); - } else { - alasql.loadFile(db.filename, !!cb, function (data) { - db.data = data; - db.tables = {}; - for (var tbid in db.data.tables) { - var tb = new alasql.Table({columns: db.data.tables[tbid].columns}); - extend(tb, db.data.tables[tbid]); - db.tables[tbid] = tb; - if (!alasql.options.autocommit) { - db.tables[tbid].data = db.data[tbid]; + FS.rollback = function (databaseid, cb) { + var res = 1; + var db = alasql.databases[databaseid]; + schemaChanged(db); + wait(); + function wait() { + setTimeout(function () { + if (db.issaving) { + return wait(); + } else { + alasql.loadFile(db.filename, !!cb, function (data) { + db.data = data; + db.tables = {}; + for (var tbid in db.data.tables) { + var tb = new alasql.Table({columns: db.data.tables[tbid].columns}); + extend(tb, db.data.tables[tbid]); + db.tables[tbid] = tb; + if (!alasql.options.autocommit) { + db.tables[tbid].data = db.data[tbid]; + } + db.tables[tbid].indexColumns(); } - db.tables[tbid].indexColumns(); - } - delete alasql.databases[databaseid]; - alasql.databases[databaseid] = new alasql.Database(databaseid); - extend(alasql.databases[databaseid], db); - alasql.databases[databaseid].engineid = 'FILESTORAGE'; - alasql.databases[databaseid].filename = db.filename; + delete alasql.databases[databaseid]; + alasql.databases[databaseid] = new alasql.Database(databaseid); + extend(alasql.databases[databaseid], db); + alasql.databases[databaseid].engineid = 'FILESTORAGE'; + alasql.databases[databaseid].filename = db.filename; - if (cb) res = cb(res); - // Todo: check why no return - }); - } - }, 100); - } -}; + if (cb) res = cb(res); + // Todo: check why no return + }); + } + }, 100); + } + }; } diff --git a/src/storage/indexeddb.js b/src/storage/indexeddb.js index 22809f4492..2553bb4122 100755 --- a/src/storage/indexeddb.js +++ b/src/storage/indexeddb.js @@ -1,194 +1,194 @@ export default function install(alasql) { -// -// 91indexeddb.js -// AlaSQL IndexedDB module -// Date: 18.04.2015 -// (c) Andrey Gershun -// - -/* global alasql, yy, utils*/ - -var IDB = (alasql.engines.INDEXEDDB = function () { - ''; -}); - -/** - * @param {string} name - * @returns {Promise<{name: string, version: number}|0>} - */ -async function _databaseExists(name) { - const indexedDB = globalThis.indexedDB; - - if (!indexedDB) { - throw new Error('IndexedDB is not supported in this browser'); - } + // + // 91indexeddb.js + // AlaSQL IndexedDB module + // Date: 18.04.2015 + // (c) Andrey Gershun + // - if (indexedDB.databases) { - const dbs = await indexedDB.databases(); - const db = dbs.find(db => db.name === name); - // @ts-ignore - return db || 0; - } + /* global alasql, yy, utils*/ - // Try if it exist - const req = indexedDB.open(name); + var IDB = (alasql.engines.INDEXEDDB = function () { + ''; + }); - return new Promise(function (resolve, reject) { - req.onsuccess = () => { - req.result.close(); - resolve({name, version: req.result.version}); - }; + /** + * @param {string} name + * @returns {Promise<{name: string, version: number}|0>} + */ + async function _databaseExists(name) { + const indexedDB = globalThis.indexedDB; - req.onupgradeneeded = evt => { - evt.target.transaction.abort(); - resolve(0); - }; + if (!indexedDB) { + throw new Error('IndexedDB is not supported in this browser'); + } - req.onerror = () => { - reject(new Error('IndexedDB error')); - }; + if (indexedDB.databases) { + const dbs = await indexedDB.databases(); + const db = dbs.find(db => db.name === name); + // @ts-ignore + return db || 0; + } - req.onblocked = () => { - resolve({name, version: req.result.version}); - }; - }); -} + // Try if it exist + const req = indexedDB.open(name); -// -// SHOW DATABASES -// work only in chrome -// -IDB.showDatabases = function (like, cb) { - if (!indexedDB.databases) { - cb(null, new Error('SHOW DATABASE is not supported in this browser')); - return; - } + return new Promise(function (resolve, reject) { + req.onsuccess = () => { + req.result.close(); + resolve({name, version: req.result.version}); + }; - indexedDB.databases().then(dblist => { - const res = []; - const relike = like && new RegExp(like.value.replace(/\%/g, '.*'), 'g'); + req.onupgradeneeded = evt => { + evt.target.transaction.abort(); + resolve(0); + }; - for (var i = 0; i < dblist.length; i++) { - if (!like || dblist[i].name.match(relike)) { - res.push({databaseid: dblist[i].name}); - } - } + req.onerror = () => { + reject(new Error('IndexedDB error')); + }; - cb(res); - }); -}; + req.onblocked = () => { + resolve({name, version: req.result.version}); + }; + }); + } -IDB.createDatabase = async function (ixdbid, args, ifnotexists, dbid, cb) { - const found = await _databaseExists(ixdbid).catch(err => { - if (cb) { - cb(null, err); - return null; + // + // SHOW DATABASES + // work only in chrome + // + IDB.showDatabases = function (like, cb) { + if (!indexedDB.databases) { + cb(null, new Error('SHOW DATABASE is not supported in this browser')); + return; } - throw err; - }); - if (found === null) { - return; // Error already handled via callback - } + indexedDB.databases().then(dblist => { + const res = []; + const relike = like && new RegExp(like.value.replace(/\%/g, '.*'), 'g'); - if (found) { - if (ifnotexists) { - cb && cb(0); - } else { - const err = new Error( - `IndexedDB: Cannot create new database "${ixdbid}" because it already exists` - ); + for (var i = 0; i < dblist.length; i++) { + if (!like || dblist[i].name.match(relike)) { + res.push({databaseid: dblist[i].name}); + } + } + + cb(res); + }); + }; + + IDB.createDatabase = async function (ixdbid, args, ifnotexists, dbid, cb) { + const found = await _databaseExists(ixdbid).catch(err => { if (cb) { cb(null, err); - return; + return null; } throw err; - } - } else { - const request = indexedDB.open(ixdbid, 1); - request.onsuccess = () => { - request.result.close(); - cb(1); - }; - } -}; + }); -IDB.dropDatabase = async function (ixdbid, ifexists, cb) { - const found = await _databaseExists(ixdbid).catch(err => { - if (cb) { - cb(null, err); - return null; + if (found === null) { + return; // Error already handled via callback } - throw err; - }); - - if (found === null) { - return; // Error already handled via callback - } - if (found) { - const request = indexedDB.deleteDatabase(ixdbid); - request.onsuccess = () => { - if (cb) cb(1); - }; - } else { - if (ifexists) { - cb && cb(0); + if (found) { + if (ifnotexists) { + cb && cb(0); + } else { + const err = new Error( + `IndexedDB: Cannot create new database "${ixdbid}" because it already exists` + ); + if (cb) { + cb(null, err); + return; + } + throw err; + } } else { + const request = indexedDB.open(ixdbid, 1); + request.onsuccess = () => { + request.result.close(); + cb(1); + }; + } + }; + + IDB.dropDatabase = async function (ixdbid, ifexists, cb) { + const found = await _databaseExists(ixdbid).catch(err => { if (cb) { - cb( - null, - new Error(`IndexedDB: Cannot drop database "${ixdbid}" because it does not exist`) - ); - return; + cb(null, err); + return null; } - throw new Error(`IndexedDB: Cannot drop database "${ixdbid}" because it does not exist`); + throw err; + }); + + if (found === null) { + return; // Error already handled via callback } - } -}; -IDB.attachDatabase = async function (ixdbid, dbid, args, params, cb) { - const found = await _databaseExists(ixdbid).catch(err => { - if (cb) { - cb(null, err); - return null; + if (found) { + const request = indexedDB.deleteDatabase(ixdbid); + request.onsuccess = () => { + if (cb) cb(1); + }; + } else { + if (ifexists) { + cb && cb(0); + } else { + if (cb) { + cb( + null, + new Error(`IndexedDB: Cannot drop database "${ixdbid}" because it does not exist`) + ); + return; + } + throw new Error(`IndexedDB: Cannot drop database "${ixdbid}" because it does not exist`); + } } - throw err; - }); + }; - if (found === null) { - return; // Error already handled via callback - } + IDB.attachDatabase = async function (ixdbid, dbid, args, params, cb) { + const found = await _databaseExists(ixdbid).catch(err => { + if (cb) { + cb(null, err); + return null; + } + throw err; + }); - if (!found) { - const err = new Error( - `IndexedDB: Cannot attach database "${ixdbid}" because it does not exist` - ); - if (cb) { - cb(null, err); - return; + if (found === null) { + return; // Error already handled via callback } - throw err; - } - - const stores = await new Promise((resolve, reject) => { - const request = indexedDB.open(ixdbid); - request.onsuccess = () => { - resolve(request.result.objectStoreNames); - request.result.close(); - }; - }); - const db = new alasql.Database(dbid || ixdbid); - db.engineid = 'INDEXEDDB'; - db.ixdbid = ixdbid; - db.tables = []; + if (!found) { + const err = new Error( + `IndexedDB: Cannot attach database "${ixdbid}" because it does not exist` + ); + if (cb) { + cb(null, err); + return; + } + throw err; + } - for (var i = 0; i < stores.length; i++) { - db.tables[stores[i]] = {}; - } + const stores = await new Promise((resolve, reject) => { + const request = indexedDB.open(ixdbid); + request.onsuccess = () => { + resolve(request.result.objectStoreNames); + request.result.close(); + }; + }); + + const db = new alasql.Database(dbid || ixdbid); + db.engineid = 'INDEXEDDB'; + db.ixdbid = ixdbid; + db.tables = []; + + for (var i = 0; i < stores.length; i++) { + db.tables[stores[i]] = {}; + } - /*/* + /*/* if (!alasql.options.autocommit) { if (db.tables) { for(var tbid in db.tables) { @@ -198,109 +198,109 @@ IDB.attachDatabase = async function (ixdbid, dbid, args, params, cb) { } */ - if (cb) cb(1); -}; + if (cb) cb(1); + }; + + IDB.createTable = async function (databaseid, tableid, ifnotexists, cb) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const found = await _databaseExists(ixdbid).catch(err => { + if (cb) { + cb(null, err); + return null; + } + throw err; + }); -IDB.createTable = async function (databaseid, tableid, ifnotexists, cb) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const found = await _databaseExists(ixdbid).catch(err => { - if (cb) { - cb(null, err); - return null; + if (found === null) { + return; // Error already handled via callback } - throw err; - }); - - if (found === null) { - return; // Error already handled via callback - } - if (!found) { - const err = new Error( - 'IndexedDB: Cannot create table in database "' + ixdbid + '" because it does not exist' - ); - if (cb) { - cb(null, err); - return; + if (!found) { + const err = new Error( + 'IndexedDB: Cannot create table in database "' + ixdbid + '" because it does not exist' + ); + if (cb) { + cb(null, err); + return; + } + throw err; } - throw err; - } - const request = indexedDB.open(ixdbid, found.version + 1); - request.onupgradeneeded = function (event) { - request.result.createObjectStore(tableid, {autoIncrement: true}); - }; - request.onsuccess = function (event) { - request.result.close(); - if (cb) cb(1); - }; - request.onerror = evt => { - cb(null, evt); - }; - request.onblocked = function (event) { - cb( - null, - new Error(`Cannot create table "${tableid}" because database "${databaseid}" is blocked`) - ); + const request = indexedDB.open(ixdbid, found.version + 1); + request.onupgradeneeded = function (event) { + request.result.createObjectStore(tableid, {autoIncrement: true}); + }; + request.onsuccess = function (event) { + request.result.close(); + if (cb) cb(1); + }; + request.onerror = evt => { + cb(null, evt); + }; + request.onblocked = function (event) { + cb( + null, + new Error(`Cannot create table "${tableid}" because database "${databaseid}" is blocked`) + ); + }; }; -}; - -IDB.dropTable = async function (databaseid, tableid, ifexists, cb) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const found = await _databaseExists(ixdbid).catch(err => { - if (cb) { - cb(null, err); - return null; - } - throw err; - }); - if (found === null) { - return; // Error already handled via callback - } + IDB.dropTable = async function (databaseid, tableid, ifexists, cb) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const found = await _databaseExists(ixdbid).catch(err => { + if (cb) { + cb(null, err); + return null; + } + throw err; + }); - if (!found) { - const err = new Error( - 'IndexedDB: Cannot drop table in database "' + ixdbid + '" because it does not exist' - ); - if (cb) { - cb(null, err); - return; + if (found === null) { + return; // Error already handled via callback } - throw err; - } - - const request = indexedDB.open(ixdbid, found.version + 1); - let err; - request.onupgradeneeded = function (evt) { - var ixdb = request.result; - if (ixdb.objectStoreNames.contains(tableid)) { - ixdb.deleteObjectStore(tableid); - delete alasql.databases[databaseid].tables[tableid]; - } else { - if (!ifexists) { - err = new Error(`IndexedDB: Cannot drop table "${tableid}" because it does not exist`); - evt.target.transaction.abort(); + if (!found) { + const err = new Error( + 'IndexedDB: Cannot drop table in database "' + ixdbid + '" because it does not exist' + ); + if (cb) { + cb(null, err); + return; } + throw err; } + + const request = indexedDB.open(ixdbid, found.version + 1); + + let err; + request.onupgradeneeded = function (evt) { + var ixdb = request.result; + if (ixdb.objectStoreNames.contains(tableid)) { + ixdb.deleteObjectStore(tableid); + delete alasql.databases[databaseid].tables[tableid]; + } else { + if (!ifexists) { + err = new Error(`IndexedDB: Cannot drop table "${tableid}" because it does not exist`); + evt.target.transaction.abort(); + } + } + }; + request.onsuccess = function (event) { + request.result.close(); + if (cb) cb(1); + }; + request.onerror = function (event) { + cb && cb(null, err || event); + }; + request.onblocked = function (event) { + cb( + null, + new Error(`Cannot drop table "${tableid}" because database "${databaseid}" is blocked`) + ); + }; }; - request.onsuccess = function (event) { - request.result.close(); - if (cb) cb(1); - }; - request.onerror = function (event) { - cb && cb(null, err || event); - }; - request.onblocked = function (event) { - cb( - null, - new Error(`Cannot drop table "${tableid}" because database "${databaseid}" is blocked`) - ); - }; -}; -/*/* + /*/* // IDB.intoTable = function(databaseid, tableid, value, cb) { // // console.log('intoTable',databaseid, tableid, value, cb); // var ixdbid = alasql.databases[databaseid].ixdbid; @@ -333,173 +333,173 @@ IDB.dropTable = async function (databaseid, tableid, ifexists, cb) { // }; */ -IDB.intoTable = function (databaseid, tableid, value, columns, cb) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const request = indexedDB.open(ixdbid); - var db = alasql.databases[databaseid]; - var table = db.tables[tableid]; - - request.onupgradeneeded = evt => { - evt.target.transaction.abort(); - const err = new Error( - `Cannot insert into table "${tableid}" because database "${databaseid}" does not exist` - ); - if (cb) cb(null, err); - }; + IDB.intoTable = function (databaseid, tableid, value, columns, cb) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const request = indexedDB.open(ixdbid); + var db = alasql.databases[databaseid]; + var table = db.tables[tableid]; - request.onsuccess = () => { - var ixdb = request.result; - var tx = ixdb.transaction([tableid], 'readwrite'); - var tb = tx.objectStore(tableid); - // Apply AUTOINCREMENT / IDENTITY values before inserting (only when table has identity columns) - if (table && table.identities && Object.keys(table.identities).length > 0) { - for (var columnid in table.identities) { - var ident = table.identities[columnid]; - for (var i = 0; i < value.length; i++) { - var userProvided = - typeof value[i][columnid] !== 'undefined' && value[i][columnid] !== null; - if (!userProvided) { - value[i][columnid] = ident.value; - } - // Advance counter: if the inserted value is >= current, sync counter past it - if (userProvided && +value[i][columnid] >= ident.value) { - ident.value = +value[i][columnid] + ident.step; - } else { - ident.value += ident.step; + request.onupgradeneeded = evt => { + evt.target.transaction.abort(); + const err = new Error( + `Cannot insert into table "${tableid}" because database "${databaseid}" does not exist` + ); + if (cb) cb(null, err); + }; + + request.onsuccess = () => { + var ixdb = request.result; + var tx = ixdb.transaction([tableid], 'readwrite'); + var tb = tx.objectStore(tableid); + // Apply AUTOINCREMENT / IDENTITY values before inserting (only when table has identity columns) + if (table && table.identities && Object.keys(table.identities).length > 0) { + for (var columnid in table.identities) { + var ident = table.identities[columnid]; + for (var i = 0; i < value.length; i++) { + var userProvided = + typeof value[i][columnid] !== 'undefined' && value[i][columnid] !== null; + if (!userProvided) { + value[i][columnid] = ident.value; + } + // Advance counter: if the inserted value is >= current, sync counter past it + if (userProvided && +value[i][columnid] >= ident.value) { + ident.value = +value[i][columnid] + ident.step; + } else { + ident.value += ident.step; + } } } } - } - for (var i = 0, ilen = value.length; i < ilen; i++) { - tb.add(value[i]); - } - tx.oncomplete = function () { - ixdb.close(); - for (var tr in table.afterinsert) { - if (table.afterinsert[tr]) { - var trigger = table.afterinsert[tr]; - if (trigger.funcid) { - alasql.fn[trigger.funcid](value); - } else if (trigger.statement) { - trigger.statement.execute(databaseid); + for (var i = 0, ilen = value.length; i < ilen; i++) { + tb.add(value[i]); + } + tx.oncomplete = function () { + ixdb.close(); + for (var tr in table.afterinsert) { + if (table.afterinsert[tr]) { + var trigger = table.afterinsert[tr]; + if (trigger.funcid) { + alasql.fn[trigger.funcid](value); + } else if (trigger.statement) { + trigger.statement.execute(databaseid); + } } } - } - if (cb) cb(ilen); + if (cb) cb(ilen); + }; }; }; -}; - -IDB.fromTable = function (databaseid, tableid, cb, idx, query) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const request = indexedDB.open(ixdbid); - - request.onupgradeneeded = evt => { - evt.target.transaction.abort(); - const err = new Error( - `Cannot select from table "${tableid}" because database "${databaseid}" does not exist` - ); - if (cb) cb(null, err); - }; - request.onsuccess = () => { - const res = []; - const ixdb = request.result; - const cur = ixdb.transaction([tableid]).objectStore(tableid).openCursor(); - - cur.onsuccess = () => { - const cursor = cur.result; - if (cursor) { - // if keyPath(columns) is not present then we take the key and value as object. - const cursorValue = - typeof cursor === 'object' ? cursor.value : {[cursor.key]: cursor.value}; - res.push(cursorValue); - cursor.continue(); - } else { - ixdb.close(); - if (cb) cb(res, idx, query); - } + IDB.fromTable = function (databaseid, tableid, cb, idx, query) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const request = indexedDB.open(ixdbid); + + request.onupgradeneeded = evt => { + evt.target.transaction.abort(); + const err = new Error( + `Cannot select from table "${tableid}" because database "${databaseid}" does not exist` + ); + if (cb) cb(null, err); + }; + + request.onsuccess = () => { + const res = []; + const ixdb = request.result; + const cur = ixdb.transaction([tableid]).objectStore(tableid).openCursor(); + + cur.onsuccess = () => { + const cursor = cur.result; + if (cursor) { + // if keyPath(columns) is not present then we take the key and value as object. + const cursorValue = + typeof cursor === 'object' ? cursor.value : {[cursor.key]: cursor.value}; + res.push(cursorValue); + cursor.continue(); + } else { + ixdb.close(); + if (cb) cb(res, idx, query); + } + }; }; }; -}; - -IDB.deleteFromTable = function (databaseid, tableid, wherefn, params, cb) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const request = indexedDB.open(ixdbid); - - request.onsuccess = () => { - const ixdb = request.result; - const cur = ixdb.transaction([tableid], 'readwrite').objectStore(tableid).openCursor(); - - let num = 0; - cur.onsuccess = () => { - var cursor = cur.result; - if (cursor) { - if (!wherefn || wherefn(cursor.value, params, alasql)) { - cursor.delete(); - num++; + + IDB.deleteFromTable = function (databaseid, tableid, wherefn, params, cb) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const request = indexedDB.open(ixdbid); + + request.onsuccess = () => { + const ixdb = request.result; + const cur = ixdb.transaction([tableid], 'readwrite').objectStore(tableid).openCursor(); + + let num = 0; + cur.onsuccess = () => { + var cursor = cur.result; + if (cursor) { + if (!wherefn || wherefn(cursor.value, params, alasql)) { + cursor.delete(); + num++; + } + cursor.continue(); + } else { + ixdb.close(); + if (cb) cb(num); } - cursor.continue(); - } else { - ixdb.close(); - if (cb) cb(num); - } + }; }; }; -}; - -IDB.updateTable = function (databaseid, tableid, assignfn, wherefn, params, cb) { - const ixdbid = alasql.databases[databaseid].ixdbid; - const request = indexedDB.open(ixdbid); - request.onsuccess = function () { - const ixdb = request.result; - const cur = ixdb.transaction([tableid], 'readwrite').objectStore(tableid).openCursor(); - - let num = 0; - cur.onsuccess = () => { - var cursor = cur.result; - if (cursor) { - if (!wherefn || wherefn(cursor.value, params)) { - var r = cursor.value; - assignfn(r, params); - cursor.update(r); - num++; + + IDB.updateTable = function (databaseid, tableid, assignfn, wherefn, params, cb) { + const ixdbid = alasql.databases[databaseid].ixdbid; + const request = indexedDB.open(ixdbid); + request.onsuccess = function () { + const ixdb = request.result; + const cur = ixdb.transaction([tableid], 'readwrite').objectStore(tableid).openCursor(); + + let num = 0; + cur.onsuccess = () => { + var cursor = cur.result; + if (cursor) { + if (!wherefn || wherefn(cursor.value, params)) { + var r = cursor.value; + assignfn(r, params); + cursor.update(r); + num++; + } + cursor.continue(); + } else { + ixdb.close(); + if (cb) cb(num); } - cursor.continue(); - } else { - ixdb.close(); - if (cb) cb(num); - } + }; }; }; -}; - -/** - * Commit transaction for IndexedDB - * Note: IndexedDB operations are auto-committed per operation - * This method provides API compatibility - */ -IDB.commit = function (databaseid, cb) { - // IndexedDB auto-commits each operation's transaction - // No additional action needed - return cb ? cb(1) : 1; -}; - -/** - * Begin transaction - alias to commit for IndexedDB - * Similar to LOCALSTORAGE pattern - */ -IDB.begin = IDB.commit; - -/** - * Rollback transaction for IndexedDB - * Note: IndexedDB operations are auto-committed per operation - * Manual rollback not supported - operations cannot be undone - */ -IDB.rollback = function (databaseid, cb) { - // IndexedDB auto-commits each operation - // Cannot rollback already-committed operations - // This provides API compatibility only - return cb ? cb(1) : 1; -}; + + /** + * Commit transaction for IndexedDB + * Note: IndexedDB operations are auto-committed per operation + * This method provides API compatibility + */ + IDB.commit = function (databaseid, cb) { + // IndexedDB auto-commits each operation's transaction + // No additional action needed + return cb ? cb(1) : 1; + }; + + /** + * Begin transaction - alias to commit for IndexedDB + * Similar to LOCALSTORAGE pattern + */ + IDB.begin = IDB.commit; + + /** + * Rollback transaction for IndexedDB + * Note: IndexedDB operations are auto-committed per operation + * Manual rollback not supported - operations cannot be undone + */ + IDB.rollback = function (databaseid, cb) { + // IndexedDB auto-commits each operation + // Cannot rollback already-committed operations + // This provides API compatibility only + return cb ? cb(1) : 1; + }; } diff --git a/src/storage/localstorage.js b/src/storage/localstorage.js index decc047795..32282cd4f0 100755 --- a/src/storage/localstorage.js +++ b/src/storage/localstorage.js @@ -1,95 +1,95 @@ import {extend} from '../utils/helpers.js'; export default function install(alasql) { -// -// 91localstorage.js -// localStorage and DOM-Storage engine -// Date: 09.12.2014 -// (c) Andrey Gershun -// + // + // 91localstorage.js + // localStorage and DOM-Storage engine + // Date: 09.12.2014 + // (c) Andrey Gershun + // -/* global alasql, yy, localStorage*/ + /* global alasql, yy, localStorage*/ -var LS = (alasql.engines.LOCALSTORAGE = function () {}); + var LS = (alasql.engines.LOCALSTORAGE = function () {}); -/** + /** Read data from localStorage with security breaks @param key {string} Address in localStorage @return {object} JSON object */ -LS.get = function (key) { - var s = localStorage.getItem(key); - if (typeof s === 'undefined') return; - var v; - try { - v = JSON.parse(s); - } catch (err) { - throw new Error('Cannot parse JSON object from localStorage' + s); - } - return v; -}; - -/** + LS.get = function (key) { + var s = localStorage.getItem(key); + if (typeof s === 'undefined') return; + var v; + try { + v = JSON.parse(s); + } catch (err) { + throw new Error('Cannot parse JSON object from localStorage' + s); + } + return v; + }; + + /** Store data into localStorage with security breaks @param key {string} Address in localStorage @return {object} JSON object */ -LS.set = function (key, value) { - if (typeof value === 'undefined') localStorage.removeItem(key); - else localStorage.setItem(key, JSON.stringify(value)); -}; + LS.set = function (key, value) { + if (typeof value === 'undefined') localStorage.removeItem(key); + else localStorage.setItem(key, JSON.stringify(value)); + }; -/** + /** Store table structure and data into localStorage @param databaseid {string} AlaSQL database id (not external localStorage) @param tableid {string} Table name @return Nothing */ -LS.storeTable = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var table = db.tables[tableid]; - // Create empty structure for table - var tbl = {}; - tbl.columns = table.columns; - tbl.data = table.data; - tbl.identities = table.identities; - tbl.defaultfns = table.defaultfns; - tbl.onupdatefns = table.onupdatefns; - // TODO: May be add indexes, objects and other fields? - LS.set(db.lsdbid + '.' + tableid, tbl); -}; - -/** + LS.storeTable = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var table = db.tables[tableid]; + // Create empty structure for table + var tbl = {}; + tbl.columns = table.columns; + tbl.data = table.data; + tbl.identities = table.identities; + tbl.defaultfns = table.defaultfns; + tbl.onupdatefns = table.onupdatefns; + // TODO: May be add indexes, objects and other fields? + LS.set(db.lsdbid + '.' + tableid, tbl); + }; + + /** Restore table structure and data @param databaseid {string} AlaSQL database id (not external localStorage) @param tableid {string} Table name @return Nothing */ -LS.restoreTable = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var tbl = LS.get(db.lsdbid + '.' + tableid); - var table = new alasql.Table(); - for (var f in tbl) { - table[f] = tbl[f]; - } - db.tables[tableid] = table; - table.indexColumns(); - // We need to add other things here - return table; -}; - -/** + LS.restoreTable = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var tbl = LS.get(db.lsdbid + '.' + tableid); + var table = new alasql.Table(); + for (var f in tbl) { + table[f] = tbl[f]; + } + db.tables[tableid] = table; + table.indexColumns(); + // We need to add other things here + return table; + }; + + /** Remove table from localStorage @param databaseid {string} AlaSQL database id (not external localStorage) @param tableid {string} Table name */ -LS.removeTable = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - localStorage.removeItem(db.lsdbid + '.' + tableid); -}; + LS.removeTable = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + localStorage.removeItem(db.lsdbid + '.' + tableid); + }; -/** + /** Create database in localStorage @param lsdbid {string} localStorage database id @param args {array} List of parameters (not used in localStorage) @@ -98,123 +98,125 @@ LS.removeTable = function (databaseid, tableid) { @param cb {function} Callback */ -LS.createDatabase = function (lsdbid, args, ifnotexists, databaseid, cb) { - var res = 1; - var ls = LS.get('alasql'); // Read list of all databases - if (!(ifnotexists && ls && ls.databases && ls.databases[lsdbid])) { - if (!ls) ls = {databases: {}}; // Empty record - if (ls.databases && ls.databases[lsdbid]) { - throw new Error( - 'localStorage: Cannot create new database "' + lsdbid + '" because it already exists' - ); + LS.createDatabase = function (lsdbid, args, ifnotexists, databaseid, cb) { + var res = 1; + var ls = LS.get('alasql'); // Read list of all databases + if (!(ifnotexists && ls && ls.databases && ls.databases[lsdbid])) { + if (!ls) ls = {databases: {}}; // Empty record + if (ls.databases && ls.databases[lsdbid]) { + throw new Error( + 'localStorage: Cannot create new database "' + lsdbid + '" because it already exists' + ); + } + ls.databases[lsdbid] = true; + LS.set('alasql', ls); + LS.set(lsdbid, {databaseid: lsdbid, tables: {}}); // Create database record + } else { + res = 0; } - ls.databases[lsdbid] = true; - LS.set('alasql', ls); - LS.set(lsdbid, {databaseid: lsdbid, tables: {}}); // Create database record - } else { - res = 0; - } - if (cb) res = cb(res); - return res; -}; - -/** + if (cb) res = cb(res); + return res; + }; + + /** Drop external database @param lsdbid {string} localStorage database id @param ifexists {boolean} Check if database exists @param cb {function} Callback */ -LS.dropDatabase = function (lsdbid, ifexists, cb) { - var res = 1; - var ls = LS.get('alasql'); - if (!(ifexists && ls && ls.databases && !ls.databases[lsdbid])) { - // 1. Remove record from 'alasql' record - if (!ls) { - if (!ifexists) { - throw new Error('There is no any AlaSQL databases in localStorage'); - } else { - return cb ? cb(0) : 0; + LS.dropDatabase = function (lsdbid, ifexists, cb) { + var res = 1; + var ls = LS.get('alasql'); + if (!(ifexists && ls && ls.databases && !ls.databases[lsdbid])) { + // 1. Remove record from 'alasql' record + if (!ls) { + if (!ifexists) { + throw new Error('There is no any AlaSQL databases in localStorage'); + } else { + return cb ? cb(0) : 0; + } } - } - if (ls.databases && !ls.databases[lsdbid]) { - throw new Error( - 'localStorage: Cannot drop database "' + lsdbid + '" because there is no such database' - ); - } - delete ls.databases[lsdbid]; - LS.set('alasql', ls); + if (ls.databases && !ls.databases[lsdbid]) { + throw new Error( + 'localStorage: Cannot drop database "' + lsdbid + '" because there is no such database' + ); + } + delete ls.databases[lsdbid]; + LS.set('alasql', ls); - // 2. Remove tables definitions - var db = LS.get(lsdbid); - for (var tableid in db.tables) { - localStorage.removeItem(lsdbid + '.' + tableid); - } + // 2. Remove tables definitions + var db = LS.get(lsdbid); + for (var tableid in db.tables) { + localStorage.removeItem(lsdbid + '.' + tableid); + } - // 3. Remove database definition - localStorage.removeItem(lsdbid); - } else { - res = 0; - } - if (cb) res = cb(res); - return res; -}; + // 3. Remove database definition + localStorage.removeItem(lsdbid); + } else { + res = 0; + } + if (cb) res = cb(res); + return res; + }; -/** + /** Attach existing localStorage database to AlaSQL database @param lsdibid {string} localStorage database id @param */ -LS.attachDatabase = function (lsdbid, databaseid, args, params, cb) { - var res = 1; - if (alasql.databases[databaseid]) { - throw new Error('Unable to attach database as "' + databaseid + '" because it already exists'); - } - if (!databaseid) databaseid = lsdbid; - var db = new alasql.Database(databaseid); - db.engineid = 'LOCALSTORAGE'; - db.lsdbid = lsdbid; - db.tables = LS.get(lsdbid).tables; - // IF AUTOABORT IS OFF then copy data to memory - if (!alasql.options.autocommit) { - if (db.tables) { - for (var tbid in db.tables) { - LS.restoreTable(databaseid, tbid); + LS.attachDatabase = function (lsdbid, databaseid, args, params, cb) { + var res = 1; + if (alasql.databases[databaseid]) { + throw new Error( + 'Unable to attach database as "' + databaseid + '" because it already exists' + ); + } + if (!databaseid) databaseid = lsdbid; + var db = new alasql.Database(databaseid); + db.engineid = 'LOCALSTORAGE'; + db.lsdbid = lsdbid; + db.tables = LS.get(lsdbid).tables; + // IF AUTOABORT IS OFF then copy data to memory + if (!alasql.options.autocommit) { + if (db.tables) { + for (var tbid in db.tables) { + LS.restoreTable(databaseid, tbid); + } } } - } - if (cb) res = cb(res); - return res; -}; + if (cb) res = cb(res); + return res; + }; -/** + /** Show list of databases from localStorage @param like {string} Mathing pattern @param cb {function} Callback */ -LS.showDatabases = function (like, cb) { - var res = []; - var ls = LS.get('alasql'); - if (like) { - // TODO: If we have a special function for LIKE patterns? - var relike = new RegExp(like.value.replace(/%/g, '.*'), 'g'); - } - if (ls && ls.databases) { - for (var dbid in ls.databases) { - res.push({databaseid: dbid}); + LS.showDatabases = function (like, cb) { + var res = []; + var ls = LS.get('alasql'); + if (like) { + // TODO: If we have a special function for LIKE patterns? + var relike = new RegExp(like.value.replace(/%/g, '.*'), 'g'); } - if (like && res && res.length > 0) { - res = res.filter(function (d) { - return d.databaseid.match(relike); - }); + if (ls && ls.databases) { + for (var dbid in ls.databases) { + res.push({databaseid: dbid}); + } + if (like && res && res.length > 0) { + res = res.filter(function (d) { + return d.databaseid.match(relike); + }); + } } - } - if (cb) res = cb(res); - return res; -}; + if (cb) res = cb(res); + return res; + }; -/** + /** Create table in localStorage database @param databaseid {string} AlaSQL database id @param tableid {string} Table id @@ -222,30 +224,30 @@ LS.showDatabases = function (like, cb) { @param cb {function} Callback */ -LS.createTable = function (databaseid, tableid, ifnotexists, cb) { - var res = 1; - var lsdbid = alasql.databases[databaseid].lsdbid; - var tb = LS.get(lsdbid + '.' + tableid); - // Check if such record exists - if (tb && !ifnotexists) { - throw new Error( - 'Table "' + tableid + '" alsready exists in localStorage database "' + lsdbid + '"' - ); - } - var lsdb = LS.get(lsdbid); - var table = alasql.databases[databaseid].tables[tableid]; - - // TODO: Check if required - lsdb.tables[tableid] = true; - - LS.set(lsdbid, lsdb); - LS.storeTable(databaseid, tableid); - - if (cb) res = cb(res); - return res; -}; - -/** + LS.createTable = function (databaseid, tableid, ifnotexists, cb) { + var res = 1; + var lsdbid = alasql.databases[databaseid].lsdbid; + var tb = LS.get(lsdbid + '.' + tableid); + // Check if such record exists + if (tb && !ifnotexists) { + throw new Error( + 'Table "' + tableid + '" alsready exists in localStorage database "' + lsdbid + '"' + ); + } + var lsdb = LS.get(lsdbid); + var table = alasql.databases[databaseid].tables[tableid]; + + // TODO: Check if required + lsdb.tables[tableid] = true; + + LS.set(lsdbid, lsdb); + LS.storeTable(databaseid, tableid); + + if (cb) res = cb(res); + return res; + }; + + /** Empty table and reset identities @param databaseid {string} AlaSQL database id (not external localStorage) @param tableid {string} Table name @@ -253,39 +255,39 @@ LS.createTable = function (databaseid, tableid, ifnotexists, cb) { @param cb {function} Callback @return 1 on success */ -LS.truncateTable = function (databaseid, tableid, ifexists, cb) { - var res = 1; - var lsdbid = alasql.databases[databaseid].lsdbid; - var lsdb; - if (alasql.options.autocommit) { - lsdb = LS.get(lsdbid); - } else { - lsdb = alasql.databases[databaseid]; - } - - if (!ifexists && !lsdb.tables[tableid]) { - throw new Error( - 'Cannot truncate table "' + tableid + '" in localStorage, because it does not exist' - ); - } - - //load table - var tbl = LS.restoreTable(databaseid, tableid); - - //clear data from table - tbl.data = []; - //TODO reset all identities - //but identities are not working on LOCALSTORAGE - //See test 607 for details - - //store table - LS.storeTable(databaseid, tableid); - - if (cb) res = cb(res); - return res; -}; - -/** + LS.truncateTable = function (databaseid, tableid, ifexists, cb) { + var res = 1; + var lsdbid = alasql.databases[databaseid].lsdbid; + var lsdb; + if (alasql.options.autocommit) { + lsdb = LS.get(lsdbid); + } else { + lsdb = alasql.databases[databaseid]; + } + + if (!ifexists && !lsdb.tables[tableid]) { + throw new Error( + 'Cannot truncate table "' + tableid + '" in localStorage, because it does not exist' + ); + } + + //load table + var tbl = LS.restoreTable(databaseid, tableid); + + //clear data from table + tbl.data = []; + //TODO reset all identities + //but identities are not working on LOCALSTORAGE + //See test 607 for details + + //store table + LS.storeTable(databaseid, tableid); + + if (cb) res = cb(res); + return res; + }; + + /** Create table in localStorage database @param databaseid {string} AlaSQL database id @param tableid {string} Table id @@ -293,45 +295,45 @@ LS.truncateTable = function (databaseid, tableid, ifexists, cb) { @param cb {function} Callback */ -LS.dropTable = function (databaseid, tableid, ifexists, cb) { - var res = 1; - var lsdbid = alasql.databases[databaseid].lsdbid; - var lsdb; - - if (alasql.options.autocommit) { - lsdb = LS.get(lsdbid); - } else { - lsdb = alasql.databases[databaseid]; - } - if (!ifexists && !lsdb.tables[tableid]) { - throw new Error( - 'Cannot drop table "' + tableid + '" in localStorage, because it does not exist' - ); - } - delete lsdb.tables[tableid]; - LS.set(lsdbid, lsdb); - // localStorage.removeItem(lsdbid+'.'+tableid); - LS.removeTable(databaseid, tableid); - if (cb) res = cb(res); - return res; -}; - -/** + LS.dropTable = function (databaseid, tableid, ifexists, cb) { + var res = 1; + var lsdbid = alasql.databases[databaseid].lsdbid; + var lsdb; + + if (alasql.options.autocommit) { + lsdb = LS.get(lsdbid); + } else { + lsdb = alasql.databases[databaseid]; + } + if (!ifexists && !lsdb.tables[tableid]) { + throw new Error( + 'Cannot drop table "' + tableid + '" in localStorage, because it does not exist' + ); + } + delete lsdb.tables[tableid]; + LS.set(lsdbid, lsdb); + // localStorage.removeItem(lsdbid+'.'+tableid); + LS.removeTable(databaseid, tableid); + if (cb) res = cb(res); + return res; + }; + + /** Read all data from table */ -LS.fromTable = function (databaseid, tableid, cb, idx, query) { - // console.log(998, databaseid, tableid, cb); - var lsdbid = alasql.databases[databaseid].lsdbid; - // var res = LS.get(lsdbid+'.'+tableid); + LS.fromTable = function (databaseid, tableid, cb, idx, query) { + // console.log(998, databaseid, tableid, cb); + var lsdbid = alasql.databases[databaseid].lsdbid; + // var res = LS.get(lsdbid+'.'+tableid); - var res = LS.restoreTable(databaseid, tableid).data; + var res = LS.restoreTable(databaseid, tableid).data; - if (cb) res = cb(res, idx, query); - return res; -}; + if (cb) res = cb(res, idx, query); + return res; + }; -/** + /** Insert data into the table @param databaseid {string} Database id @param tableid {string} Table id @@ -340,118 +342,118 @@ LS.fromTable = function (databaseid, tableid, cb, idx, query) { @param cb {function} Callback */ -LS.intoTable = function (databaseid, tableid, value, columns, cb) { - // console.log('intoTable',databaseid, tableid, value, cb); - var lsdbid = alasql.databases[databaseid].lsdbid; - var res = value.length; - // var tb = LS.get(lsdbid+'.'+tableid); - var tb = LS.restoreTable(databaseid, tableid); - for (var columnid in tb.identities) { - var ident = tb.identities[columnid]; - - for (var index in value) { - value[index][columnid] = ident.value; - ident.value += ident.step; + LS.intoTable = function (databaseid, tableid, value, columns, cb) { + // console.log('intoTable',databaseid, tableid, value, cb); + var lsdbid = alasql.databases[databaseid].lsdbid; + var res = value.length; + // var tb = LS.get(lsdbid+'.'+tableid); + var tb = LS.restoreTable(databaseid, tableid); + for (var columnid in tb.identities) { + var ident = tb.identities[columnid]; + + for (var index in value) { + value[index][columnid] = ident.value; + ident.value += ident.step; + } } - } - if (!tb.data) tb.data = []; - tb.data = tb.data.concat(value); - LS.storeTable(databaseid, tableid); - if (cb) res = cb(res); - //console.log(167,res); - return res; -}; - -/** + if (!tb.data) tb.data = []; + tb.data = tb.data.concat(value); + LS.storeTable(databaseid, tableid); + if (cb) res = cb(res); + //console.log(167,res); + return res; + }; + + /** Laad data from table */ -LS.loadTableData = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var lsdbid = alasql.databases[databaseid].lsdbid; - LS.restoreTable(databaseid, tableid); - // db.tables[tableid].data = LS.get(lsdbid+'.'+tableid); -}; - -/** + LS.loadTableData = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var lsdbid = alasql.databases[databaseid].lsdbid; + LS.restoreTable(databaseid, tableid); + // db.tables[tableid].data = LS.get(lsdbid+'.'+tableid); + }; + + /** Save data to the table */ -LS.saveTableData = function (databaseid, tableid) { - var db = alasql.databases[databaseid]; - var lsdbid = alasql.databases[databaseid].lsdbid; - LS.storeTable(databaseid, tableid); - // LS.set(lsdbid+'.'+tableid,db.tables[tableid].data); - db.tables[tableid].data = undefined; -}; + LS.saveTableData = function (databaseid, tableid) { + var db = alasql.databases[databaseid]; + var lsdbid = alasql.databases[databaseid].lsdbid; + LS.storeTable(databaseid, tableid); + // LS.set(lsdbid+'.'+tableid,db.tables[tableid].data); + db.tables[tableid].data = undefined; + }; -/** + /** Commit */ -LS.commit = function (databaseid, cb) { - // console.log('COMMIT'); - var db = alasql.databases[databaseid]; - var lsdbid = alasql.databases[databaseid].lsdbid; - var lsdb = {databaseid: lsdbid, tables: {}}; - if (db.tables) { - for (var tbid in db.tables) { - // TODO: Question - do we need this line - lsdb.tables[tbid] = true; - LS.storeTable(databaseid, tbid); - // LS.set(lsdbid+'.'+tbid, db.tables[tbid].data); + LS.commit = function (databaseid, cb) { + // console.log('COMMIT'); + var db = alasql.databases[databaseid]; + var lsdbid = alasql.databases[databaseid].lsdbid; + var lsdb = {databaseid: lsdbid, tables: {}}; + if (db.tables) { + for (var tbid in db.tables) { + // TODO: Question - do we need this line + lsdb.tables[tbid] = true; + LS.storeTable(databaseid, tbid); + // LS.set(lsdbid+'.'+tbid, db.tables[tbid].data); + } } - } - LS.set(lsdbid, lsdb); - return cb ? cb(1) : 1; -}; + LS.set(lsdbid, lsdb); + return cb ? cb(1) : 1; + }; -/** + /** Alias BEGIN = COMMIT */ -LS.begin = LS.commit; + LS.begin = LS.commit; -/** + /** ROLLBACK */ -LS.rollback = function (databaseid, cb) { - // This does not work and should be fixed - // Plus test 151 and 231 - - return; - - // console.log(207,databaseid); - var db = alasql.databases[databaseid]; - db.dbversion++; - // console.log(db.dbversion) - var lsdbid = alasql.databases[databaseid].lsdbid; - var lsdb = LS.get(lsdbid); - // if(!alasql.options.autocommit) { - - delete alasql.databases[databaseid]; - alasql.databases[databaseid] = new alasql.Database(databaseid); - extend(alasql.databases[databaseid], lsdb); - alasql.databases[databaseid].databaseid = databaseid; - alasql.databases[databaseid].engineid = 'LOCALSTORAGE'; - - if (lsdb.tables) { - for (var tbid in lsdb.tables) { - // var tb = new alasql.Table({columns: db.tables[tbid].columns}); - // extend(tb,lsdb.tables[tbid]); - // lsdb.tables[tbid] = true; - - // if(!alasql.options.autocommit) { - - // lsdb.tables[tbid].data = LS.get(db.lsdbid+'.'+tbid); - LS.restoreTable(databaseid, tbid); - // } - // lsdb.tables[tbid].indexColumns(); - - // index columns - // convert types + LS.rollback = function (databaseid, cb) { + // This does not work and should be fixed + // Plus test 151 and 231 + + return; + + // console.log(207,databaseid); + var db = alasql.databases[databaseid]; + db.dbversion++; + // console.log(db.dbversion) + var lsdbid = alasql.databases[databaseid].lsdbid; + var lsdb = LS.get(lsdbid); + // if(!alasql.options.autocommit) { + + delete alasql.databases[databaseid]; + alasql.databases[databaseid] = new alasql.Database(databaseid); + extend(alasql.databases[databaseid], lsdb); + alasql.databases[databaseid].databaseid = databaseid; + alasql.databases[databaseid].engineid = 'LOCALSTORAGE'; + + if (lsdb.tables) { + for (var tbid in lsdb.tables) { + // var tb = new alasql.Table({columns: db.tables[tbid].columns}); + // extend(tb,lsdb.tables[tbid]); + // lsdb.tables[tbid] = true; + + // if(!alasql.options.autocommit) { + + // lsdb.tables[tbid].data = LS.get(db.lsdbid+'.'+tbid); + LS.restoreTable(databaseid, tbid); + // } + // lsdb.tables[tbid].indexColumns(); + + // index columns + // convert types + } } - } - // } - //console.log(999, alasql.databases[databaseid]); -}; + // } + //console.log(999, alasql.databases[databaseid]); + }; } diff --git a/src/storage/sqljs.js b/src/storage/sqljs.js index 74544f923d..f9e1641605 100755 --- a/src/storage/sqljs.js +++ b/src/storage/sqljs.js @@ -1,173 +1,174 @@ export default function install(alasql) { var yy = alasql.yy; -// -// 93sqlite.js -// SQLite database support -// (c) 2014, Andrey Gershun -// - -var SQLITE = (alasql.engines.SQLITE = function () {}); - -function getSqlJs(cb, errorcb) { - let sqljs = SQLITE.sqljs; - let sqljsPromise = SQLITE.sqljsPromise; - const globalSQL = alasql.utils.global.SQL; - - if (sqljs && sqljs.Database) { - cb(sqljs); - return; - } + // + // 93sqlite.js + // SQLite database support + // (c) 2014, Andrey Gershun + // - if (globalSQL && globalSQL.Database) { - SQLITE.sqljs = sqljs = globalSQL; - cb(sqljs); - return; - } + var SQLITE = (alasql.engines.SQLITE = function () {}); - const initSqlJs = (typeof globalSQL === 'function' && globalSQL) || alasql.utils.global.initSqlJs; + function getSqlJs(cb, errorcb) { + let sqljs = SQLITE.sqljs; + let sqljsPromise = SQLITE.sqljsPromise; + const globalSQL = alasql.utils.global.SQL; - if (!initSqlJs) { - const err = new Error('SQL.js library is not loaded'); - if (errorcb) { - errorcb(err); + if (sqljs && sqljs.Database) { + cb(sqljs); return; } - throw err; - } - if (!sqljsPromise) { - const initResult = initSqlJs(); - const initPromise = - initResult && typeof initResult.then === 'function' - ? initResult - : Promise.resolve(initResult); - SQLITE.sqljsPromise = sqljsPromise = initPromise - .then(function (sqljsModule) { - if (!sqljsModule || !sqljsModule.Database) { - throw new Error('SQL.js library did not expose a Database constructor'); - } - SQLITE.sqljs = sqljs = sqljsModule; - return sqljsModule; - }) - .catch(function (err) { - SQLITE.sqljs = null; - SQLITE.sqljsPromise = null; - throw err; - }); - } + if (globalSQL && globalSQL.Database) { + SQLITE.sqljs = sqljs = globalSQL; + cb(sqljs); + return; + } - sqljsPromise.then(cb, errorcb); -} + const initSqlJs = + (typeof globalSQL === 'function' && globalSQL) || alasql.utils.global.initSqlJs; -SQLITE.createDatabase = function (wdbid, args, ifnotexists, dbid, cb) { - throw new Error('Connot create SQLITE database in memory. Attach it.'); -}; + if (!initSqlJs) { + const err = new Error('SQL.js library is not loaded'); + if (errorcb) { + errorcb(err); + return; + } + throw err; + } -SQLITE.dropDatabase = function (databaseid) { - throw new Error('This is impossible to drop SQLite database. Detach it.'); -}; + if (!sqljsPromise) { + const initResult = initSqlJs(); + const initPromise = + initResult && typeof initResult.then === 'function' + ? initResult + : Promise.resolve(initResult); + SQLITE.sqljsPromise = sqljsPromise = initPromise + .then(function (sqljsModule) { + if (!sqljsModule || !sqljsModule.Database) { + throw new Error('SQL.js library did not expose a Database constructor'); + } + SQLITE.sqljs = sqljs = sqljsModule; + return sqljsModule; + }) + .catch(function (err) { + SQLITE.sqljs = null; + SQLITE.sqljsPromise = null; + throw err; + }); + } -SQLITE.attachDatabase = function (sqldbid, dbid, args, params, cb) { - var res = 1; - if (alasql.databases[dbid]) { - throw new Error('Unable to attach database as "' + dbid + '" because it already exists'); + sqljsPromise.then(cb, errorcb); } - if ((args[0] && args[0] instanceof yy.StringValue) || args[0] instanceof yy.ParamValue) { - let value; - if (args[0] instanceof yy.StringValue) { - value = args[0].value; - } else if (args[0] instanceof yy.ParamValue) { - value = params[args[0].param]; + SQLITE.createDatabase = function (wdbid, args, ifnotexists, dbid, cb) { + throw new Error('Connot create SQLITE database in memory. Attach it.'); + }; + + SQLITE.dropDatabase = function (databaseid) { + throw new Error('This is impossible to drop SQLite database. Detach it.'); + }; + + SQLITE.attachDatabase = function (sqldbid, dbid, args, params, cb) { + var res = 1; + if (alasql.databases[dbid]) { + throw new Error('Unable to attach database as "' + dbid + '" because it already exists'); } - alasql.utils.loadBinaryFile( - value, - true, - function (data) { - getSqlJs( - function (SQL) { - const db = new alasql.Database(dbid || sqldbid); - db.engineid = 'SQLITE'; - db.sqldbid = sqldbid; - const sqldb = (db.sqldb = new SQL.Database(data)); - db.tables = []; - const tables = sqldb.exec("SELECT * FROM sqlite_master WHERE type='table'")[0].values; - - tables.forEach(function (tbl) { - db.tables[tbl[1]] = {}; - const columns = (db.tables[tbl[1]].columns = []); - const ast = alasql.parse(tbl[4]); - const coldefs = ast.statements[0].columns; - if (coldefs && coldefs.length > 0) { - coldefs.forEach(function (cd) { - columns.push(cd); - }); - } - }); - - cb(1); - }, - function (err) { - cb(null, err); - } - ); - }, - function (err) { - const fileError = new Error('Cannot open SQLite database file "' + args[0].value + '"'); - fileError.cause = err; - cb(null, fileError); - } - ); - return res; - } else { - throw new Error('Cannot attach SQLite database without a file'); - } - return res; -}; + if ((args[0] && args[0] instanceof yy.StringValue) || args[0] instanceof yy.ParamValue) { + let value; + if (args[0] instanceof yy.StringValue) { + value = args[0].value; + } else if (args[0] instanceof yy.ParamValue) { + value = params[args[0].param]; + } + alasql.utils.loadBinaryFile( + value, + true, + function (data) { + getSqlJs( + function (SQL) { + const db = new alasql.Database(dbid || sqldbid); + db.engineid = 'SQLITE'; + db.sqldbid = sqldbid; + const sqldb = (db.sqldb = new SQL.Database(data)); + db.tables = []; + const tables = sqldb.exec("SELECT * FROM sqlite_master WHERE type='table'")[0].values; + + tables.forEach(function (tbl) { + db.tables[tbl[1]] = {}; + const columns = (db.tables[tbl[1]].columns = []); + const ast = alasql.parse(tbl[4]); + const coldefs = ast.statements[0].columns; + if (coldefs && coldefs.length > 0) { + coldefs.forEach(function (cd) { + columns.push(cd); + }); + } + }); + + cb(1); + }, + function (err) { + cb(null, err); + } + ); + }, + function (err) { + const fileError = new Error('Cannot open SQLite database file "' + args[0].value + '"'); + fileError.cause = err; + cb(null, fileError); + } + ); + return res; + } else { + throw new Error('Cannot attach SQLite database without a file'); + } -SQLITE.fromTable = function (databaseid, tableid, cb, idx, query) { - var data = alasql.databases[databaseid].sqldb.exec('SELECT * FROM ' + tableid); - var columns = (query.sources[idx].columns = []); - if (data[0].columns.length > 0) { - data[0].columns.forEach(function (columnid) { - columns.push({columnid: columnid}); - }); - } + return res; + }; + + SQLITE.fromTable = function (databaseid, tableid, cb, idx, query) { + var data = alasql.databases[databaseid].sqldb.exec('SELECT * FROM ' + tableid); + var columns = (query.sources[idx].columns = []); + if (data[0].columns.length > 0) { + data[0].columns.forEach(function (columnid) { + columns.push({columnid: columnid}); + }); + } - var res = []; - if (data[0].values.length > 0) { - data[0].values.forEach(function (d) { - var r = {}; - columns.forEach(function (col, idx) { - r[col.columnid] = d[idx]; + var res = []; + if (data[0].values.length > 0) { + data[0].values.forEach(function (d) { + var r = {}; + columns.forEach(function (col, idx) { + r[col.columnid] = d[idx]; + }); + res.push(r); }); - res.push(r); - }); - } - if (cb) cb(res, idx, query); -}; - -SQLITE.intoTable = function (databaseid, tableid, value, columns, cb) { - var sqldb = alasql.databases[databaseid].sqldb; - for (var i = 0, ilen = value.length; i < ilen; i++) { - var s = 'INSERT INTO ' + tableid + ' ('; - var d = value[i]; - var keys = Object.keys(d); - s += keys.join(','); - s += ') VALUES ('; - s += keys - .map(function (k) { - var v = d[k]; - if (typeof v == 'string') v = "'" + v + "'"; - return v; - }) - .join(','); - s += ')'; - sqldb.exec(s); - } - var res = ilen; - if (cb) cb(res); - return res; -}; + } + if (cb) cb(res, idx, query); + }; + + SQLITE.intoTable = function (databaseid, tableid, value, columns, cb) { + var sqldb = alasql.databases[databaseid].sqldb; + for (var i = 0, ilen = value.length; i < ilen; i++) { + var s = 'INSERT INTO ' + tableid + ' ('; + var d = value[i]; + var keys = Object.keys(d); + s += keys.join(','); + s += ') VALUES ('; + s += keys + .map(function (k) { + var v = d[k]; + if (typeof v == 'string') v = "'" + v + "'"; + return v; + }) + .join(','); + s += ')'; + sqldb.exec(s); + } + var res = ilen; + if (cb) cb(res); + return res; + }; } diff --git a/src/utils/env.js b/src/utils/env.js index 73e8a09669..39cefe6eb7 100644 --- a/src/utils/env.js +++ b/src/utils/env.js @@ -1,186 +1,186 @@ export default function install(alasql) { var utils = alasql.utils; -/* + /* Environment detection and the path of the library, split from src/15utility.js (docs/src-map.md). Same functions, same bodies. */ -/** + /** Get the global scope Inspired by System.global @return {object} The global scope */ -utils.global = (function () { - if (typeof self !== 'undefined') { - return self; - } - if (typeof window !== 'undefined') { - return window; - } - if (typeof global !== 'undefined') { - return global; - } - return Function('return this')(); -})(); - -/** + utils.global = (function () { + if (typeof self !== 'undefined') { + return self; + } + if (typeof window !== 'undefined') { + return window; + } + if (typeof global !== 'undefined') { + return global; + } + return Function('return this')(); + })(); + + /** Find out if a function is native to the enviroment @param {function} Function to check @return {boolean} True if function is native */ -var isNativeFunction = (utils.isNativeFunction = function (fn) { - return typeof fn === 'function' && !!~fn.toString().indexOf('[native code]'); -}); + var isNativeFunction = (utils.isNativeFunction = function (fn) { + return typeof fn === 'function' && !!~fn.toString().indexOf('[native code]'); + }); -/** + /** Find out if code is running in a web worker enviroment @return {boolean} True if code is running in a web worker enviroment */ -utils.isWebWorker = (function () { - try { - var importScripts = utils.global.importScripts; - return utils.isNativeFunction(importScripts); - } catch (e) { - return false; - } -})(); - -/** + utils.isWebWorker = (function () { + try { + var importScripts = utils.global.importScripts; + return utils.isNativeFunction(importScripts); + } catch (e) { + return false; + } + })(); + + /** Find out if code is running in a node enviroment @return {boolean} True if code is running in a node enviroment */ -utils.isNode = (function () { - try { - if (typeof process === 'undefined' || !process.versions || !process.versions.node) { + utils.isNode = (function () { + try { + if (typeof process === 'undefined' || !process.versions || !process.versions.node) { + return false; + } + return true; + } catch (e) { return false; } - return true; - } catch (e) { - return false; - } -})(); + })(); -/** + /** Find out if code is running in a browser enviroment @return {boolean} True if code is running in a browser enviroment */ -utils.isBrowser = (function () { - try { - return utils.isNativeFunction(utils.global.location.reload); - } catch (e) { - return false; - } -})(); - -/** + utils.isBrowser = (function () { + try { + return utils.isNativeFunction(utils.global.location.reload); + } catch (e) { + return false; + } + })(); + + /** Find out if code is running in a browser with a browserify setup @return {boolean} True if code is running in a browser with a browserify setup */ -utils.isBrowserify = (function () { - return utils.isBrowser && typeof process !== 'undefined' && process.browser; -})(); + utils.isBrowserify = (function () { + return utils.isBrowser && typeof process !== 'undefined' && process.browser; + })(); -/** + /** Find out if code is running in a browser with a requireJS setup @return {boolean} True if code is running in a browser with a requireJS setup */ -utils.isRequireJS = (function () { - return ( - utils.isBrowser && typeof require === 'function' && typeof require.specified === 'function' - ); -})(); + utils.isRequireJS = (function () { + return ( + utils.isBrowser && typeof require === 'function' && typeof require.specified === 'function' + ); + })(); -/** + /** Find out if code is running with Meteor in the enviroment @return {boolean} True if code is running with Meteor in the enviroment @todo Find out if this is the best way to do this */ -utils.isMeteor = (function () { - return typeof Meteor !== 'undefined' && Meteor.release; -})(); + utils.isMeteor = (function () { + return typeof Meteor !== 'undefined' && Meteor.release; + })(); -/** + /** Find out if code is running on a Meteor client @return {boolean} True if code is running on a Meteor client */ -utils.isMeteorClient = utils.isMeteorClient = (function () { - return utils.isMeteor && Meteor.isClient; -})(); + utils.isMeteorClient = utils.isMeteorClient = (function () { + return utils.isMeteor && Meteor.isClient; + })(); -/** + /** Find out if code is running on a Meteor server @return {boolean} True if code is running on a Meteor server */ -utils.isMeteorServer = (function () { - return utils.isMeteor && Meteor.isServer; -})(); + utils.isMeteorServer = (function () { + return utils.isMeteor && Meteor.isServer; + })(); -/** + /** Find out code is running in a cordovar enviroment @return {boolean} True if code is running in a web worker enviroment @todo Find out if this is the best way to do this */ -utils.isCordova = (function () { - return typeof cordova === 'object'; -})(); - -utils.isReactNative = (function () { - var isReact = false; - //*not-for-browser/* - try { - if (typeof require('react-native') === 'object') { - isReact = true; + utils.isCordova = (function () { + return typeof cordova === 'object'; + })(); + + utils.isReactNative = (function () { + var isReact = false; + //*not-for-browser/* + try { + if (typeof require('react-native') === 'object') { + isReact = true; + } + } catch (e) { + void 0; } - } catch (e) { - void 0; - } - //*/ - return isReact; -})(); - -utils.hasIndexedDB = (function () { - return !!utils.global.indexedDB; -})(); - -utils.isArray = function (obj) { - return '[object Array]' === Object.prototype.toString.call(obj); -}; - -/** + //*/ + return isReact; + })(); + + utils.hasIndexedDB = (function () { + return !!utils.global.indexedDB; + })(); + + utils.isArray = function (obj) { + return '[object Array]' === Object.prototype.toString.call(obj); + }; + + /** Get path of alasql.js @todo Rewrite and simplify the code. Review, is this function is required separately */ -utils.findAlaSQLPath = function () { - /** type {string} Path to alasql library and plugins */ - - if (utils.isWebWorker) { - return ''; - /** @todo Check how to get path in worker */ - } else if (utils.isMeteorClient) { - return '/packages/dist/'; - } else if (utils.isMeteorServer) { - return 'assets/packages/dist/'; - } else if (utils.isNode) { - return __dirname; - } else if (utils.isBrowser) { - var sc = document.getElementsByTagName('script'); - - for (var i = 0; i < sc.length; i++) { - if (sc[i].src.substr(-16).toLowerCase() === 'alasql-worker.js') { - return sc[i].src.substr(0, sc[i].src.length - 16); - } else if (sc[i].src.substr(-20).toLowerCase() === 'alasql-worker.min.js') { - return sc[i].src.substr(0, sc[i].src.length - 20); - } else if (sc[i].src.substr(-9).toLowerCase() === 'alasql.js') { - return sc[i].src.substr(0, sc[i].src.length - 9); - } else if (sc[i].src.substr(-13).toLowerCase() === 'alasql.min.js') { - return sc[i].src.substr(0, sc[i].src.length - 13); + utils.findAlaSQLPath = function () { + /** type {string} Path to alasql library and plugins */ + + if (utils.isWebWorker) { + return ''; + /** @todo Check how to get path in worker */ + } else if (utils.isMeteorClient) { + return '/packages/dist/'; + } else if (utils.isMeteorServer) { + return 'assets/packages/dist/'; + } else if (utils.isNode) { + return __dirname; + } else if (utils.isBrowser) { + var sc = document.getElementsByTagName('script'); + + for (var i = 0; i < sc.length; i++) { + if (sc[i].src.substr(-16).toLowerCase() === 'alasql-worker.js') { + return sc[i].src.substr(0, sc[i].src.length - 16); + } else if (sc[i].src.substr(-20).toLowerCase() === 'alasql-worker.min.js') { + return sc[i].src.substr(0, sc[i].src.length - 20); + } else if (sc[i].src.substr(-9).toLowerCase() === 'alasql.js') { + return sc[i].src.substr(0, sc[i].src.length - 9); + } else if (sc[i].src.substr(-13).toLowerCase() === 'alasql.min.js') { + return sc[i].src.substr(0, sc[i].src.length - 13); + } } } - } - return ''; -}; + return ''; + }; -// set AlaSQl path -alasql.path = alasql.utils.findAlaSQLPath(); + // set AlaSQl path + alasql.path = alasql.utils.findAlaSQLPath(); } diff --git a/src/utils/fileio.js b/src/utils/fileio.js index 5ded4e153f..12feef12fe 100644 --- a/src/utils/fileio.js +++ b/src/utils/fileio.js @@ -4,24 +4,24 @@ export default function install(alasql) { function saveAs() { return alasql.utils.saveAs.apply(this, arguments); } -/* + /* File reading and writing for Node, the browser, React Native, Cordova and Meteor, split from src/15utility.js (docs/src-map.md). Same functions, same bodies. */ -/** + /** Cut BOM first character for UTF-8 files (for merging two files) @param {string} s Source string @return {string} Replaced string */ -var cutbom = function (s) { - if (s[0] === String.fromCharCode(65279)) { - s = s.substr(1); - } - return s; -}; + var cutbom = function (s) { + if (s[0] === String.fromCharCode(65279)) { + s = s.substr(1); + } + return s; + }; -/** + /** Load text file from anywhere @param {string|object} path File path or HTML event @param {boolean} asy True - async call, false - sync call @@ -33,110 +33,110 @@ var cutbom = function (s) { @todo Smaller if-else structures. */ -const protocolRegex = /^[a-z]+:\/\//i; -let loadFile = (utils.loadFile = function (path, asy, success, error) { - var data, fs; - if (utils.isNode || utils.isMeteorServer) { - //*not-for-browser/* - fs = require('fs'); - - // If path is empty, than read data from stdin (for Node) - if ([null, undefined].includes(path)) { - var buff = ''; - process.stdin.setEncoding('utf8'); - process.stdin.on('readable', function () { - var chunk = process.stdin.read(); - if (chunk !== null) { - buff += chunk.toString(); - } - }); - process.stdin.on('end', function () { - success(cutbom(buff)); - }); - return; - } + const protocolRegex = /^[a-z]+:\/\//i; + let loadFile = (utils.loadFile = function (path, asy, success, error) { + var data, fs; + if (utils.isNode || utils.isMeteorServer) { + //*not-for-browser/* + fs = require('fs'); + + // If path is empty, than read data from stdin (for Node) + if ([null, undefined].includes(path)) { + var buff = ''; + process.stdin.setEncoding('utf8'); + process.stdin.on('readable', function () { + var chunk = process.stdin.read(); + if (chunk !== null) { + buff += chunk.toString(); + } + }); + process.stdin.on('end', function () { + success(cutbom(buff)); + }); + return; + } - if (protocolRegex.test(path)) { - fetchData(path, x => success(cutbom(x)), error, asy); - return; - } + if (protocolRegex.test(path)) { + fetchData(path, x => success(cutbom(x)), error, asy); + return; + } - //If async callthen call async - if (asy) { - fs.readFile(path, function (err, data) { - if (err) { - return error(err, null); - } - success(cutbom(data.toString())); - }); + //If async callthen call async + if (asy) { + fs.readFile(path, function (err, data) { + if (err) { + return error(err, null); + } + success(cutbom(data.toString())); + }); + return; + } + + // Call sync version + try { + data = fs.readFileSync(path); + } catch (e) { + error(e, null); + return; + } + + success(cutbom(data.toString())); return; } - // Call sync version - try { - data = fs.readFileSync(path); - } catch (e) { - error(e, null); + if (utils.isReactNative) { + // If ReactNative + var RNFS = require('react-native-fs'); + RNFS.readFile(path, 'utf8') + .then(function (contents) { + success(cutbom(contents)); + }) + .catch(function (err) { + return error(err, null); + }); + //*/ return; } - success(cutbom(data.toString())); - return; - } - - if (utils.isReactNative) { - // If ReactNative - var RNFS = require('react-native-fs'); - RNFS.readFile(path, 'utf8') - .then(function (contents) { - success(cutbom(contents)); - }) - .catch(function (err) { - return error(err, null); - }); - //*/ - return; - } - - if (utils.isCordova) { - /* If Cordova */ - utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { - fileSystem.root.getFile(path, {create: false}, function (fileEntry) { - fileEntry.file(function (file) { - var fileReader = new FileReader(); - fileReader.onloadend = function (e) { - success(cutbom(this.result)); - }; - fileReader.readAsText(file); + if (utils.isCordova) { + /* If Cordova */ + utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { + fileSystem.root.getFile(path, {create: false}, function (fileEntry) { + fileEntry.file(function (file) { + var fileReader = new FileReader(); + fileReader.onloadend = function (e) { + success(cutbom(this.result)); + }; + fileReader.readAsText(file); + }); }); }); - }); - return; - } + return; + } - /* For string */ - if (typeof path === 'string') { - // For browser read from tag - /* + /* For string */ + if (typeof path === 'string') { + // For browser read from tag + /* SELECT * FROM TXT('#one') -- read data from HTML element with id="one" */ - if (path.substr(0, 1) === '#' && typeof document !== 'undefined') { - data = document.querySelector(path).textContent; - success(data); - return; - } + if (path.substr(0, 1) === '#' && typeof document !== 'undefined') { + data = document.querySelector(path).textContent; + success(data); + return; + } - /* + /* Simply read file from HTTP request, like: SELECT * FROM TXT('http://alasql.org/README.md'); */ - fetchData(path, x => success(cutbom(x)), error, asy); - return; - } + fetchData(path, x => success(cutbom(x)), error, asy); + return; + } - if (path instanceof Event) { - /* + if (path instanceof Event) { + /* For browser read from files input element */ - /** @type {array} List of files from element */ - var files = path.target.files; - /** type {object} */ - var reader = new FileReader(); - /** type {string} */ - var name = files[0].name; - reader.onload = function (e) { - var data = e.target.result; - success(cutbom(data)); - }; - reader.readAsText(files[0]); - } - fetchData(path, x => success(cutbom(x)), error, asy); -}); + /** @type {array} List of files from element */ + var files = path.target.files; + /** type {object} */ + var reader = new FileReader(); + /** type {string} */ + var name = files[0].name; + reader.onload = function (e) { + var data = e.target.result; + success(cutbom(data)); + }; + reader.readAsText(files[0]); + } + fetchData(path, x => success(cutbom(x)), error, asy); + }); -let _fetch = typeof fetch !== 'undefined' ? fetch : null; -//*not-for-browser/* -_fetch = typeof fetch !== 'undefined' ? fetch : require('cross-fetch'); -//*/ + let _fetch = typeof fetch !== 'undefined' ? fetch : null; + //*not-for-browser/* + _fetch = typeof fetch !== 'undefined' ? fetch : require('cross-fetch'); + //*/ -async function fetchData(path, success, error, async) { - if (async) { - return getData(path, success, error); + async function fetchData(path, success, error, async) { + if (async) { + return getData(path, success, error); + } + return await getData(path, success, error); } - return await getData(path, success, error); -} -function getData(path, success, error) { - return _fetch(path) - .then(response => response.text()) - .then(txt => { - success(txt); - }) - .catch(e => { - if (error) return error(e); - console.error(e); - throw e; - }); -} + function getData(path, success, error) { + return _fetch(path) + .then(response => response.text()) + .then(txt => { + success(txt); + }) + .catch(e => { + if (error) return error(e); + console.error(e); + throw e; + }); + } -function getBinaryData(path, success, error) { - return _fetch(path) - .then(response => response.arrayBuffer()) - .then(buf => { - var a = new Uint8Array(buf); - var b = [...a].map(e => String.fromCharCode(e)).join(''); - success(b); - }) - .catch(e => { - if (error) return error(e); - console.error(e); - throw e; - }); -} + function getBinaryData(path, success, error) { + return _fetch(path) + .then(response => response.arrayBuffer()) + .then(buf => { + var a = new Uint8Array(buf); + var b = [...a].map(e => String.fromCharCode(e)).join(''); + success(b); + }) + .catch(e => { + if (error) return error(e); + console.error(e); + throw e; + }); + } -/** + /** @function Load binary file from anywhere @param {string} path File path @param {boolean} asy True - async call, false - sync call @@ -211,197 +211,197 @@ function getBinaryData(path, success, error) { @todo merge functionality from loadFile and LoadBinaryFile */ -var loadBinaryFile = (utils.loadBinaryFile = function ( - path, - runAsync, - success, - error = x => { - throw x; - } -) { - var fs; - if (utils.isNode || utils.isMeteorServer) { - //*not-for-browser/* - fs = require('fs'); + var loadBinaryFile = (utils.loadBinaryFile = function ( + path, + runAsync, + success, + error = x => { + throw x; + } + ) { + var fs; + if (utils.isNode || utils.isMeteorServer) { + //*not-for-browser/* + fs = require('fs'); - if (/^[a-z]+:\/\//i.test(path)) { - return getBinaryData(path, success, error); - } else { - if (runAsync) { - fs.readFile(path, function (err, data) { - if (err) { - return error(err); + if (/^[a-z]+:\/\//i.test(path)) { + return getBinaryData(path, success, error); + } else { + if (runAsync) { + fs.readFile(path, function (err, data) { + if (err) { + return error(err); + } + var arr = []; + for (var i = 0; i < data.length; ++i) { + arr[i] = String.fromCharCode(data[i]); + } + success(arr.join('')); + }); + } else { + var data; + try { + data = fs.readFileSync(path); + } catch (e) { + return error(e); } var arr = []; for (var i = 0; i < data.length; ++i) { arr[i] = String.fromCharCode(data[i]); } success(arr.join('')); - }); - } else { - var data; - try { - data = fs.readFileSync(path); - } catch (e) { - return error(e); - } - var arr = []; - for (var i = 0; i < data.length; ++i) { - arr[i] = String.fromCharCode(data[i]); } - success(arr.join('')); + } + } else if (utils.isReactNative) { + // If ReactNative + //var RNFS = require('react-native-fs'); + var RNFetchBlob = require('react-native-fetch-blob').default; + var dirs = RNFetchBlob.fs.dirs; + //should use readStream instead if the file is large + RNFetchBlob.fs + .readFile(path, 'base64') + .then(function (data) { + success(data); + }) + .catch(error); + //*/ + } else { + if (typeof path === 'string') { + // For browser + var xhr = new XMLHttpRequest(); + xhr.open('GET', path, runAsync); // Async + xhr.responseType = 'arraybuffer'; + xhr.onload = function () { + var data = new Uint8Array(xhr.response); + var arr = []; + for (var i = 0; i < data.length; ++i) { + arr[i] = String.fromCharCode(data[i]); + } + success(arr.join('')); + }; + xhr.onerror = error; + xhr.send(); + } else if (path instanceof Event) { + // console.log("event"); + var files = path.target.files; + var reader = new FileReader(); + var name = files[0].name; + reader.onload = function (e) { + var data = e.target.result; + success(data); + }; + reader.onerror = error; + reader.readAsArrayBuffer(files[0]); + } else if (path instanceof Blob) { + success(path); } } - } else if (utils.isReactNative) { - // If ReactNative - //var RNFS = require('react-native-fs'); - var RNFetchBlob = require('react-native-fetch-blob').default; - var dirs = RNFetchBlob.fs.dirs; - //should use readStream instead if the file is large - RNFetchBlob.fs - .readFile(path, 'base64') - .then(function (data) { - success(data); - }) - .catch(error); - //*/ - } else { - if (typeof path === 'string') { - // For browser - var xhr = new XMLHttpRequest(); - xhr.open('GET', path, runAsync); // Async - xhr.responseType = 'arraybuffer'; - xhr.onload = function () { - var data = new Uint8Array(xhr.response); - var arr = []; - for (var i = 0; i < data.length; ++i) { - arr[i] = String.fromCharCode(data[i]); - } - success(arr.join('')); - }; - xhr.onerror = error; - xhr.send(); - } else if (path instanceof Event) { - // console.log("event"); - var files = path.target.files; - var reader = new FileReader(); - var name = files[0].name; - reader.onload = function (e) { - var data = e.target.result; - success(data); - }; - reader.onerror = error; - reader.readAsArrayBuffer(files[0]); - } else if (path instanceof Blob) { - success(path); + }); + + var removeFile = (utils.removeFile = function (path, cb) { + if (utils.isNode) { + //*not-for-browser/* + var fs = require('fs'); + fs.remove(path, cb); + } else if (utils.isCordova) { + utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { + fileSystem.root.getFile( + path, + {create: false}, + function (fileEntry) { + fileEntry.remove(cb); + cb && cb(); // jshint ignore:line + }, + function () { + cb && cb(); // jshint ignore:line + } + ); + }); + } else if (utils.isReactNative) { + // If ReactNative + var RNFS = require('react-native-fs'); + RNFS.unlink(path) + .then(function () { + cb && cb(); + }) + .catch(function (err) { + throw err; + }); + //*/ + } else { + throw new Error('You can remove files only in Node.js and Apache Cordova'); } - } -}); + }); -var removeFile = (utils.removeFile = function (path, cb) { - if (utils.isNode) { + // Todo: check if it makes sense to support cordova and Meteor server + var deleteFile = (utils.deleteFile = function (path, cb) { //*not-for-browser/* - var fs = require('fs'); - fs.remove(path, cb); - } else if (utils.isCordova) { - utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { - fileSystem.root.getFile( - path, - {create: false}, - function (fileEntry) { - fileEntry.remove(cb); - cb && cb(); // jshint ignore:line - }, - function () { - cb && cb(); // jshint ignore:line - } - ); - }); - } else if (utils.isReactNative) { - // If ReactNative - var RNFS = require('react-native-fs'); - RNFS.unlink(path) - .then(function () { - cb && cb(); - }) - .catch(function (err) { - throw err; - }); + if (utils.isNode) { + var fs = require('fs'); + fs.unlink(path, cb); + } else if (utils.isReactNative) { + // If ReactNative + var RNFS = require('react-native-fs'); + RNFS.unlink(path) + .then(function () { + cb && cb(); + }) + .catch(function (err) { + throw err; + }); + } //*/ - } else { - throw new Error('You can remove files only in Node.js and Apache Cordova'); - } -}); - -// Todo: check if it makes sense to support cordova and Meteor server -var deleteFile = (utils.deleteFile = function (path, cb) { - //*not-for-browser/* - if (utils.isNode) { - var fs = require('fs'); - fs.unlink(path, cb); - } else if (utils.isReactNative) { - // If ReactNative - var RNFS = require('react-native-fs'); - RNFS.unlink(path) - .then(function () { - cb && cb(); - }) - .catch(function (err) { - throw err; - }); - } - //*/ -}); - -utils.autoExtFilename = function (filename, ext, config) { - config = config || {}; - if ( - typeof filename !== 'string' || - filename.match(/^[A-Z]+:\/\/|\n|\..{2,6}$/i) || - config.autoExt === 0 || - config.autoExt === false - ) { - return filename; - } - return filename + '.' + ext; -}; + }); + + utils.autoExtFilename = function (filename, ext, config) { + config = config || {}; + if ( + typeof filename !== 'string' || + filename.match(/^[A-Z]+:\/\/|\n|\..{2,6}$/i) || + config.autoExt === 0 || + config.autoExt === false + ) { + return filename; + } + return filename + '.' + ext; + }; -var fileExists = (utils.fileExists = function (path, cb) { - if (utils.isNode) { - //*not-for-browser/* - var fs = require('fs'); - fs.exists(path, cb); - } else if (utils.isCordova) { - utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { - fileSystem.root.getFile( - path, - {create: false}, - function (fileEntry) { - cb(true); - }, - function () { - cb(false); - } - ); - }); - } else if (utils.isReactNative) { - // If ReactNative - var RNFS = require('react-native-fs'); - RNFS.exists(path) - .then(function (yes) { - cb && cb(yes); - }) - .catch(function (err) { - throw err; + var fileExists = (utils.fileExists = function (path, cb) { + if (utils.isNode) { + //*not-for-browser/* + var fs = require('fs'); + fs.exists(path, cb); + } else if (utils.isCordova) { + utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { + fileSystem.root.getFile( + path, + {create: false}, + function (fileEntry) { + cb(true); + }, + function () { + cb(false); + } + ); }); - //*/ - } else { - // TODO Cordova, etc. - throw new Error('You can use exists() only in Node.js or Apach Cordova'); - } -}); + } else if (utils.isReactNative) { + // If ReactNative + var RNFS = require('react-native-fs'); + RNFS.exists(path) + .then(function (yes) { + cb && cb(yes); + }) + .catch(function (err) { + throw err; + }); + //*/ + } else { + // TODO Cordova, etc. + throw new Error('You can use exists() only in Node.js or Apach Cordova'); + } + }); -/** + /** Save text file from anywhere @param {string} path File path @param {array} data Data object @@ -409,52 +409,52 @@ var fileExists = (utils.fileExists = function (path, cb) { @param {object=} opts */ -var saveFile = (utils.saveFile = function (path, data, cb, opts) { - var res = 1; - if (path === undefined) { - // - // Return data into result variable - // like: alasql('SELECT * INTO TXT() FROM ?',[data]); - // - res = data; - if (cb) { - res = cb(res); - } - } else { - if (utils.isNode) { - //*not-for-browser/* - var fs = require('fs'); - data = fs.writeFileSync(path, data); + var saveFile = (utils.saveFile = function (path, data, cb, opts) { + var res = 1; + if (path === undefined) { + // + // Return data into result variable + // like: alasql('SELECT * INTO TXT() FROM ?',[data]); + // + res = data; if (cb) { res = cb(res); } - } else if (utils.isReactNative) { - var RNFS = require('react-native-fs'); - RNFS.writeFile(path, data) - .then(function (success) { - //, 'utf8' - if (cb) res = cb(res); - }) - .catch(function (err) { - console.error(err.message); - }); - } else if (utils.isCordova) { - utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { - // alasql.utils.removeFile(path,function(){ - fileSystem.root.getFile(path, {create: true}, function (fileEntry) { - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function () { - if (cb) { - res = cb(res); - } - }; - fileWriter.write(data); + } else { + if (utils.isNode) { + //*not-for-browser/* + var fs = require('fs'); + data = fs.writeFileSync(path, data); + if (cb) { + res = cb(res); + } + } else if (utils.isReactNative) { + var RNFS = require('react-native-fs'); + RNFS.writeFile(path, data) + .then(function (success) { + //, 'utf8' + if (cb) res = cb(res); + }) + .catch(function (err) { + console.error(err.message); + }); + } else if (utils.isCordova) { + utils.global.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) { + // alasql.utils.removeFile(path,function(){ + fileSystem.root.getFile(path, {create: true}, function (fileEntry) { + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function () { + if (cb) { + res = cb(res); + } + }; + fileWriter.write(data); + }); }); }); - }); - //*/ + //*/ - /*/* + /*/* } else if((typeof cordova == 'object') && cordova.file) { // console.log('saveFile 1'); // Cordova @@ -485,7 +485,7 @@ var saveFile = (utils.saveFile = function (path, data, cb, opts) { // console.log("ok, in theory i worked"); }); */ - /*/* + /*/* // Corodva function writeFinish() { // ... your done code here... @@ -508,34 +508,34 @@ var saveFile = (utils.saveFile = function (path, data, cb, opts) { writeNext(writeFinish); } */ - // }); - // }); - // }); - } else { - var opt = { - disableAutoBom: false, - }; - alasql.utils.extend(opt, opts); - var blob = new Blob([data], {type: 'text/plain;charset=utf-8'}); - saveAs(blob, path, opt.disableAutoBom); - if (cb) { - res = cb(res); + // }); + // }); + // }); + } else { + var opt = { + disableAutoBom: false, + }; + alasql.utils.extend(opt, opts); + var blob = new Blob([data], {type: 'text/plain;charset=utf-8'}); + saveAs(blob, path, opt.disableAutoBom); + if (cb) { + res = cb(res); + } } } - } - return res; -}); - -// For LOAD -// var saveBinaryFile = utils.saveFile = function(path, data, cb) { -// if(utils.isNode) { -// // For Node.js -// var fs = require('fs'); -// var data = fs.writeFileSync(path,data); -// } else { -// var blob = new Blob([data], {type: "text/plain;charset=utf-8"}); -// saveAs(blob, path); -// } -// }; + return res; + }); + + // For LOAD + // var saveBinaryFile = utils.saveFile = function(path, data, cb) { + // if(utils.isNode) { + // // For Node.js + // var fs = require('fs'); + // var data = fs.writeFileSync(path,data); + // } else { + // var blob = new Blob([data], {type: "text/plain;charset=utf-8"}); + // saveAs(blob, path); + // } + // }; } diff --git a/src/utils/filesaver.js b/src/utils/filesaver.js index d4c485791b..fff0c97416 100755 --- a/src/utils/filesaver.js +++ b/src/utils/filesaver.js @@ -1,209 +1,208 @@ export default function install(alasql, internal) { var utils = alasql.utils; -if(utils.isBrowser && !utils.isWebWorker) { -/*! FileSaver.js - * A saveAs() FileSaver implementation. - * 1.3.2 - * 2016-06-16 18:25:19 - * - * By Eli Grey, http://eligrey.com - * License: MIT - * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md - */ + if (utils.isBrowser && !utils.isWebWorker) { + /*! FileSaver.js + * A saveAs() FileSaver implementation. + * 1.3.2 + * 2016-06-16 18:25:19 + * + * By Eli Grey, http://eligrey.com + * License: MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ -/*global self */ -/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + /*global self */ + /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ -/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ -var saveAs = - saveAs || - (function (view) { - 'use strict'; - // IE <10 is explicitly unsupported - if ( - typeof view === 'undefined' || - (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) - ) { - return; - } - var doc = view.document, - // only get URL when necessary in case Blob.js hasn't overridden it yet - get_URL = function () { - return view.URL || view.webkitURL || view; - }, - save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'), - can_use_save_link = 'download' in save_link, - click = function (node) { - var event = new MouseEvent('click'); - node.dispatchEvent(event); - }, - is_safari = /constructor/i.test(view.HTMLElement) || view.safari, - is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), - throw_outside = function (ex) { - (view.setImmediate || view.setTimeout)(function () { - throw ex; - }, 0); - }, - force_saveable_type = 'application/octet-stream', - // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to - arbitrary_revoke_timeout = 1000 * 40, // in ms - revoke = function (file) { - var revoker = function () { - if (typeof file === 'string') { - // file is an object URL - get_URL().revokeObjectURL(file); - } else { - // file is a File - file.remove(); - } - }; - setTimeout(revoker, arbitrary_revoke_timeout); - }, - dispatch = function (filesaver, event_types, event) { - event_types = [].concat(event_types); - var i = event_types.length; - while (i--) { - var listener = filesaver['on' + event_types[i]]; - if (typeof listener === 'function') { - try { - listener.call(filesaver, event || filesaver); - } catch (ex) { - throw_outside(ex); - } - } - } - }, - auto_bom = function (blob) { - // prepend BOM for UTF-8 XML and text/* types (including HTML) - // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + var saveAs = + saveAs || + (function (view) { + 'use strict'; + // IE <10 is explicitly unsupported if ( - /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( - blob.type - ) + typeof view === 'undefined' || + (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) ) { - return new Blob([String.fromCharCode(0xfeff), blob], { - type: blob.type, - }); - } - return blob; - }, - FileSaver = function (blob, name, no_auto_bom) { - if (!no_auto_bom) { - blob = auto_bom(blob); + return; } - // First try a.download, then web filesystem, then object URLs - var filesaver = this, - type = blob.type, - force = type === force_saveable_type, - object_url, - dispatch_all = function () { - dispatch(filesaver, 'writestart progress write writeend'.split(' ')); + var doc = view.document, + // only get URL when necessary in case Blob.js hasn't overridden it yet + get_URL = function () { + return view.URL || view.webkitURL || view; + }, + save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'), + can_use_save_link = 'download' in save_link, + click = function (node) { + var event = new MouseEvent('click'); + node.dispatchEvent(event); + }, + is_safari = /constructor/i.test(view.HTMLElement) || view.safari, + is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), + throw_outside = function (ex) { + (view.setImmediate || view.setTimeout)(function () { + throw ex; + }, 0); + }, + force_saveable_type = 'application/octet-stream', + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + arbitrary_revoke_timeout = 1000 * 40, // in ms + revoke = function (file) { + var revoker = function () { + if (typeof file === 'string') { + // file is an object URL + get_URL().revokeObjectURL(file); + } else { + // file is a File + file.remove(); + } + }; + setTimeout(revoker, arbitrary_revoke_timeout); }, - // on any filesys errors revert to saving with object URLs - fs_error = function () { - if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { - // Safari doesn't allow downloading of blob urls - var reader = new FileReader(); - reader.onloadend = function () { - var url = is_chrome_ios - ? reader.result - : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); - var popup = view.open(url, '_blank'); - if (!popup) view.location.href = url; - url = undefined; // release reference before dispatching + dispatch = function (filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver['on' + event_types[i]]; + if (typeof listener === 'function') { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + }, + auto_bom = function (blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if ( + /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( + blob.type + ) + ) { + return new Blob([String.fromCharCode(0xfeff), blob], { + type: blob.type, + }); + } + return blob; + }, + FileSaver = function (blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob); + } + // First try a.download, then web filesystem, then object URLs + var filesaver = this, + type = blob.type, + force = type === force_saveable_type, + object_url, + dispatch_all = function () { + dispatch(filesaver, 'writestart progress write writeend'.split(' ')); + }, + // on any filesys errors revert to saving with object URLs + fs_error = function () { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader(); + reader.onloadend = function () { + var url = is_chrome_ios + ? reader.result + : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + var popup = view.open(url, '_blank'); + if (!popup) view.location.href = url; + url = undefined; // release reference before dispatching + filesaver.readyState = filesaver.DONE; + dispatch_all(); + }; + reader.readAsDataURL(blob); + filesaver.readyState = filesaver.INIT; + return; + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob); + } + if (force) { + view.location.href = object_url; + } else { + var opened = view.open(object_url, '_blank'); + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url; + } + } filesaver.readyState = filesaver.DONE; dispatch_all(); + revoke(object_url); }; - reader.readAsDataURL(blob); - filesaver.readyState = filesaver.INIT; - return; - } - // don't create more object URLs than needed - if (!object_url) { + filesaver.readyState = filesaver.INIT; + + if (can_use_save_link) { object_url = get_URL().createObjectURL(blob); + setTimeout(function () { + save_link.href = object_url; + save_link.download = name; + click(save_link); + dispatch_all(); + revoke(object_url); + filesaver.readyState = filesaver.DONE; + }); + return; } - if (force) { - view.location.href = object_url; - } else { - var opened = view.open(object_url, '_blank'); - if (!opened) { - // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html - view.location.href = object_url; - } - } - filesaver.readyState = filesaver.DONE; - dispatch_all(); - revoke(object_url); - }; - filesaver.readyState = filesaver.INIT; - - if (can_use_save_link) { - object_url = get_URL().createObjectURL(blob); - setTimeout(function () { - save_link.href = object_url; - save_link.download = name; - click(save_link); - dispatch_all(); - revoke(object_url); - filesaver.readyState = filesaver.DONE; - }); - return; - } - fs_error(); - }, - FS_proto = FileSaver.prototype, - saveAs = function (blob, name, no_auto_bom) { - return new FileSaver(blob, name || blob.name || 'download', no_auto_bom); - }; - // IE 10+ (native saveAs) - if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { - return function (blob, name, no_auto_bom) { - name = name || blob.name || 'download'; + fs_error(); + }, + FS_proto = FileSaver.prototype, + saveAs = function (blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || 'download', no_auto_bom); + }; + // IE 10+ (native saveAs) + if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { + return function (blob, name, no_auto_bom) { + name = name || blob.name || 'download'; - if (!no_auto_bom) { - blob = auto_bom(blob); + if (!no_auto_bom) { + blob = auto_bom(blob); + } + return navigator.msSaveOrOpenBlob(blob, name); + }; } - return navigator.msSaveOrOpenBlob(blob, name); - }; - } - - FS_proto.abort = function () {}; - FS_proto.readyState = FS_proto.INIT = 0; - FS_proto.WRITING = 1; - FS_proto.DONE = 2; - FS_proto.error = - FS_proto.onwritestart = - FS_proto.onprogress = - FS_proto.onwrite = - FS_proto.onabort = - FS_proto.onerror = - FS_proto.onwriteend = - null; + FS_proto.abort = function () {}; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; - return saveAs; - })( - (typeof self !== 'undefined' && self) || - (typeof window !== 'undefined' && window) || - this.content - ); -// `self` is undefined in Firefox for Android content script context -// while `this` is nsIContentFrameMessageManager -// with an attribute `content` that corresponds to the window + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; -if (typeof module !== 'undefined' && module.exports) { - module.exports.saveAs = saveAs; -} else if (typeof define !== 'undefined' && define !== null && define.amd !== null) { - define('FileSaver.js', function () { - return saveAs; - }); -} + return saveAs; + })( + (typeof self !== 'undefined' && self) || + (typeof window !== 'undefined' && window) || + this.content + ); + // `self` is undefined in Firefox for Android content script context + // while `this` is nsIContentFrameMessageManager + // with an attribute `content` that corresponds to the window -// FileSaveAs -alasql.utils.saveAs = saveAs; + if (typeof module !== 'undefined' && module.exports) { + module.exports.saveAs = saveAs; + } else if (typeof define !== 'undefined' && define !== null && define.amd !== null) { + define('FileSaver.js', function () { + return saveAs; + }); + } -}; + // FileSaveAs + alasql.utils.saveAs = saveAs; + } internal.saveAs = saveAs; } diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 62a2891dc9..ca41899e0e 100755 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -314,34 +314,34 @@ export var escapeqq = function (s) { }; export default function install(alasql) { -/*jshint unused:false*/ -/* + /*jshint unused:false*/ + /* Utilities for Alasql.js @todo Review the list of utilities @todo Find more effective utilities */ -/** + /** Alasql utility functions @type {object} */ -var utils = (alasql.utils = {}); -utils.extend = extend; -utils.distinctArray = distinctArray; -utils.undoubleq = escapeqq; -utils.arrayUnion = arrayUnion; -utils.arrayDiff = arrayDiff; -utils.arrayIntersect = arrayIntersect; -utils.escapeq = escapeq; -utils.hash = hash; -utils.arrayUnionDeep = arrayUnionDeep; -utils.arrayExceptDeep = arrayExceptDeep; -utils.arrayIntersectDeep = arrayIntersectDeep; -utils.cloneDeep = cloneDeep; -utils.deepEqual = deepEqual; - -/** + var utils = (alasql.utils = {}); + utils.extend = extend; + utils.distinctArray = distinctArray; + utils.undoubleq = escapeqq; + utils.arrayUnion = arrayUnion; + utils.arrayDiff = arrayDiff; + utils.arrayIntersect = arrayIntersect; + utils.escapeq = escapeq; + utils.hash = hash; + utils.arrayUnionDeep = arrayUnionDeep; + utils.arrayExceptDeep = arrayExceptDeep; + utils.arrayIntersectDeep = arrayIntersectDeep; + utils.cloneDeep = cloneDeep; + utils.deepEqual = deepEqual; + + /** Escape string @function @param {string} s Source string @@ -351,9 +351,9 @@ utils.deepEqual = deepEqual; Pit\er's => Pit\\er\'s */ -// based on joliss/js-string-escape + // based on joliss/js-string-escape -/** + /** Replace double quotes with single quote @param {string} s Source string @return {string} Replaced string @@ -362,34 +362,34 @@ utils.deepEqual = deepEqual; Piter''s => Piter's */ -var doubleq = (utils.doubleq = function (s) { - return s.replace(/(\'\')/g, "\\'"); -}); + var doubleq = (utils.doubleq = function (s) { + return s.replace(/(\'\')/g, "\\'"); + }); -/** + /** Replace single quote with escaped single quote @param {string} s Source string @return {string} Replaced string @todo Check this function */ -var doubleqq = (utils.doubleqq = function (s) { - return s.replace(/'/g, "\\'"); -}); + var doubleqq = (utils.doubleqq = function (s) { + return s.replace(/'/g, "\\'"); + }); -/** + /** @function Hash a string to signed integer @param {string} source string @return {integer} hash number */ -// FNV-1a inspired hashing + // FNV-1a inspired hashing -/** + /** Check equality of objects */ -/*/* + /*/* var equalDeep = utils.equalDeep = function equalDeep (x, y, deep) { if (deep) { if (x === y){ @@ -434,109 +434,109 @@ var doubleqq = (utils.doubleqq = function (s) { }; */ -/** - * Extracts the primitive data - */ -var getValueOf = (utils.getValueOf = function (val) { - return typeof val === 'object' && (val instanceof String || val instanceof Number) - ? val.valueOf() - : val; -}); + /** + * Extracts the primitive data + */ + var getValueOf = (utils.getValueOf = function (val) { + return typeof val === 'object' && (val instanceof String || val instanceof Number) + ? val.valueOf() + : val; + }); -/** + /** Flat array by first row */ -var flatArray = (utils.flatArray = function (a) { - //console.log(684,a); - if (!a || 0 === a.length) { - return []; - } + var flatArray = (utils.flatArray = function (a) { + //console.log(684,a); + if (!a || 0 === a.length) { + return []; + } - // For recordsets - if (typeof a === 'object' && a instanceof alasql.Recordset) { - return a.data.map(function (ai) { - return getValueOf(ai[a.columns[0].columnid]); + // For recordsets + if (typeof a === 'object' && a instanceof alasql.Recordset) { + return a.data.map(function (ai) { + return getValueOf(ai[a.columns[0].columnid]); + }); + } + // Else for other arrays + var key = Object.keys(a[0])[0]; + if (key === undefined) { + return []; + } + return a.map(function (ai) { + return ai[key]; }); - } - // Else for other arrays - var key = Object.keys(a[0])[0]; - if (key === undefined) { - return []; - } - return a.map(function (ai) { - return ai[key]; }); -}); -/** + /** Convert array of objects to array of arrays */ -var arrayOfArrays = (utils.arrayOfArrays = function (a) { - return a.map(function (aa) { - var ar = []; - for (var key in aa) { - ar.push(aa[key]); - } - return ar; + var arrayOfArrays = (utils.arrayOfArrays = function (a) { + return a.map(function (aa) { + var ar = []; + for (var key in aa) { + ar.push(aa[key]); + } + return ar; + }); }); -}); -if (!Array.isArray) { - Array.isArray = function (arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; -} + if (!Array.isArray) { + Array.isArray = function (arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + } -/** + /** Excel:convert number to Excel column, like 1 => 'A' @param {integer} i Column number, starting with 0 @return {string} Column name, starting with 'A' */ -var xlsnc = (utils.xlsnc = function (i) { - var first = ''; - if (i > 701) { - let x = (((i - 26) / (26 * 26)) | 0) - 1; - first = String.fromCharCode(65 + (x % 26)); - i = i % (26 * 26); - } - var addr = String.fromCharCode(65 + (i % 26)); - if (i >= 26) { - i = ((i / 26) | 0) - 1; - addr = String.fromCharCode(65 + (i % 26)) + addr; - if (i > 26) { + var xlsnc = (utils.xlsnc = function (i) { + var first = ''; + if (i > 701) { + let x = (((i - 26) / (26 * 26)) | 0) - 1; + first = String.fromCharCode(65 + (x % 26)); + i = i % (26 * 26); + } + var addr = String.fromCharCode(65 + (i % 26)); + if (i >= 26) { i = ((i / 26) | 0) - 1; addr = String.fromCharCode(65 + (i % 26)) + addr; + if (i > 26) { + i = ((i / 26) | 0) - 1; + addr = String.fromCharCode(65 + (i % 26)) + addr; + } } - } - return first + addr; -}); + return first + addr; + }); -/** + /** Excel:conver Excel column name to number @param {string} s Column number, like 'A' or 'BE' @return {string} Column name, starting with 0 */ -var xlscn = (utils.xlscn = function (s) { - var n = s.charCodeAt(0) - 65; - if (s.length > 1) { - n = (n + 1) * 26 + s.charCodeAt(1) - 65; - // console.log(n, s.charCodeAt(0)-65, s.charCodeAt(1)-65); - if (s.length > 2) { - n = (n + 1) * 26 + s.charCodeAt(2) - 65; + var xlscn = (utils.xlscn = function (s) { + var n = s.charCodeAt(0) - 65; + if (s.length > 1) { + n = (n + 1) * 26 + s.charCodeAt(1) - 65; + // console.log(n, s.charCodeAt(0)-65, s.charCodeAt(1)-65); + if (s.length > 2) { + n = (n + 1) * 26 + s.charCodeAt(2) - 65; + } } - } - return n; -}); + return n; + }); -var domEmptyChildren = (utils.domEmptyChildren = function (container) { - var len = container.childNodes.length; - while (len--) { - container.removeChild(container.lastChild); - } -}); + var domEmptyChildren = (utils.domEmptyChildren = function (container) { + var len = container.childNodes.length; + while (len--) { + container.removeChild(container.lastChild); + } + }); -/** + /** SQL LIKE emulation @parameter {string} pattern Search pattern @parameter {string} value Searched value @@ -544,167 +544,167 @@ var domEmptyChildren = (utils.domEmptyChildren = function (container) { @return {boolean} If value LIKE pattern ESCAPE escape */ -/* * Tests if a given value matches a pattern with optional escape character. - * Supports SQL-like syntax with % and _ as wildcards and custom escape character. - */ -var patternCache = new Map(); // escape character -> pattern -> RegExp -var like = (utils.like = function (pattern, value, escape = '') { - var cache = patternCache.get(escape); - if (!cache) patternCache.set(escape, (cache = new Map())); - var re = cache.get(pattern); - if (!re) { - var regexStr = '^'; // Start regex pattern to match from the beginning. - var i = 0; // Index for traversing the pattern string. - - while (i < pattern.length) { - var currentChar = pattern[i]; - var nextChar = i < pattern.length - 1 ? pattern[i + 1] : ''; - - // Handle escape character. - if (currentChar === escape) { - regexStr += '\\' + nextChar; - i++; // Skip next character as it's escaped. + /* * Tests if a given value matches a pattern with optional escape character. + * Supports SQL-like syntax with % and _ as wildcards and custom escape character. + */ + var patternCache = new Map(); // escape character -> pattern -> RegExp + var like = (utils.like = function (pattern, value, escape = '') { + var cache = patternCache.get(escape); + if (!cache) patternCache.set(escape, (cache = new Map())); + var re = cache.get(pattern); + if (!re) { + var regexStr = '^'; // Start regex pattern to match from the beginning. + var i = 0; // Index for traversing the pattern string. + + while (i < pattern.length) { + var currentChar = pattern[i]; + var nextChar = i < pattern.length - 1 ? pattern[i + 1] : ''; + + // Handle escape character. + if (currentChar === escape) { + regexStr += '\\' + nextChar; + i++; // Skip next character as it's escaped. + } + // Handle negation within character classes. + else if (currentChar === '[' && nextChar === '^') { + regexStr += '[^'; + i++; // Include '^' as part of the set. + } + // Directly append square brackets. + else if (currentChar === '[' || currentChar === ']') { + regexStr += currentChar; + } + // Replace '%' with regex to match any character sequence. + else if (currentChar === '%') { + regexStr += '[\\s\\S]*'; + } + // Replace '_' with regex to match any single character. + else if (currentChar === '_') { + regexStr += '.'; + } + // Escape special regex characters. + else if ('/.*+?|(){}'.indexOf(currentChar) > -1) { + regexStr += '\\' + currentChar; + } + // Append literal characters. + else { + regexStr += currentChar; + } + i++; } - // Handle negation within character classes. - else if (currentChar === '[' && nextChar === '^') { - regexStr += '[^'; - i++; // Include '^' as part of the set. - } - // Directly append square brackets. - else if (currentChar === '[' || currentChar === ']') { - regexStr += currentChar; - } - // Replace '%' with regex to match any character sequence. - else if (currentChar === '%') { - regexStr += '[\\s\\S]*'; - } - // Replace '_' with regex to match any single character. - else if (currentChar === '_') { - regexStr += '.'; - } - // Escape special regex characters. - else if ('/.*+?|(){}'.indexOf(currentChar) > -1) { - regexStr += '\\' + currentChar; - } - // Append literal characters. - else { - regexStr += currentChar; - } - i++; - } - regexStr += '$'; // End regex pattern to match until the end. - // Compile and cache the regex pattern for future use. - re = RegExp(regexStr, 'i'); - cache.set(pattern, re); - } + regexStr += '$'; // End regex pattern to match until the end. + // Compile and cache the regex pattern for future use. + re = RegExp(regexStr, 'i'); + cache.set(pattern, re); + } - // Convert value to string (handling null/undefined) and test against compiled pattern. - return ('' + (value ?? '')).search(re) > -1; -}); + // Convert value to string (handling null/undefined) and test against compiled pattern. + return ('' + (value ?? '')).search(re) > -1; + }); -/** - * Tests if a given value matches a glob pattern. - * The function supports '*', '?' as wildcards where '*' matches any sequence of characters, - * and '?' matches any single character. Square brackets can be used for character sets and ranges. - * - * @param {string} value - The string value to test against the glob pattern. - * @param {string} pattern - The glob pattern to match the value against. - * @returns {boolean} - True if the value matches the pattern, false otherwise. - */ -utils.glob = function (value, pattern) { - var currentIndex = 0; // Index for traversing the pattern string. - var regexPattern = '^'; // Start regex pattern to match from the beginning. - - while (currentIndex < pattern.length) { - var currentChar = pattern[currentIndex]; - var nextChar = currentIndex < pattern.length - 1 ? pattern[currentIndex + 1] : ''; - - // Handle character sets and negation within them. - if (currentChar === '[' && nextChar === '^') { - regexPattern += '[^'; - currentIndex++; // Include '^' as part of the set. - } else if (currentChar === '[' || currentChar === ']') { - // Directly append square brackets. - regexPattern += currentChar; - } else if (currentChar === '*') { - // Replace '*' with regex to match any character sequence. - regexPattern += '.*'; - } else if (currentChar === '?') { - // Replace '?' with regex to match any single character. - regexPattern += '.'; - } else if ('/.*+?|(){}'.indexOf(currentChar) > -1) { - // Escape special regex characters. - regexPattern += '\\' + currentChar; - } else { - // Append literal characters. - regexPattern += currentChar; + /** + * Tests if a given value matches a glob pattern. + * The function supports '*', '?' as wildcards where '*' matches any sequence of characters, + * and '?' matches any single character. Square brackets can be used for character sets and ranges. + * + * @param {string} value - The string value to test against the glob pattern. + * @param {string} pattern - The glob pattern to match the value against. + * @returns {boolean} - True if the value matches the pattern, false otherwise. + */ + utils.glob = function (value, pattern) { + var currentIndex = 0; // Index for traversing the pattern string. + var regexPattern = '^'; // Start regex pattern to match from the beginning. + + while (currentIndex < pattern.length) { + var currentChar = pattern[currentIndex]; + var nextChar = currentIndex < pattern.length - 1 ? pattern[currentIndex + 1] : ''; + + // Handle character sets and negation within them. + if (currentChar === '[' && nextChar === '^') { + regexPattern += '[^'; + currentIndex++; // Include '^' as part of the set. + } else if (currentChar === '[' || currentChar === ']') { + // Directly append square brackets. + regexPattern += currentChar; + } else if (currentChar === '*') { + // Replace '*' with regex to match any character sequence. + regexPattern += '.*'; + } else if (currentChar === '?') { + // Replace '?' with regex to match any single character. + regexPattern += '.'; + } else if ('/.*+?|(){}'.indexOf(currentChar) > -1) { + // Escape special regex characters. + regexPattern += '\\' + currentChar; + } else { + // Append literal characters. + regexPattern += currentChar; + } + currentIndex++; } - currentIndex++; - } - regexPattern += '$'; // End regex pattern to match until the end. + regexPattern += '$'; // End regex pattern to match until the end. - // Convert value to uppercase, compile the regex pattern in uppercase to perform a case-insensitive match. - return ('' + (value || '')).toUpperCase().search(RegExp(regexPattern.toUpperCase())) > -1; -}; + // Convert value to uppercase, compile the regex pattern in uppercase to perform a case-insensitive match. + return ('' + (value || '')).toUpperCase().search(RegExp(regexPattern.toUpperCase())) > -1; + }; -/** - * Type converter regex patterns - */ -var reTypeConverter = { - str: /string|char$|text/i, - int: /^int|int$/i, - num: /float|double|real|^num|decimal|money/i, - bool: /^bool/i, - date: /^date|^time/i, -}; + /** + * Type converter regex patterns + */ + var reTypeConverter = { + str: /string|char$|text/i, + int: /^int|int$/i, + num: /float|double|real|^num|decimal|money/i, + bool: /^bool/i, + date: /^date|^time/i, + }; -/** - * Convert a value to the appropriate type based on column definition - * @param {*} value - The value to convert - * @param {string} dbtypeid - The database type (INT, FLOAT, STRING, etc.) - * @return {*} The converted value - */ -utils.typeConverter = function (value, dbtypeid) { - // If value is null or undefined, return as is - if (value === null || value === undefined) { - return value; - } + /** + * Convert a value to the appropriate type based on column definition + * @param {*} value - The value to convert + * @param {string} dbtypeid - The database type (INT, FLOAT, STRING, etc.) + * @return {*} The converted value + */ + utils.typeConverter = function (value, dbtypeid) { + // If value is null or undefined, return as is + if (value === null || value === undefined) { + return value; + } - // If no type specified, try to auto-convert if it looks like a number - if (!dbtypeid) { - if (alasql.options.csvStringToNumber && typeof value === 'string' && value.length > 0) { - if (value == +value) { - return +value; + // If no type specified, try to auto-convert if it looks like a number + if (!dbtypeid) { + if (alasql.options.csvStringToNumber && typeof value === 'string' && value.length > 0) { + if (value == +value) { + return +value; + } } + return value; } - return value; - } - // Check type using regex patterns - if (reTypeConverter.str.test(dbtypeid)) { - return String(value); - } - if (reTypeConverter.int.test(dbtypeid)) { - var intVal = parseInt(value, 10); - return isNaN(intVal) ? value : intVal; - } - if (reTypeConverter.num.test(dbtypeid)) { - var numVal = parseFloat(value); - return isNaN(numVal) ? value : numVal; - } - if (reTypeConverter.bool.test(dbtypeid)) { - if (typeof value === 'string') { - return /^(true|1|yes)$/i.test(value); + // Check type using regex patterns + if (reTypeConverter.str.test(dbtypeid)) { + return String(value); + } + if (reTypeConverter.int.test(dbtypeid)) { + var intVal = parseInt(value, 10); + return isNaN(intVal) ? value : intVal; + } + if (reTypeConverter.num.test(dbtypeid)) { + var numVal = parseFloat(value); + return isNaN(numVal) ? value : numVal; + } + if (reTypeConverter.bool.test(dbtypeid)) { + if (typeof value === 'string') { + return /^(true|1|yes)$/i.test(value); + } + return Boolean(value); + } + if (reTypeConverter.date.test(dbtypeid)) { + return value instanceof Date ? value : new Date(value); } - return Boolean(value); - } - if (reTypeConverter.date.test(dbtypeid)) { - return value instanceof Date ? value : new Date(value); - } - // Unknown type, return as is - return value; -}; + // Unknown type, return as is + return value; + }; } diff --git a/src/utils/uncomment.js b/src/utils/uncomment.js index ceef0af79f..da93047563 100755 --- a/src/utils/uncomment.js +++ b/src/utils/uncomment.js @@ -1,5 +1,5 @@ export default function install(alasql) { -/** + /** Strip all comments. @function @param {string} str @@ -13,71 +13,71 @@ export default function install(alasql) { @todo Remove unused code */ -/* global alasql */ + /* global alasql */ -alasql.utils.uncomment = function (str) { - // Add some padding so we can always look ahead and behind by two chars - str = ('__' + str + '__').split(''); - var quote = false, - quoteSign, - // regularExpression = false, - // characterClass = false, - blockComment = false, - lineComment = false; - // preserveComment = false; + alasql.utils.uncomment = function (str) { + // Add some padding so we can always look ahead and behind by two chars + str = ('__' + str + '__').split(''); + var quote = false, + quoteSign, + // regularExpression = false, + // characterClass = false, + blockComment = false, + lineComment = false; + // preserveComment = false; - for (var i = 0, l = str.length; i < l; i++) { - // When checking for quote escaping, we also need to check that the - // escape sign itself is not escaped, as otherwise '\\' would cause - // the wrong impression of an unclosed string: - var unescaped = str[i - 1] !== '\\' || str[i - 2] === '\\'; + for (var i = 0, l = str.length; i < l; i++) { + // When checking for quote escaping, we also need to check that the + // escape sign itself is not escaped, as otherwise '\\' would cause + // the wrong impression of an unclosed string: + var unescaped = str[i - 1] !== '\\' || str[i - 2] === '\\'; - if (quote) { - if (str[i] === quoteSign && unescaped) { - quote = false; - } - } else if (blockComment) { - // Is the block comment closing? - if (str[i] === '*' && str[i + 1] === '/') { - // if (!preserveComment) - str[i] = str[i + 1] = ''; - blockComment /* = preserveComment*/ = false; - // Increase by 1 to skip closing '/', as it would be mistaken - // for a regexp otherwise - i++; - } else { - //if (!preserveComment) { + if (quote) { + if (str[i] === quoteSign && unescaped) { + quote = false; + } + } else if (blockComment) { + // Is the block comment closing? + if (str[i] === '*' && str[i + 1] === '/') { + // if (!preserveComment) + str[i] = str[i + 1] = ''; + blockComment /* = preserveComment*/ = false; + // Increase by 1 to skip closing '/', as it would be mistaken + // for a regexp otherwise + i++; + } else { + //if (!preserveComment) { + str[i] = ''; + } + } else if (lineComment) { + // One-line comments end with the line-break + if (str[i + 1] === '\n' || str[i + 1] === '\r' || str.length - 2 === i) { + lineComment = false; + } str[i] = ''; - } - } else if (lineComment) { - // One-line comments end with the line-break - if (str[i + 1] === '\n' || str[i + 1] === '\r' || str.length - 2 === i) { - lineComment = false; - } - str[i] = ''; - } else { - if (str[i] === '"' || str[i] === "'") { - quote = true; - quoteSign = str[i]; - } else if (str[i] === '[' && str[i - 1] !== '@') { - quote = true; - quoteSign = ']'; - } else if (str[i] === '-' && str[i + 1] === '-') { - str[i] = ''; - lineComment = true; - } else if (str[i] === '/' && str[i + 1] === '*') { - // Do not filter out conditional comments /*@ ... */ - // and comments marked as protected /*! ... */ - // preserveComment = /[@!]/.test(str[i + 2]); - // if (!preserveComment) - str[i] = ''; - blockComment = true; + } else { + if (str[i] === '"' || str[i] === "'") { + quote = true; + quoteSign = str[i]; + } else if (str[i] === '[' && str[i - 1] !== '@') { + quote = true; + quoteSign = ']'; + } else if (str[i] === '-' && str[i + 1] === '-') { + str[i] = ''; + lineComment = true; + } else if (str[i] === '/' && str[i + 1] === '*') { + // Do not filter out conditional comments /*@ ... */ + // and comments marked as protected /*! ... */ + // preserveComment = /[@!]/.test(str[i + 2]); + // if (!preserveComment) + str[i] = ''; + blockComment = true; + } } } - } - // Remove padding again. - str = str.join('').slice(2, -2); + // Remove padding again. + str = str.join('').slice(2, -2); - return str; -}; + return str; + }; } diff --git a/src/utils/xlsx.js b/src/utils/xlsx.js index d0bf7a678c..ff652a0df3 100644 --- a/src/utils/xlsx.js +++ b/src/utils/xlsx.js @@ -1,29 +1,29 @@ export default function install(alasql, internal) { var utils = alasql.utils; -/* + /* The XLSX loader: the SheetJS library found or handed in, split from src/15utility.js (docs/src-map.md). Same functions, same bodies. */ -var getXLSX = function () { - var XLSX = alasql.private.externalXlsxLib || utils.global.XLSX || null; + var getXLSX = function () { + var XLSX = alasql.private.externalXlsxLib || utils.global.XLSX || null; - if (XLSX) { - return XLSX; - } + if (XLSX) { + return XLSX; + } - if (utils.isNode || utils.isBrowserify || utils.isMeteorServer) { - //*not-for-browser/* - XLSX = require('../modules/xlsx/xlsx') || null; - alasql.private.externalXlsxLib = XLSX; - //*/ - } + if (utils.isNode || utils.isBrowserify || utils.isMeteorServer) { + //*not-for-browser/* + XLSX = require('../modules/xlsx/xlsx') || null; + alasql.private.externalXlsxLib = XLSX; + //*/ + } - if (!XLSX) { - throw new Error('Please include the xlsx.js library'); - } + if (!XLSX) { + throw new Error('Please include the xlsx.js library'); + } - return XLSX; -}; + return XLSX; + }; internal.getXLSX = getXLSX; } diff --git a/src/worker/launcher.js b/src/worker/launcher.js index 5fec1a0749..4e81f411ad 100755 --- a/src/worker/launcher.js +++ b/src/worker/launcher.js @@ -1,78 +1,78 @@ export default function install(alasql) { -alasql = alasql || false; + alasql = alasql || false; -if (!alasql) { - throw new Error('alasql was not found'); -} + if (!alasql) { + throw new Error('alasql was not found'); + } -alasql.worker = function () { - throw new Error('Can find webworker in this enviroment'); -}; + alasql.worker = function () { + throw new Error('Can find webworker in this enviroment'); + }; -if (typeof Worker !== 'undefined') { - alasql.worker = function (path, paths, cb) { - // var path; - if (path === true) { - path = undefined; - } + if (typeof Worker !== 'undefined') { + alasql.worker = function (path, paths, cb) { + // var path; + if (path === true) { + path = undefined; + } - if (typeof path === 'undefined') { - var sc = document.getElementsByTagName('script'); - for (var i = 0; i < sc.length; i++) { - if (sc[i].src.substr(-16).toLowerCase() === 'alasql-worker.js') { - path = sc[i].src.substr(0, sc[i].src.length - 16) + 'alasql.js'; - break; - } else if (sc[i].src.substr(-20).toLowerCase() === 'alasql-worker.min.js') { - path = sc[i].src.substr(0, sc[i].src.length - 20) + 'alasql.min.js'; - break; - } else if (sc[i].src.substr(-9).toLowerCase() === 'alasql.js') { - path = sc[i].src; - break; - } else if (sc[i].src.substr(-13).toLowerCase() === 'alasql.min.js') { - path = sc[i].src.substr(0, sc[i].src.length - 13) + 'alasql.min.js'; - break; + if (typeof path === 'undefined') { + var sc = document.getElementsByTagName('script'); + for (var i = 0; i < sc.length; i++) { + if (sc[i].src.substr(-16).toLowerCase() === 'alasql-worker.js') { + path = sc[i].src.substr(0, sc[i].src.length - 16) + 'alasql.js'; + break; + } else if (sc[i].src.substr(-20).toLowerCase() === 'alasql-worker.min.js') { + path = sc[i].src.substr(0, sc[i].src.length - 20) + 'alasql.min.js'; + break; + } else if (sc[i].src.substr(-9).toLowerCase() === 'alasql.js') { + path = sc[i].src; + break; + } else if (sc[i].src.substr(-13).toLowerCase() === 'alasql.min.js') { + path = sc[i].src.substr(0, sc[i].src.length - 13) + 'alasql.min.js'; + break; + } } } - } - if (typeof path === 'undefined') { - throw new Error('Path to alasql.js is not specified'); - } else if (path !== false) { - var js = "importScripts('"; - js += path; - js += - "');self.onmessage = function(event) {" + - 'alasql(event.data.sql,event.data.params, function(data){' + - 'postMessage({id:event.data.id, data:data});});}'; + if (typeof path === 'undefined') { + throw new Error('Path to alasql.js is not specified'); + } else if (path !== false) { + var js = "importScripts('"; + js += path; + js += + "');self.onmessage = function(event) {" + + 'alasql(event.data.sql,event.data.params, function(data){' + + 'postMessage({id:event.data.id, data:data});});}'; - var blob = new Blob([js], {type: 'text/plain'}); - alasql.webworker = new Worker(URL.createObjectURL(blob)); + var blob = new Blob([js], {type: 'text/plain'}); + alasql.webworker = new Worker(URL.createObjectURL(blob)); - alasql.webworker.onmessage = function (event) { - var id = event.data.id; + alasql.webworker.onmessage = function (event) { + var id = event.data.id; - alasql.buffer[id](event.data.data); - delete alasql.buffer[id]; - }; + alasql.buffer[id](event.data.data); + delete alasql.buffer[id]; + }; - alasql.webworker.onerror = function (e) { - throw e; - }; + alasql.webworker.onerror = function (e) { + throw e; + }; - if (arguments.length > 1) { - var sql = - 'REQUIRE ' + - paths - .map(function (p) { - return '"' + p + '"'; - }) - .join(','); - alasql(sql, [], cb); + if (arguments.length > 1) { + var sql = + 'REQUIRE ' + + paths + .map(function (p) { + return '"' + p + '"'; + }) + .join(','); + alasql(sql, [], cb); + } + } else if (path === false) { + delete alasql.webworker; + return; } - } else if (path === false) { - delete alasql.webworker; - return; - } - }; -} + }; + } } diff --git a/test/parity/README.md b/test/parity/README.md index 4ea41f6da4..831e3f07cb 100644 --- a/test/parity/README.md +++ b/test/parity/README.md @@ -19,7 +19,7 @@ Record the build before a change and the build after it, and compare. When a ste ## The reference -The published 4.21.0 tarball (`--ref`, the default) until a merge of `develop` brings a behaviour fix into the branch. From then on the reference is `develop` at the commit last merged forward, built with its own `build.sh`: `--ref ` takes a sha, a branch or a tag, extracts it with `git archive` under `.ref/` and builds it there once. `--ref ` downloads any published version. Otherwise every merged 4.x fix shows up as a difference. The published tarball is not always the build of its tag: 4.20.1 on npm carries a dist built from `develop-a98910ff` that calls itself 4.20.0 (fixed by #2731 for later releases); the recording's `meta` line shows what was really recorded. +The published 4.21.0 tarball (`--ref`, the default) until a merge of `develop` brings a behaviour fix into the branch. From then on the reference is `develop` at the commit last merged forward, built with its own `build.sh`: `--ref ` takes a sha, a branch or a tag, extracts it with `git archive` under `.ref/` and builds it there once. `--ref ` downloads any published version. Otherwise every merged 4.x fix shows up as a difference. Since #2798 was merged forward (2026-10-22, step 10) the reference is `develop` at 044ab07a, recorded as `ref-044ab07a`; `yarn test-parity` names it. The published tarball is not always the build of its tag: 4.20.1 on npm carries a dist built from `develop-a98910ff` that calls itself 4.20.0 (fixed by #2731 for later releases); the recording's `meta` line shows what was really recorded. After a recording against another dist, `dist/` is rebuilt with the project's `build-only` script, so a new build script is used as soon as it exists. diff --git a/test/parity/ledger.md b/test/parity/ledger.md index f0fdfbcd19..b1b25c17a1 100644 --- a/test/parity/ledger.md +++ b/test/parity/ledger.md @@ -24,7 +24,9 @@ One row per step of `context/roadmap-phase-1.md` (from step 4 the Commit column | 6. the SELECT pipeline, sixteen files under `src/select/`; step 6 complete | #2840 | build.js, esbuild | 923,455 / 141,320 | 919,023 / 140,724 | 525,286 / 115,303 | 4,093 / 1,933 | 4,915 / 1,762 | yes | 97 (Bun 97, Node 96) against the step 5 build, 3 runs, 172 cases; it marked cte +54, subquery +15, mutation +11 and group-by +10 percent on Bun and constraints +12 on Node, which focused runs of 7 put at cte 0 and 0, subquery +9 and 0, constraints +4 and +2, mutation +3 and +1, group-by -5 and -9 (Bun and Node); the one case behind subquery, the JOIN baseline, was +26 to +31 percent on Bun in two focused runs and +1 to +3 on Node, and runs at parity on both builds when timed on its own through `alasql(sql, params)`, so it is the JIT state of the harness run, not the conversion | | 7. the other statements, twenty-one files under `src/statements/`, `src/graph/` and `src/engine/statements.js`; step 7 complete; under 90 percent: `engine/statements.js` 89 (29 lines), `statements/dropindex.js` 85 (22 lines) | #2842 | build.js, esbuild | 926,899 / 141,807 | 922,467 / 141,207 | 526,039 / 115,646 | 4,096 / 1,934 | 4,915 / 1,762 | yes | test-perf-mini 104 against 4.21.0 (under the fail line of 105); it marked left-join +69, index +47, boolean +46, core +32 and memory +26 percent on Node, measured while another session's runner held the machine at a load of 24; a focused run of 7 on a quieter machine against the step 6 build put boolean +3, core +1, left-join 0 and index -3, score 100 | | 8. formats, storage engines, console, dialects and FileSaver, twenty-three files under `src/console/`, `src/dialects/`, `src/formats/`, `src/statements/`, `src/storage/` and `src/utils/filesaver.js`; step 8 complete; under 90 percent: `formats/gexf.js` 83 (9 lines), `statements/assert.js` 83 (30 lines), `statements/source.js` 89 (34 lines); the three two-line dialect stubs are below git's rename threshold | #2843 | build.js, esbuild | 930,253 / 142,351 | 925,821 / 141,749 | 524,747 / 115,613 | 4,093 / 1,933 | 4,915 / 1,762 | yes | test-perf-mini 107 against 4.21.0 (over the fail line of 105), run with the machine still settling after another session's runner (5-minute load average 52); it marked compile +159 percent (the two compile cases at +300); a focused run of 7 on Node against the step 7 build, on a quiet machine, put compile +1, subquery -1 and index -1, score 99; a second test-perf-mini was stopped by the system for memory pressure before it ran and is to be rerun | -| 9. the worker launcher, the worker head and tail, the finishing file; 97saveas.js, the legacy generator, its tools and four unused numbered files removed; no numbered file left; step 9 complete; under 90 percent: `engine/finish.js` 77 (34 lines, UMD tail and a stray brace out) | #2845 | build.js, esbuild | 929,626 / 142,125 | 925,188 / 141,480 | 524,543 / 115,441 | 4,676 / 2,210 (the launcher is bundled as a module now, not concatenated) | 4,915 / 1,762 | yes | BENCH_STEP9 | +| 9. the worker launcher, the worker head and tail, the finishing file; 97saveas.js, the legacy generator, its tools and four unused numbered files removed; no numbered file left; step 9 complete; under 90 percent: `engine/finish.js` 77 (34 lines, UMD tail and a stray brace out) | #2845 | build.js, esbuild | 929,626 / 142,125 | 925,188 / 141,480 | 524,543 / 115,441 | 4,676 / 2,210 (the launcher is bundled as a module now, not concatenated) | 4,915 / 1,762 | yes | end of the phase: 102 (Bun 101, Node 102) against 4.21.0 on npm, 3 runs, 172 cases, on a quiet machine (load 3, no other test process); it marked subquery +19 (Bun) and +8 (Node), json-path -7 and +16, memory -3 and +13, which a focused run of 7 put at json-path +14 and +9, subquery +13 and +4, core +4 and +8, memory -7 and -1 (Bun and Node). The subquery mark is one case, the JOIN baseline, at +49 to +52 percent on Bun and within 3 on Node: it is the same without keepNames (+51), it is at parity with 4.21.0 when timed alone through `alasql(sql, params)` on Bun, and across the step builds on Bun it reads -31 (1b), +22 (2), -2 (3), -28 (4a), +42 (4b), +44 (5), +52 (9), so it is a JavaScriptCore layout effect of the harness run, not a conversion cost. json-path at +14 on Bun and +9 on Node is the one dimension left above 10 on a runtime | +| develop merged forward before step 10's Prettier commit: #2798 (044ab07a, `60createtable.js` and `94filestorage.js`); git carried both changes into `src/statements/createtable.js` and `src/storage/filestorage.js` without a conflict, and test1846.js joined the corpus | 35bd5a99 | build.js, esbuild | 929,770 / 142,135 | 925,332 / 141,491 | 524,616 / 115,473 | 4,679 / 2,214 | 4,915 / 1,762 | yes | not run; the reference moved to develop at 044ab07a, built with its own build.sh (`ref-044ab07a`): 0 differences, API identical apart from the 11 allowed; against the old 4.21.0 reference one result differs, the xlsx serial date of test815.js, which is the fixed clock running ahead by the statements test1846.js adds before it (README, "The reference"), not #2798 | +| 10. Prettier once over the 84 converted modules (commit fac32eef, in `.git-blame-ignore-revs`), the conversion folders out of `.prettierignore`, `no-undef` over the converted modules, CI's formatting check widened to nested folders; step 10 complete | #2847 | build.js, esbuild | 930,140 / 142,209 | 925,702 / 141,564 | 524,616 / 115,472 | 4,679 / 2,214 | 4,915 / 1,762 | yes | test-perf-mini 100 against 4.21.0 on the tip, nothing marked; formatting changes no code, so the end-of-phase figure in row 9 stands. Parity on the tip against `ref-044ab07a`: 0 differences, API identical apart from the 11 allowed; double run 0 unstable | Notes diff --git a/test/test1846.js b/test/test1846.js new file mode 100644 index 0000000000..9315c8b275 --- /dev/null +++ b/test/test1846.js @@ -0,0 +1,161 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); + var fs = require('fs'); +} + +let testId = '1846'; // Use the ID of the issue being fixed by this PR + +describe(`Test ${testId} - REFERENCES in FILESTORAGE`, function () { + const fsdbid = 'test' + testId + 'fs'; + const filename = 'test' + testId + '.db'; + + function attach(done) { + alasql('ATTACH FILESTORAGE DATABASE ' + fsdbid + '("' + filename + '")', function () { + alasql('USE ' + fsdbid); + done(); + }); + } + + function detach() { + alasql('USE alasql'); + alasql('DETACH DATABASE ' + fsdbid); + } + + before(function (done) { + if (fs.existsSync(filename)) fs.unlinkSync(filename); + alasql('CREATE FILESTORAGE DATABASE ' + fsdbid + '("' + filename + '")', function () { + attach(done); + }); + }); + + after(function (done) { + detach(); + alasql('DROP FILESTORAGE DATABASE ' + fsdbid, function () { + done(); + }); + }); + + it('A) Column REFERENCES to a table in the same session', function () { + let res = []; + res.push( + alasql(`CREATE TABLE IF NOT EXISTS banks ( + name STRING PRIMARY KEY, + is_open BOOLEAN DEFAULT true, + [key] STRING + )`) + ); + res.push( + alasql(`CREATE TABLE IF NOT EXISTS pigs ( + id STRING PRIMARY KEY, + bank STRING REFERENCES banks(name), + ready BOOLEAN + )`) + ); + // Creating a table must not remove the primary key of the other tables + res.push(alasql.databases[fsdbid].tables.banks.pk.columns); + res.push(alasql.databases[fsdbid].tables.pigs.xcolumns.bank.foreignkey); + assert.deepStrictEqual(res, [1, 1, ['name'], {tableid: 'banks', columnid: 'name'}]); + }); + + it('B) Column REFERENCES to a table after the database is attached again', function (done) { + detach(); + attach(function () { + let res = []; + res.push( + alasql(`CREATE TABLE IF NOT EXISTS piglets ( + id STRING PRIMARY KEY, + bank STRING REFERENCES banks(name) + )`) + ); + res.push(alasql.databases[fsdbid].tables.piglets.xcolumns.bank.foreignkey); + assert.deepStrictEqual(res, [1, {tableid: 'banks', columnid: 'name'}]); + done(); + }); + }); +}); + +describe(`Test ${testId} - REFERENCES in memory`, function () { + before(function () { + alasql('create database test' + testId); + alasql('use test' + testId); + }); + + after(function () { + alasql('drop database test' + testId); + }); + + it('C) Column REFERENCES is still enforced', function () { + let res = []; + res.push(alasql('CREATE TABLE banks (name STRING PRIMARY KEY)')); + res.push(alasql('CREATE TABLE pigs (id STRING, bank STRING REFERENCES banks(name))')); + res.push(alasql("INSERT INTO banks VALUES ('Bank1')")); + res.push(alasql("INSERT INTO pigs VALUES ('pig1', 'Bank1')")); + assert.throws( + () => alasql("INSERT INTO pigs VALUES ('pig2', 'NoBank')"), + /Foreign key "NoBank" not found in table "banks"/ + ); + res.push(alasql('SELECT * FROM pigs')); + assert.deepStrictEqual(res, [1, 1, 1, 1, [{id: 'pig1', bank: 'Bank1'}]]); + }); + + it('D) REFERENCES to another column name still checks the primary key, as before', function () { + let res = []; + res.push(alasql('CREATE TABLE owners (id INT PRIMARY KEY)')); + res.push(alasql('INSERT INTO owners VALUES (1)')); + res.push(alasql('CREATE TABLE cats (owner INT REFERENCES owners(Id))')); + res.push(alasql('CREATE TABLE dogs (owner INT REFERENCES owners(owner_id))')); + res.push(alasql('INSERT INTO cats VALUES (1)')); + res.push(alasql('INSERT INTO dogs VALUES (1)')); + assert.throws(() => alasql('INSERT INTO cats VALUES (2)'), /Foreign key "2" not found/); + assert.deepStrictEqual(res, [1, 1, 1, 1, 1, 1]); + }); +}); + +describe(`Test ${testId} - FILESTORAGE with autocommit off`, function () { + const fsdbid = 'test' + testId + 'nc'; + const filename = 'test' + testId + 'nc.db'; + let autocommit; + + function attach(done) { + alasql('ATTACH FILESTORAGE DATABASE ' + fsdbid + '("' + filename + '")', function () { + alasql('USE ' + fsdbid); + done(); + }); + } + + before(function (done) { + autocommit = alasql.options.autocommit; + alasql.options.autocommit = false; + if (fs.existsSync(filename)) fs.unlinkSync(filename); + alasql('CREATE FILESTORAGE DATABASE ' + fsdbid + '("' + filename + '")', function () { + attach(done); + }); + }); + + after(function (done) { + alasql('USE alasql'); + alasql('DETACH DATABASE ' + fsdbid); + alasql.options.autocommit = autocommit; + alasql('DROP FILESTORAGE DATABASE ' + fsdbid, function () { + done(); + }); + }); + + it('E) INSERT and SELECT work, also after the database is attached again', function (done) { + let res = []; + res.push(alasql('CREATE TABLE banks (name STRING PRIMARY KEY)')); + res.push(alasql('CREATE TABLE pigs (id STRING, bank STRING REFERENCES banks(name))')); + res.push(alasql("INSERT INTO banks VALUES ('Bank1')")); + res.push(alasql("INSERT INTO pigs VALUES ('pig1', 'Bank1')")); + res.push(alasql('SELECT * FROM pigs')); + res.push(alasql('COMMIT TRANSACTION')); + assert.deepStrictEqual(res, [1, 1, 1, 1, [{id: 'pig1', bank: 'Bank1'}], 1]); + alasql('USE alasql'); + alasql('DETACH DATABASE ' + fsdbid); + attach(function () { + assert.deepStrictEqual(alasql('SELECT * FROM pigs'), [{id: 'pig1', bank: 'Bank1'}]); + done(); + }); + }); +});