diff --git a/src/30statements.js b/src/30statements.js index 102c87f502..b84b904803 100755 --- a/src/30statements.js +++ b/src/30statements.js @@ -24,6 +24,6 @@ yy.Statements = class Statements { const res = statements.map(st => st(params)); if (cb) cb(res); return res; - }; + }; } }; diff --git a/src/50expression.js b/src/50expression.js index 8f086ab5b8..bd76fdc93d 100755 --- a/src/50expression.js +++ b/src/50expression.js @@ -1,11 +1,3 @@ -/* -// -// Expressions for Alasql.js -// Date: 03.11.2014 -// (c) 2014, Andrey Gershun -// -*/ - { const assign = Object.assign; @@ -197,65 +189,45 @@ } } + const toTypeNumberOps = new Set(['-', '*', '/', '%', '^']); + const toTypeStringOps = new Set(['||']); + 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 this.left.toString() + ' ' + this.op + ' (' + this.right.toString() + ')'; + return `${leftStr} ${this.op} (${this.right.toString()})`; } + if (this.allsome) { - return ( - this.left.toString() + - ' ' + - this.op + - ' ' + - this.allsome + - ' (' + - this.right.toString() + - ')' - ); + return `${leftStr} ${this.op} ${this.allsome} (${this.right.toString()})`; } - if (this.op === '->' || this.op === '!') { - var s = this.left.toString() + this.op; - // console.log(this.right); - if (typeof this.right !== 'string' && typeof this.right !== 'number') { - s += '('; - } - - s += this.right.toString(); - if (typeof this.right !== 'string' && typeof this.right !== 'number') { - s += ')'; - } - - return s; + 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.op === 'BETWEEN' || this.op === 'NOT BETWEEN') { - var s = - this.left.toString() + - ' ' + - this.op + - ' ' + - this.right1.toString() + - ' AND ' + - this.right2.toString(); - - return s; + return `${leftStr} ${this.op} ${this.right1.toString()} AND ${this.right2.toString()}`; } - return ( - this.left.toString() + - ' ' + - this.op + - ' ' + - (this.allsome ? this.allsome + ' ' : '') + - this.right.toString() - ); + return `${leftStr} ${this.op} ${this.allsome ? this.allsome + ' ' : ''}${this.right.toString()}`; } + findAggregator(query) { if (this.left && this.left.findAggregator) { this.left.findAggregator(query); @@ -267,69 +239,38 @@ } toType(tableid) { - if (['-', '*', '/', '%', '^'].indexOf(this.op) > -1) { + if (toTypeNumberOps.has(this.op)) return 'number'; - } - if (['||'].indexOf(this.op) > -1) { + + if (toTypeStringOps.has(this.op)) return 'string'; - } + if (this.op === '+') { - if (this.left.toType(tableid) === 'string' || this.right.toType(tableid) === 'string') { + const leftType = this.left.toType(tableid); + const rightType = this.right.toType(tableid); + + if (leftType === 'string' || rightType === 'string') { return 'string'; } - if (this.left.toType(tableid) === 'number' || this.right.toType(tableid) === 'number') { + if (leftType === 'number' || rightType === 'number') { return 'number'; } } - if ( - [ - 'AND', - 'OR', - 'NOT', - '=', - '==', - '===', - '!=', - '!==', - '!===', - '>', - '>=', - '<', - '<=', - 'IN', - 'NOT IN', - 'LIKE', - 'NOT LIKE', - 'REGEXP', - 'GLOB', - ].indexOf(this.op) > -1 - ) { + if (toTypeBoolOps.has(this.op) || this.allsome) return 'boolean'; - } - if ( - this.op === 'BETWEEN' || - this.op === 'NOT BETWEEN' || - this.op === 'IS NULL' || - this.op === 'IS NOT NULL' - ) { - return 'boolean'; - } - if (this.allsome) { - return 'boolean'; - } + if (!this.op) + return this.left.toType(tableid); - if (!this.op) { - return this.left.toType(); - } return 'unknown'; } + toJS(context, tableid, defcols) { // console.log(this); var s; @@ -615,13 +556,7 @@ op = '&&'; } - // if(this.op === '^') { - // // return 'Math.pow(' - // // + leftJS() - // // + ',' - // // + rightJS() - // // + ')'; - // } + var expr = s || '(' + leftJS() + op + rightJS() + ')'; var declareRefs = 'y=[(' + refs.join('), (') + ')]'; @@ -785,6 +720,13 @@ } } + const toJsOpMapping = { + '~': '~', + '-': '-', + '+': '+', + 'NOT': '!', + }; + class UniOp { constructor(params) { assign(this, params); @@ -814,77 +756,39 @@ } toType() { - if (this.op === '-') { - return 'number'; - } - - if (this.op === '+') { - return 'number'; - } - - if (this.op === 'NOT') { - return 'boolean'; + switch (this.op) { + case '-': + case '+': + return 'number'; + case 'NOT': + return 'boolean'; + default: + return 'string'; } - - // Todo: implement default case } - toJS(context, tableid, defcols) { - if (this.op === '~') { - return '(~(' + this.right.toJS(context, tableid, defcols) + '))'; - } - if (this.op === '-') { - return '(-(' + this.right.toJS(context, tableid, defcols) + '))'; - } - if (this.op === '+') { - return '(' + this.right.toJS(context, tableid, defcols) + ')'; + toJS(context, tableid, defcols) { + if (this.right instanceof Column && this.op === '#') { + return `(alasql.databases[alasql.useid].objects['${this.right.columnid}'])`; } - if (this.op === 'NOT') { - return '!(' + this.right.toJS(context, tableid, defcols) + ')'; - } + const rightJS = this.right.toJS(context, tableid, defcols); - if (this.op === '#') { - if (this.right instanceof Column) { - return "(alasql.databases[alasql.useid].objects['" + this.right.columnid + "'])"; - } else { - return ( - '(alasql.databases[alasql.useid].objects[' + - this.right.toJS(context, tableid, defcols) + - '])' - ); - } + if (toJsOpMapping.hasOwnProperty(this.op)) { + return `(${toJsOpMapping[this.op]}(${rightJS}))`; } - // Please avoid === here if (this.op == null) { - // jshint ignore:line - return '(' + this.right.toJS(context, tableid, defcols) + ')'; + return `(${rightJS})`; } - // Todo: implement default case. + throw new Error(`Unsupported operator: ${this.op}`); } + } - /*/* -// yy.Star = class { -// constructor (params) { return assign(this, params); } -// toString () { -// var s = this.fieldid; -// if (this.tableid) { -// s = this.tableid + '.' + s; -// if (this.databaseid) { -// s = this.databaseid + '.' + s; -// } -// } -// if (this.alias) -// s += ' AS ' + this.alias; -// return s; -// } -// } -*/ class Column { constructor(params) { @@ -892,82 +796,55 @@ } toString() { - var s; + let s = this.columnid; + if (this.columnid == +this.columnid) { s = '[' + this.columnid + ']'; - } else { - s = this.columnid; } + if (this.tableid) { - if (+this.columnid === this.columnid) { - s = this.tableid + s; - } else { - s = this.tableid + '.' + s; - } + s = this.tableid + (this.columnid === +this.columnid ? '' : '.') + s; + if (this.databaseid) { s = this.databaseid + '.' + s; } } + return s; } + toJS(context, tableid, defcols) { - var s = ''; if (!this.tableid && tableid === '' && !defcols) { - if (this.columnid !== '_') { - s = context + "['" + this.columnid + "']"; - } else { - if (context === 'g') { - s = "g['_']"; - } else { - s = context; - } - } - } else { - if (context === 'g') { - s = "g['" + this.nick + "']"; - } else if (this.tableid) { - if (this.columnid !== '_') { - s = context + "['" + this.tableid + "']['" + this.columnid + "']"; - } else { - if (context === 'g') { - s = "g['_']"; - } else { - s = context + "['" + this.tableid + "']"; - } - } - } else if (defcols) { - var tbid = defcols[this.columnid]; - if (tbid === '-') { - throw new Error( - 'Cannot resolve column "' + this.columnid + '" because it exists in two source tables' - ); - } else if (tbid) { - if (this.columnid !== '_') { - s = context + "['" + tbid + "']['" + this.columnid + "']"; - } else { - s = context + "['" + tbid + "']"; - } - // console.log(836,tbid,s); - } else { - if (this.columnid !== '_') { - s = context + "['" + (this.tableid || tableid) + "']['" + this.columnid + "']"; - } else { - s = context + "['" + (this.tableid || tableid) + "']"; - } - } - } else if (tableid === -1) { - s = context + "['" + this.columnid + "']"; + return this.columnid !== '_' ? `${context}['${this.columnid}']` : (context === 'g' ? "g['_']" : context); + } + + if (context === 'g') { + return `g['${this.nick}']`; + } + + 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 !== '_') { - s = context + "['" + (this.tableid || tableid) + "']['" + this.columnid + "']"; - } else { - s = context + "['" + (this.tableid || tableid) + "']"; - } + return this.columnid !== '_' ? `${context}['${this.tableid || tableid}']['${this.columnid}']` : `${context}['${this.tableid || tableid}']`; } } - return s; + + if (tableid === -1) { + return `${context}['${this.columnid}']`; + } + + return this.columnid !== '_' ? `${context}['${this.tableid || tableid}']['${this.columnid}']` : `${context}['${this.tableid || tableid}']`; } + } class AggrValue { @@ -976,68 +853,39 @@ } toString() { - var s = ''; - if (this.aggregatorid === 'REDUCE') { - s += this.funcid.replace(re_invalidFnNameChars, '') + '('; - } else { - s += this.aggregatorid + '('; - } + 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 (this.distinct) { - s += 'DISTINCT '; - } + return `${funcName}(${distinctPart}${expressionPart})${overPart}`; + } - if (this.expression) { - s += this.expression.toString(); - } - s += ')'; + findAggregator(query) { + const colas = escapeq(this.toString()) + ':' + query.selectGroup.length; - if (this.over) { - s += ' ' + this.over.toString(); - } - return s; - } + if (!this.nick) { + this.nick = colas; - findAggregator(query) { - var colas = escapeq(this.toString()) + ':' + query.selectGroup.length; - var found = false; - if (!found) { - if (!this.nick) { - this.nick = colas; - var found = false; - for (var i = 0; i < query.removeKeys.length; i++) { - if (query.removeKeys[i] === colas) { - found = true; - break; - } - } - if (!found) { - query.removeKeys.push(colas); - } + if (!query.removeKeys.includes(colas)) { + query.removeKeys.push(colas); } - query.selectGroup.push(this); } - return; + + query.selectGroup.push(this); } + toType() { - if ( - ['SUM', 'COUNT', 'AVG', 'MIN', 'MAX', 'AGGR', 'VAR', 'STDDEV', 'TOTAL'].indexOf( - this.aggregatorid - ) > -1 - ) { + if (['SUM', 'COUNT', 'AVG', 'MIN', 'MAX', 'AGGR', 'VAR', 'STDDEV', 'TOTAL'].includes(this.aggregatorid)) { return 'number'; } - if (['ARRAY'].indexOf(this.aggregatorid) > -1) { + if (this.aggregatorid === 'ARRAY') { return 'array'; } - if (['FIRST', 'LAST'].indexOf(this.aggregatorid) > -1) { - return this.expression.toType(); - } - return this.expression.toType(); } diff --git a/src/59convert.js b/src/59convert.js index ba89b1645d..e0551566dc 100755 --- a/src/59convert.js +++ b/src/59convert.js @@ -188,49 +188,58 @@ alasql.stdfn.CONVERT = function (value, args) { } } - if (args.dbtypeid == 'Date') { - return t; - } else if (udbtypeid == 'DATE') { - return s.formattedYear + '.' + s.formattedMonth + '.' + s.formattedDate; - } else if (udbtypeid == 'DATETIME' || udbtypeid == 'DATETIME2') { - var f = s.fullYear + '.' + s.formattedMonth + '.' + s.formattedDate; - f += ' ' + s.formattedHour + ':' + s.formattedMinutes + ':' + s.formattedSeconds; - f += '.' + s.formattedMilliseconds; - return f; - } else if (['MONEY'].indexOf(udbtypeid) > -1) { - var m = +val; - return (m | 0) + ((m * 100) % 100) / 100; - } else if (['BOOLEAN'].indexOf(udbtypeid) > -1) { - return !!val; - } else if ( - ['INT', 'INTEGER', 'SMALLINT', 'BIGINT', 'SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].indexOf( - args.dbtypeid.toUpperCase() - ) > -1 - ) { - return val | 0; - } else if ( - ['STRING', 'VARCHAR', 'NVARCHAR', 'CHARACTER VARIABLE'].indexOf(args.dbtypeid.toUpperCase()) > - -1 - ) { - if (args.dbsize) return ('' + val).substr(0, args.dbsize); - else return '' + val; - } else if (['CHAR', 'CHARACTER', 'NCHAR'].indexOf(udbtypeid) > -1) { - return (val + new Array(args.dbsize + 1).join(' ')).substr(0, args.dbsize); - //else return ""+val.substr(0,1); - } else if (['NUMBER', 'FLOAT', 'DECIMAL', 'NUMERIC'].indexOf(udbtypeid) > -1) { - var m = +val; - //toPrecision sets the number of numbers total in the result - m = args.dbsize !== undefined ? parseFloat(m.toPrecision(args.dbsize)) : m; - //toFixed sets the number of numbers to the right of the decimal - m = args.dbprecision !== undefined ? parseFloat(m.toFixed(args.dbprecision)) : m; - return m; - } else if (['JSON'].indexOf(udbtypeid) > -1) { - if (typeof val == 'object') return val; - try { - return JSON.parse(val); - } catch (err) { - throw new Error('Cannot convert string to JSON'); - } + 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; + default: + return val; } - return val; }; diff --git a/test/lib/assert/assert.js b/test/lib/assert/assert.js index 8af563547f..369c80de15 100644 --- a/test/lib/assert/assert.js +++ b/test/lib/assert/assert.js @@ -118,7 +118,7 @@ var keys = []; for (var key in obj) keys.push(key); return keys; - }; + }; // 1. The assert module provides functions that throw // AssertionError's when particular conditions are not met. The // assert module must conform to the following interface. diff --git a/test/test293.js b/test/test293.js index 3cce5621fb..146db571bf 100644 --- a/test/test293.js +++ b/test/test293.js @@ -173,10 +173,10 @@ if (typeof exports === 'object') { 'function' == typeof define && define.amd ? define(function () { return A; - }) + }) : 'object' == typeof module && module.exports - ? (module.exports = A) - : (n.md5 = A); + ? (module.exports = A) + : (n.md5 = A); })(this); } diff --git a/types/alasql.d.ts b/types/alasql.d.ts index 6a38e20573..c93392d9c7 100644 --- a/types/alasql.d.ts +++ b/types/alasql.d.ts @@ -83,60 +83,60 @@ declare module 'alasql' { } /** - * AlaSQL database object. This is a lightweight implimentation - * - * @interface database - */ + * AlaSQL database object. This is a lightweight implimentation + * + * @interface database + */ interface database { /** - * The database ID. - * - * @type {string} - * @memberof database - */ + * The database ID. + * + * @type {string} + * @memberof database + */ databaseid: string; - + /** - * The collection of tables in the database. - * - * @type {tableLookUp} - * @memberof database - */ - tables: tableLookUp; - } - + * The collection of tables in the database. + * + * @type {tableLookUp} + * @memberof database + */ + tables: tableLookUp; + } + /** - * AlaSQL table object. This is a lightweight implimentation - * - * @interface table - */ + * AlaSQL table object. This is a lightweight implimentation + * + * @interface table + */ interface table { /** - * The array of data stored in the table which can be queried - * - * @type {any[]} - * @memberof table - */ + * The array of data stored in the table which can be queried + * + * @type {any[]} + * @memberof table + */ data: any[]; } /** - * AlaSQL database dictionary - * - * @interface databaseLookUp - */ + * AlaSQL database dictionary + * + * @interface databaseLookUp + */ interface databaseLookUp { [databaseName: string]: database; } /** - * AlaSQL table dictionary - * - * @interface tableLookUp - */ - interface tableLookUp { + * AlaSQL table dictionary + * + * @interface tableLookUp + */ + interface tableLookUp { [tableName: string]: table; - } + } interface AlaSQL { options: AlaSQLOptions; @@ -152,41 +152,41 @@ declare module 'alasql' { setXLSX(xlsxlib: typeof xlsx): void; /** - * Array of databases in the AlaSQL object. - * - * @type {databaseLookUp} - * @memberof AlaSQL - */ + * Array of databases in the AlaSQL object. + * + * @type {databaseLookUp} + * @memberof AlaSQL + */ databases: databaseLookUp; /** - * Equivalent to alasql('USE '+databaseid). This will change the current - * database to the one specified. This will update the useid property and - * the tables property. - * - * @param {string} databaseid - * @memberof AlaSQL - */ - use (databaseid: string): void; - + * Equivalent to alasql('USE '+databaseid). This will change the current + * database to the one specified. This will update the useid property and + * the tables property. + * + * @param {string} databaseid + * @memberof AlaSQL + */ + use(databaseid: string): void; + /** - * The current database ID. If no database is selected, this is the - * default database ID (called alasql). - * - * @type {string} - * @memberof AlaSQL - */ + * The current database ID. If no database is selected, this is the + * default database ID (called alasql). + * + * @type {string} + * @memberof AlaSQL + */ useid: string; - + /** - * Array of the tables in the default database (called alasql). If - * the database is changes via a USE statement or the use method, this - * becomes the tables in the new database. - * - * @type {tableLookUp} - * @memberof AlaSQL - */ - tables: tableLookUp; + * Array of the tables in the default database (called alasql). If + * the database is changes via a USE statement or the use method, this + * becomes the tables in the new database. + * + * @type {tableLookUp} + * @memberof AlaSQL + */ + tables: tableLookUp; } const alasql: AlaSQL;