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 += '| #'; - 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 += '
' + JSONtoString(res) + ' '; - } - // if() {} + }); + } - // if(typeof res == 'object') { - // s += ''+JSON.stringify(res)+' '; - // } else { - // } - return s; -} + 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 += ''; - s += '| '; - // Column title - if (typeof column.title != 'undefined') { - if (typeof column.title == 'function') { - s += column.title(sheet, column, columnidx); + if (typeof column.width == 'undefined') { + if (sheet.column && sheet.column.width != 'undefined') { + column.width = sheet.column.width; } else { - s += column.title; + column.width = '120px'; } } - s += ' | '; + 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]; }); - s += '|
|---|---|
| '; - // TODO Replace with extend... - var format = cell.format; - if (typeof value == 'undefined') { - s += ''; - } else if (typeof format != 'undefined') { - if (typeof format == 'function') { - s += format(value); - } else if (typeof format == 'string') { - s += value; // TODO - add string format - } else { - throw new Error('Unknown format type. Should be function or string'); + // TODO Replace with extend... + typestyle = typestyle || 'mso-number-format:"\\@";'; // Default type style + + s += " | '; + + // TODO Replace with extend... + var format = cell.format; + if (typeof value == 'undefined') { + s += ''; + } else if (typeof format != 'undefined') { + if (typeof format == 'function') { + s += format(value); + } else if (typeof format == 'string') { + s += value; // TODO - add string format + } else { + throw new Error('Unknown format type. Should be function or string'); + } } else { - s += value; + if (typeid == 'number' || typeid == 'date') { + s += value.toString(); + } else if (typeid == 'money') { + s += (+value).toFixed(2); + } else { + s += value; + } } - } - s += ' | '; - }); + s += ''; + }); - s += '