From 488850e8eb7dbe11a68162f2b32cc84cf821b080 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 18:31:35 +0200 Subject: [PATCH 01/75] Bootstrap v2 --- .npmignore | 9 - Gruntfile.js | 31 -- README.md | 65 +-- bower.json | 23 - example.html | 31 -- package.json | 19 +- simpleSqlParser.js | 1031 +++++++++++++------------------------------- tests/tests.html | 14 - tests/tests.js | 835 ----------------------------------- 9 files changed, 296 insertions(+), 1762 deletions(-) delete mode 100644 .npmignore delete mode 100644 Gruntfile.js delete mode 100644 bower.json delete mode 100644 example.html delete mode 100644 tests/tests.html delete mode 100644 tests/tests.js diff --git a/.npmignore b/.npmignore deleted file mode 100644 index c065047..0000000 --- a/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -# Sublime Text projects -*.sublime-project -*.sublime-workspace - -node_modules -tests -example.html -Gruntfile.js -.git* diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 67de7a5..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = function(grunt) { - - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - jshint: { - all: ['Gruntfile.js', 'simpleSqlParser.js', 'tests/tests.js'], - options : { - multistr: true, - sub: true, - }, - }, - qunit: { - all: ['tests/**/*.html'], - }, - watch: { - files: ['**.js'], - tasks: ['default'], - options: { - atBegin: true, - interrupt: true, - }, - }, - }); - - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-qunit'); - grunt.loadNpmTasks('grunt-contrib-watch'); - - grunt.registerTask('default', ['jshint', 'qunit']); - -}; diff --git a/README.md b/README.md index e771ca9..e521461 100644 --- a/README.md +++ b/README.md @@ -3,70 +3,7 @@ simpleSqlParser Javascript library to parse CRUD (Create Retrieve Update Delete) SQL queries. -## How to use - -### From browser - -Import the JS file in your page: - -```html - -``` - -### From Node.js - -Install the module from [npm](https://npmjs.org/package/simple-sql-parser) using `npm i simple-sql-parser --save`. - -Import the JS module in your app: - -```js -var simpleSqlParser = require('simple-sql-parser'); -``` - -### Parse a query: - -```js -var ast = simpleSqlParser.sql2ast('your SQL query'); -console.log(ast); -``` - -### Create a query from AST: - -```js -var query = simpleSqlParser.ast2sql(ast); -console.log(query); -``` - -*AST means Abstract Syntax Tree.* - -## Examples - -* See `example.html` (open brower's console) -* Have a look to the unit tests `tests/tests.js` to see what's possible - -## Notes - -simpleSqlParser only supports these queries: - -* SELECT -* INSERT -* UPDATE -* DELETE - -simpleSqlParser **is not a full SQL parser!** -It only support few SQL mechanisms and keywords. -Feel free to make a pull request/issue. - -## How to install dev tools - -If you want to contribute, please write tests and respect the coding style. - -To install dev tools: - -- install Node.js (http://nodejs.org/) -- install **grunt-cli** globally: `npm i -g grunt-cli` -- install dev dependencies: `npm i` -- use `grunt` to check your code! +**This is a work in progress!** ## License diff --git a/bower.json b/bower.json deleted file mode 100644 index 20f8d43..0000000 --- a/bower.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "simple-sql-parser", - "main": "simpleSqlParser.js", - "homepage": "https://github.com/dsferruzza/simpleSqlParser", - "authors": [ - "David Sferruzza " - ], - "description": "Javascript library to parse CRUD (Create Retrieve Update Delete) SQL queries.", - "keywords": [ - "sql", - "parse", - "ast" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "tests", - "package.json", - "Gruntfile.js" - ] -} diff --git a/example.html b/example.html deleted file mode 100644 index 885ddc3..0000000 --- a/example.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - simpleSqlParser - - - -

Check console to see the output

- - - - diff --git a/package.json b/package.json index 635fab9..1e17796 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simple-sql-parser", "description": "Javascript library to parse CRUD (Create Retrieve Update Delete) SQL queries.", - "version": "1.3.0", + "version": "2.0.0-alpha", "main": "simpleSqlParser.js", "keywords": [ "sql", @@ -13,12 +13,6 @@ "email": "david.sferruzza@gmail.com", "url": "https://github.com/dsferruzza" }, - "contributors": [ - { - "name": "RienNeVaPlus", - "url": "https://github.com/RienNeVaPlus" - } - ], "maintainers": [ { "name": "David Sferruzza", @@ -35,14 +29,7 @@ }, "license": "MIT", "homepage": "https://github.com/dsferruzza/simpleSqlParser", - "devDependencies": { - "qunitjs": "*", - "grunt": "*", - "grunt-contrib-qunit": "*", - "grunt-contrib-jshint": "*", - "grunt-contrib-watch": "*" - }, - "scripts": { - "test": "grunt" + "dependencies": { + "parsimmon": "0.4.0" } } diff --git a/simpleSqlParser.js b/simpleSqlParser.js index fe6db2a..2ac4dea 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -1,768 +1,321 @@ (function(exports) { "use strict"; - function trim(str) { - if (typeof str == 'string') return str.replace(/^\s+/g,'').replace(/\s+$/g,''); - else return str; - } + // Load Parsimmon + if (typeof Parsimmon != 'object') var Parsimmon = require('Parsimmon'); - // Split a string using a separator, only if this separator isn't beetween brackets - function protect_split(separator, str) { - var sep = '######'; - - var string = false; - var nb_brackets = 0; - var new_str = ""; - for (var i = 0 ; i < str.length ; i++) { - if (!string && /['"`]/.test(str[i])) string = str[i]; - else if (string && str[i] == string) string = false; - else if (!string && str[i] == '(') nb_brackets ++; - else if (!string && str[i] == ')') nb_brackets --; - - if (str[i] == separator && (nb_brackets > 0 || string)) new_str += sep; - else new_str += str[i]; - } - str = new_str; - - str = str.split(separator); - str = str.map(function (item) { - return trim(item.replace(new RegExp(sep, 'g'), separator)); - }); - - return str; - } - // Add some # inside a string to avoid it to match a regex/split - function protect(str) { - var result = '#'; - var length = str.length; - for (var i = 0 ; i < length ; i++) result += str[i] + "#"; - return result; - } - // Restore a string output by protect() to its original state - function unprotect(str) { - var result = ''; - var length = str.length; - for (var i = 1 ; i < length ; i = i + 2) result += str[i]; - return result; - } + /******************************************************************************************** + ALIASES + ********************************************************************************************/ + var seq = Parsimmon.seq; + var alt = Parsimmon.alt; + var regex = Parsimmon.regex; + var string = Parsimmon.string; + var optWhitespace = Parsimmon.optWhitespace; + var whitespace = Parsimmon.whitespace; + var lazy = Parsimmon.lazy; - // Parse a query - // parseCond: (bool) parse conditions in WHERE and JOIN (default true) - exports.sql2ast = function (query, parseCond) { - if (typeof parseCond == 'undefined' || parseCond === null) parseCond = true; - - // Remove semi-colons and keep only the first query - var semi_colon = '###semi-colon###'; - query = query.replace(/[("'`].*;.*[)"'`]/g, function (match) { - return match.replace(/;/g, semi_colon); - }); - var eor = '###EOR###'; - query = query.replace(/;/g, eor); - query = query.split(eor)[0]; - query = query.replace(new RegExp(semi_colon, 'g'), ';'); - - // Define which words can act as separator - var keywords = ['SELECT', 'FROM', 'DELETE FROM', 'INSERT INTO', 'UPDATE', 'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'ORDER BY', 'GROUP BY', 'HAVING', 'WHERE', 'LIMIT', 'VALUES', 'SET']; - var parts_name = keywords.map(function (item) { - return item + ' '; - }); - parts_name = parts_name.concat(keywords.map(function (item) { - return item + '('; - })); - parts_name = parts_name.concat(parts_name.map(function (item) { - return item.toLowerCase(); - })); - var parts_name_escaped = parts_name.map(function (item) { - return item.replace('(', '[\\(]'); - }); - - // Hide words defined as separator but written inside brackets in the query - query = query.replace(/\((.+?)\)|"(.+?)"|'(.+?)'|`(.+?)`/gi, function (match) { - return match.replace(new RegExp(parts_name_escaped.join('|'), 'gi'), protect); - }); - - // Write the position(s) in query of these separators - var parts_order = []; - function realNameCallback(match, name) { - return name; - } - parts_name.forEach(function (item) { - var pos = 0; - var part; - - do { - part = query.indexOf(item, pos); - if (part != -1) { - var realName = item.replace(/^((\w|\s)+?)\s?\(?$/i, realNameCallback); - parts_order[part] = realName; // Position won't be exact because the use of protect() (above) and unprotect() alter the query string ; but we just need the order :) - pos = part + realName.length; - } - } - while (part != -1); - }); - - // Delete duplicates (caused, for example, by JOIN and INNER JOIN) - var busy_until = 0; - parts_order.forEach(function (item, key) { - if (busy_until > key) delete parts_order[key]; - else { - busy_until = parseInt(key, 10) + item.length; - - // Replace JOIN by INNER JOIN - if (item == 'JOIN') parts_order[key] = 'INNER JOIN'; - } - }); - - // Generate protected word list to reverse the use of protect() - var words = parts_name_escaped.slice(0); - words = words.map(function (item) { - return protect(item); - }); - words = words.join('|'); - - // Split parts - var parts = query.split(new RegExp(parts_name_escaped.join('|'), 'i')); - - // Unhide words precedently hidden with protect() - query = query.replace(/\((.+?)\)|"(.+?)"|'(.+?)'|`(.+?)`/gi, function (match) { - return match.replace(new RegExp(words, 'gi'), unprotect); - }); - parts = parts.map(function (item) { - return item.replace(/\((.+?)\)|"(.+?)"|'(.+?)'|`(.+?)`/gi, function (match) { - return match.replace(new RegExp(words, 'gi'), unprotect); - }); - }); - - // Define analysis functions - var analysis = []; - - analysis['SELECT'] = function (str) { - var result = protect_split(',', str); - result = result.filter(function(item) { - return item !== ''; - }).map(function(item) { - return {name: item}; - }); - return result; - }; - analysis['SET'] = function (str) { - var result = protect_split(',', str); - result = result.filter(function(item) { - return item !== ''; - }).map(function(item) { - return {expression: item}; - }); - return result; - }; - analysis['FROM'] = analysis['DELETE FROM'] = analysis['UPDATE'] = function (str) { - var result = str.split(','); - result = result.map(function(item) { - return trim(item); - }); - result.forEach(function(item, key) { - if (item === '') result.splice(key); - }); - result = result.map(function(item) { - var table = item.split(' AS '); - var alias = table[1] || ''; - if (alias.indexOf('"') === 0 && alias.lastIndexOf('"') == alias.length - 1) alias = alias.substring(1, alias.length - 1); - return {table: table[0], as: alias}; - }); - return result; - }; + /******************************************************************************************** + COMMON PATTERNS + ********************************************************************************************/ - analysis['LEFT JOIN'] = analysis['JOIN'] = analysis['INNER JOIN'] = analysis['RIGHT JOIN'] = function (str) { - str = str.split(' ON '); - var table = str[0].split(' AS '); - var result = {}; - result['table'] = trim(table[0]); - result['as'] = trim(table[1]) || ''; - result['cond'] = trim(str[1]); - - return result; - }; - - analysis['WHERE'] = function (str) { - return trim(str); - }; + // Make a parser optionnal + // "empty" parameter will be returned as result if the optionnal parser can't match + function opt(parser, empty) { + if (typeof empty == 'undefined') empty = []; + return parser.or(string('').map(function(node) { + return empty; + })); + } - analysis['ORDER BY'] = function (str) { - str = str.split(','); - var result = []; - str.forEach(function (item, key) { - var order_by = /([A-Za-z0-9_\.]+)\s*(ASC|DESC){0,1}/gi; - order_by = order_by.exec(item); - if (order_by !== null) { - var tmp = {}; - tmp['column'] = trim(order_by[1]); - tmp['order'] = trim(order_by[2]); - if(order_by[2] === undefined ){ - tmp['order']="ASC"; - } - result.push(tmp); - } - }); - return result; - }; - - analysis['GROUP BY'] = function (str) { - str = str.split(','); - var result = []; - str.forEach(function (item, key) { - var group_by = /([A-Za-z0-9_\.]+)/gi; - group_by = group_by.exec(item); - if (group_by !== null) { - var tmp = {}; - tmp['column'] = trim(group_by[1]); - result.push(tmp); - } - }); - return result; - }; - analysis['LIMIT'] = function (str) { - var limit = /((\d+)\s*,\s*)?(\d+)/gi; - limit = limit.exec(str); - if (typeof limit[2] == 'undefined') limit[2] = 0; - var result = {}; - result['nb'] = parseInt(trim(limit[3]), 10); - result['from'] = parseInt(trim(limit[2]), 10); - return result; - }; + // Join results of a parser + function mkString(node) { + return node.join(''); + } - analysis['INSERT INTO'] = function (str) { - var insert = /([A-Za-z0-9_\.]+)\s*(\(([A-Za-z0-9_\., ]+)\))?/gi; - insert = insert.exec(str); - var result = {}; - result['table'] = trim(insert[1]); - if (typeof insert[3] != 'undefined') { - result['columns'] = insert[3].split(','); - result['columns'] = result['columns'].map(function (item) { - return trim(item); - }); - } - return result; - }; + // Add an item to an optionnal list and return the final list + function mergeOptionnalList(node) { + node[0].push(node[1]); + return node[0]; + } - analysis['VALUES'] = function (str) { - str = trim(str); - if (str[0] != '(') str = '(' + str; // If query has "VALUES(...)" instead of "VALUES (...)" - var groups = protect_split(',', str); - var result = []; - groups.forEach(function(group) { - group = group.replace(/^\(/g,'').replace(/\)$/g,''); - group = protect_split(',', group); - result.push(group); - }); - return result; - }; + // Generate a parser that accept a comma-separated list of something + function optionnalList(parser) { + return seq( + parser.skip(optWhitespace).skip(string(',')).skip(optWhitespace).many(), + parser.skip(optWhitespace) + ).map(mergeOptionnalList); + } - // TODO: handle HAVING - - // Analyze parts - var result = {}; - var j = 0; - parts_order.forEach(function (item, key) { - item = item.toUpperCase(); - j++; - if (typeof analysis[item] != 'undefined') { - var part_result = analysis[item](parts[j]); - - if (typeof result[item] != 'undefined') { - if (typeof result[item] == 'string' || typeof result[item][0] == 'undefined') { - var tmp = result[item]; - result[item] = []; - result[item].push(tmp); - } - - result[item].push(part_result); - } - else result[item] = part_result; - } - else console.log('Can\'t analyze statement "' + item + '"'); - }); - - // Reorganize joins - if (typeof result['LEFT JOIN'] != 'undefined') { - if (typeof result['JOIN'] == 'undefined') result['JOIN'] = []; - if (typeof result['LEFT JOIN'][0] != 'undefined') { - result['LEFT JOIN'].forEach(function (item) { - item.type = 'left'; - result['JOIN'].push(item); - }); - } - else { - result['LEFT JOIN'].type = 'left'; - result['JOIN'].push(result['LEFT JOIN']); - } - delete result['LEFT JOIN']; - } - if (typeof result['INNER JOIN'] != 'undefined') { - if (typeof result['JOIN'] == 'undefined') result['JOIN'] = []; - if (typeof result['INNER JOIN'][0] != 'undefined') { - result['INNER JOIN'].forEach(function (item) { - item.type = 'inner'; - result['JOIN'].push(item); - }); - } - else { - result['INNER JOIN'].type = 'inner'; - result['JOIN'].push(result['INNER JOIN']); - } - delete result['INNER JOIN']; - } - if (typeof result['RIGHT JOIN'] != 'undefined') { - if (typeof result['JOIN'] == 'undefined') result['JOIN'] = []; - if (typeof result['RIGHT JOIN'][0] != 'undefined') { - result['RIGHT JOIN'].forEach(function (item) { - item.type = 'right'; - result['JOIN'].push(item); - }); - } - else { - result['RIGHT JOIN'].type = 'right'; - result['JOIN'].push(result['RIGHT JOIN']); - } - delete result['RIGHT JOIN']; - } - - // Parse conditions - if (parseCond) { - if (typeof result['WHERE'] == 'string') { - result['WHERE'] = CondParser.parse(result['WHERE']); - } - if (typeof result['JOIN'] != 'undefined') { - result['JOIN'].forEach(function (item, key) { - result['JOIN'][key]['cond'] = CondParser.parse(item['cond']); - }); - } - } - - return result; - }; - /* - * LEXER & PARSER FOR SQL CONDITIONS - * Inspired by https://github.com/DmitrySoshnikov/Essentials-of-interpretation - */ + /******************************************************************************************** + LOW LEVEL PARSERS + ********************************************************************************************/ + + // The name of a column/table + var colName = alt( + regex(/(?!(FROM|AS)\s)[a-zA-Z0-9_*]+/i), + regex(/`[^`\\]*(?:\\.[^`\\]*)*`/) + ); + + // A string + var str = alt( + regex(/"[^"\\]*(?:\\.[^"\\]*)*"/), + regex(/'[^'\\]*(?:\\.[^'\\]*)*'/) + ); + + // A function expression + var func = seq( + alt( + regex(/[a-zA-Z0-9_]+\(/), + string('(') + ), + opt(lazy(function() { return argList; })).map(mkString), + string(')') + ).map(mkString); + + // A table.column expression + var tableAndColumn = seq( + colName, + string('.'), + colName + ); + + // An operator + var operator = alt( + string('+'), + string('-'), + string('*'), + string('/'), + string('&'), + string('~'), + string('|'), + string('^'), + regex(/XOR/i), + string('<=>'), + string('='), + string('!='), + string('>'), + string('>='), + string('<'), + string('<='), + regex(/IS NULL/i), + regex(/IS NOT/i), + regex(/IS NOT NULL/i), + regex(/IS/i), + string('>>'), + string('<<'), + regex(/LIKE/i), + regex(/NOT LIKE/i), + string('%'), + regex(/MOD/i), + regex(/NOT/i), + string('||'), + regex(/OR/i), + string('&&'), + regex(/AND/i) + ); + + // A number + var number = regex(/[-]?\d+\.?\d*/); + + + + /******************************************************************************************** + EXPRESSION PARSERS + ********************************************************************************************/ + + // Mathematical expression + var mathExpression = seq( + alt( + tableAndColumn, + number, + func, + colName, + str + ), + optWhitespace, + opt(operator, null), + optWhitespace, + opt(lazy(function() { return mathExpression; })) + ).map(mkString); + + // Column expresion + var colExpression = alt( + tableAndColumn.map(function(node) { + return { + expression: node.join(''), + table: node[0], + column: node[2], + TandC: true + }; + }), + colName.map(function(node) { + return { + expression: node, + table: null, + column: node, + col: true + }; + }), + mathExpression.map(function(node) { + return { + expression: node.trim(), + table: null, + column: null, + math: true + }; + }), + func.map(function(node) { + return { + expression: node, + table: null, + column: null, + func: true + }; + }), + str.map(function(node) { + return { + expression: node, + table: null, + column: null, + str: true + }; + }) + ); + + // Expression following a SELECT statement + var colListExpression = seq( + colExpression, + opt( // Alias + seq( + optWhitespace, + opt(regex(/AS\s/i)), + alt(colName, str), + optWhitespace + ).map(function(node) { + var n = {}; + n.alias = node[2]; + n.expression = node.join(''); + return n; + }), + null + ) + ).map(function(node) { + var n = node[0]; + n.alias = (node[1] !== null) ? node[1].alias : null; + n.expression = n.expression + ((node[1] !== null) ? node[1].expression : ''); + return n; + }); + + // Expression inside a function + var argListExpression = alt( + str, + func, + colExpression.map(function(node) { + return node.expression; + }) + ); + + // Expression following a FROM statement + var tableListExpression = seq( + alt( + tableAndColumn.map(mkString), + colName + ), + opt( // Alias + seq( + optWhitespace, + opt(regex(/AS/i)), + optWhitespace, + alt(colName, str), + optWhitespace + ).map(function(node) { + return node[3]; + }), + null + ) + ).map(function(node) { + var n = {}; + n.table = node[0]; + n.alias = node[1]; + return n; + }); + + + + /******************************************************************************************** + HIGH LEVEL PARSERS + ********************************************************************************************/ + + // List of arguments inside a function + var argList = seq( + seq(argListExpression, optWhitespace, string(','), optWhitespace).map(mkString).many(), + argListExpression.skip(optWhitespace) + ).map(mergeOptionnalList); + + // List of expressions following a SELECT statement + var colList = optionnalList(colListExpression); + + // List of table following a FROM statement + var tableList = optionnalList(tableListExpression); + + + + /******************************************************************************************** + MAIN PARSERS + ********************************************************************************************/ + + // SELECT parser + var p = seq( + regex(/SELECT/i).skip(optWhitespace).then(opt(colList)), + regex(/FROM/i).skip(optWhitespace).then(opt(tableList)) + ).map(function(node) { + return { + type: 'select', + select: node[0], + from: node[1] + }; + }); - // Constructor - function CondLexer(source) { - this.source = source; - this.cursor = 0; - this.currentChar = ""; - this.readNextChar(); - } - CondLexer.prototype = { - constructor: CondLexer, - - // Read the next character (or return an empty string if cursor is at the end of the source) - readNextChar: function () { - if (typeof this.source != 'string') this.currentChar = ""; - else this.currentChar = this.source[this.cursor++] || ""; - }, - - // Determine the next token - readNextToken: function () { - if (/\w/.test(this.currentChar)) return this.readWord(); - if (/["'`]/.test(this.currentChar)) return this.readString(); - if (/[()]/.test(this.currentChar)) return this.readGroupSymbol(); - if (/[!=<>]/.test(this.currentChar)) return this.readOperator(); - - if (this.currentChar === "") return {type: 'eot', value: ''}; - else { - this.readNextChar(); - return {type: 'empty', value: ''}; - } - }, - - readWord: function () { - var tokenValue = ""; - var nb_brackets = 0; - var string = false; - while (/./.test(this.currentChar)) { - // Check if we are in a string - if (!string && /['"`]/.test(this.currentChar)) string = this.currentChar; - else if (string && this.currentChar == string) string = false; - else { - // Allow spaces inside functions (only if we are not in a string) - if (!string) { - // Token is finished if there is a closing bracket outside a string and with no opening - if (this.currentChar == ')' && nb_brackets <= 0) break; - - if (this.currentChar == '(') nb_brackets++; - else if (this.currentChar == ')') nb_brackets--; - - // Token is finished if there is a operator symbol outside a string - if (/[!=<>]/.test(this.currentChar)) break; - } - - // Token is finished on the first space which is outside a string or a function - if (this.currentChar == ' ' && nb_brackets <= 0) break; - } - - tokenValue += this.currentChar; - this.readNextChar(); - } - - if (/^(AND|OR)$/i.test(tokenValue)) return {type: 'logic', value: tokenValue}; - if (/^(IS|NOT)$/i.test(tokenValue)) return {type: 'operator', value: tokenValue}; - else return {type: 'word', value: tokenValue}; - }, - - readString: function () { - var tokenValue = ""; - var quote = this.currentChar; - - tokenValue += this.currentChar; - this.readNextChar(); - - while (this.currentChar != quote && this.currentChar !== "") { - tokenValue += this.currentChar; - this.readNextChar(); - } - - tokenValue += this.currentChar; - this.readNextChar(); - - // Handle this case : `table`.`column` - if (this.currentChar == '.') { - tokenValue += this.currentChar; - this.readNextChar(); - tokenValue += this.readString().value; - - return {type: 'word', value: tokenValue}; - } - - return {type: 'string', value: tokenValue}; - }, - - readGroupSymbol: function () { - var tokenValue = this.currentChar; - this.readNextChar(); - - return {type: 'group', value: tokenValue}; - }, - - readOperator: function () { - var tokenValue = this.currentChar; - this.readNextChar(); - - if (/[=<>]/.test(this.currentChar)) { - tokenValue += this.currentChar; - this.readNextChar(); - } - - return {type: 'operator', value: tokenValue}; - }, - }; + /******************************************************************************************** + PUBLIC FUNCTIONS + ********************************************************************************************/ - // Tokenise a string (only useful for debug) - CondLexer.tokenize = function (source) { - var lexer = new CondLexer(source); - var tokens = []; - do { - var token = lexer.readNextToken(); - if (token.type != 'empty') tokens.push(token); - } - while (lexer.currentChar); - return tokens; + exports.sql2ast = function(sql) { + return p.parse(sql); }; +})(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports); - // Constructor - function CondParser(source) { - this.lexer = new CondLexer(source); - this.currentToken = ""; - - this.readNextToken(); - } - - CondParser.prototype = { - constructor: CondParser, - - // Read the next token (skip empty tokens) - readNextToken: function () { - this.currentToken = this.lexer.readNextToken(); - while (this.currentToken.type == 'empty') this.currentToken = this.lexer.readNextToken(); - return this.currentToken; - }, - - // Wrapper function ; parse the source - parseExpressionsRecursively: function () { - return this.parseLogicalExpression(); - }, - - // Parse logical expressions (AND/OR) - parseLogicalExpression: function () { - var leftNode = this.parseConditionExpression(); - - while (this.currentToken.type == 'logic') { - var logic = this.currentToken.value; - this.readNextToken(); - - var rightNode = this.parseConditionExpression(); - - // If we are chaining the same logical operator, add nodes to existing object instead of creating another one - if (typeof leftNode.logic != 'undefined' && leftNode.logic == logic && typeof leftNode.terms != 'undefined') leftNode.terms.push(rightNode); - else { - var terms = [leftNode, rightNode]; - leftNode = {'logic': logic, 'terms': terms.slice(0)}; - } - } - - return leftNode; - }, - - // Parse conditions ([word/string] [operator] [word/string]) - parseConditionExpression: function () { - var leftNode = this.parseBaseExpression(); - - if (this.currentToken.type == 'operator') { - var operator = this.currentToken.value; - this.readNextToken(); - - // If there are 2 adjacent operators, join them with a space (exemple: IS NOT) - if (this.currentToken.type == 'operator') { - operator += ' ' + this.currentToken.value; - this.readNextToken(); - } - - var rightNode = this.parseBaseExpression(); - - leftNode = {'operator': operator, 'left': leftNode, 'right': rightNode}; - } - - return leftNode; - }, - - // Parse base items - parseBaseExpression: function () { - var astNode = ""; - - // If this is a word/string, return its value - if (this.currentToken.type == 'word' || this.currentToken.type == 'string') { - astNode = this.currentToken.value; - this.readNextToken(); - } - // If this is a group, skip brackets and parse the inside - else if (this.currentToken.type == 'group') { - this.readNextToken(); - astNode = this.parseExpressionsRecursively(); - this.readNextToken(); - } - - return astNode; - }, - }; - // Parse a string - CondParser.parse = function (source) { - return new CondParser(source).parseExpressionsRecursively(); - }; +var simpleSqlParser = require('./simpleSqlParser.js'); - // Generate the SQL query corresponding to an AST output by sql2ast() - exports.ast2sql = function (ast) { - var result = ''; - - // Define subfunctions - function select(ast) { - if (typeof ast['SELECT'] != 'undefined') { - return 'SELECT ' + ast['SELECT'].map(function(item) { - return item.name; - }).join(', '); - } - else return ''; - } - - function from(ast) { - if (typeof ast['FROM'] != 'undefined') { - var result = ' FROM '; - var tmp = ast['FROM'].map(function (item) { - var str = item.table; - if (item.as !== '') str += ' AS ' + item.as; - return str; - }); - result += tmp.join(', '); - return result; - } - else return ''; - } - - function join(ast) { - if (typeof ast['JOIN'] != 'undefined') { - var result = ''; - ast['JOIN'].forEach(function(item) { - result += ' ' + item.type.toUpperCase() + ' JOIN ' + item.table; - if (item.as !== '') result += ' AS ' + item.as; - result += ' ON ' + cond2sql(item.cond); - }); - return result; - } - else return ''; - } - - function where(ast) { - if (typeof ast['WHERE'] != 'undefined') { - return ' WHERE ' + cond2sql(ast['WHERE']); - } - else return ''; - } - - function order_by(ast) { - if (typeof ast['ORDER BY'] != 'undefined') { - var result = ' ORDER BY '; - var orders = ast['ORDER BY'].map(function (item) { - return item.column + ' ' + item.order; - }); - result += orders.join(', '); - return result; - } - else return ''; - } - - function group_by(ast) { - if (typeof ast['GROUP BY'] != 'undefined') { - var result = ' GROUP BY '; - var groups = ast['GROUP BY'].map(function (item) { - return item.column; - }); - result += groups.join(', '); - return result; - } - else return ''; - } - - function limit(ast) { - if (typeof ast['LIMIT'] != 'undefined' && typeof ast['LIMIT'].nb != 'undefined' && parseInt(ast['LIMIT'].nb, 10) > 0) { - var result = ' LIMIT '; - if (typeof ast['LIMIT'].from != 'undefined' && parseInt(ast['LIMIT'].from, 10) > 1) result += ast['LIMIT'].from + ','; - result += ast['LIMIT'].nb; - return result; - } - else return ''; - } - - function insert_into(ast) { - if (typeof ast['INSERT INTO'] != 'undefined') { - var result = 'INSERT INTO ' + ast['INSERT INTO'].table; - if (typeof ast['INSERT INTO'].columns != 'undefined') { - result += ' ('; - result += ast['INSERT INTO'].columns.join(', '); - result += ')'; - } - return result; - } - else return ''; - } - - function values(ast) { - if (typeof ast['VALUES'] != 'undefined') { - var result = ' VALUES '; - var vals = ast['VALUES'].map(function (item) { - return '(' + item.join(', ') + ')'; - }); - result += vals.join(', '); - return result; - } - else return ''; - } - - function delete_from(ast) { - if (typeof ast['DELETE FROM'] != 'undefined') { - var result = 'DELETE FROM '; - result += ast['DELETE FROM'].map(function (item) { - var str = item.table; - if (item.as !== '') str += ' AS ' + item.as; - return str; - }).join(', '); - return result; - } - else return ''; - } - - function update(ast) { - if (typeof ast['UPDATE'] != 'undefined') { - var result = 'UPDATE '; - result += ast['UPDATE'].map(function (item) { - var str = item.table; - if (item.as !== '') str += ' AS ' + item.as; - return str; - }).join(', '); - return result; - } - else return ''; - } - - function set(ast) { - if (typeof ast['SET'] != 'undefined') { - return ' SET ' + ast['SET'].map(function(item) { - return item.expression; - }).join(', '); - } - else return ''; - } - - - // Check request's type - if (typeof ast['SELECT'] != 'undefined' && typeof ast['FROM'] != 'undefined') { - result = select(ast) + from(ast) + join(ast) + where(ast) + group_by(ast) + order_by(ast) + limit(ast); - } - else if (typeof ast['INSERT INTO'] != 'undefined') { - result = insert_into(ast) + values(ast); - } - else if (typeof ast['UPDATE'] != 'undefined') { - result = update(ast) + set(ast) + where(ast); - } - else if (typeof ast['DELETE FROM'] != 'undefined') { - result = delete_from(ast) + where(ast); - } - else result = null; - - return result; - }; - // Generate SQL from a condition AST output by sql2ast() or CondParser - function cond2sql(cond, not_first) { - var result = ''; - - // If there is a logical operation - if (typeof cond.logic != 'undefined') { - result = cond.terms.map(function (item) { - return cond2sql(item, true); - }); - result = result.join(' ' + cond.logic + ' '); - if (typeof not_first !== 'undefined') result = '(' + result + ')'; - } - // If there is a condition - else if (typeof cond.left != 'undefined') { - result = cond.left; - if (typeof cond.operator != 'undefined') { - result += ' ' + cond.operator; - if (typeof cond.right != 'undefined') { - result += ' ' + cond.right; - } - } - } - // If there is a boolean - else result = cond; - - return result; +// Tests (will be rewritten/automatized/externalized) +var q = [ + 'SELECT colname1 AS col1, colname2 "col2", "k\'ge\\"rg",`gfrg` , y, \'other string\' FROM table', + 'SELECT FROM table1, table2 AS t2', + 'SELECT * FROM table', + 'SELECT a, NOW(), TRUC("test\\" ", table.col , MACHIN(NOW())), b FROM table', + 'SELECT table.col FROM table', + 'SELECT (1+2) / MACHIN(6.6, 2, table.`col`) AS truc FROM' +]; +q.forEach(function(query) { + var ast = simpleSqlParser.sql2ast(query); + if (ast.status) { + console.log('--> ' + query); + console.log(ast.value); } + else console.log(ast); + console.log(); +}); - // Export some methods for tests - exports.trim = trim; - exports.protect = protect; - exports.unprotect = unprotect; - exports.protect_split = protect_split; - exports.CondLexer = CondLexer; - exports.CondParser = CondParser; - -}(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports)); diff --git a/tests/tests.html b/tests/tests.html deleted file mode 100644 index 212e226..0000000 --- a/tests/tests.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - Tests - simpleSqlParser - - - - -
- - - - - diff --git a/tests/tests.js b/tests/tests.js deleted file mode 100644 index 4339c97..0000000 --- a/tests/tests.js +++ /dev/null @@ -1,835 +0,0 @@ -(function () { - "use strict"; - - var m = simpleSqlParser; - - module('sql2ast'); - - test('trim', function () { - expect(9); - - deepEqual(m.trim('test'), 'test'); - deepEqual(m.trim('test '), 'test'); - deepEqual(m.trim(' test'), 'test'); - deepEqual(m.trim(' test '), 'test'); - deepEqual(m.trim('test test'), 'test test'); - deepEqual(m.trim(' test \ - '), 'test'); - - var integer = 5; - var array = [0, 1, 2]; - var object = {test: "test", great_aswer: 42}; - deepEqual(m.trim(integer), integer); - deepEqual(m.trim(array), array); - deepEqual(m.trim(object), object); - }); - - test('protect/unprotect', function () { - expect(9); - - deepEqual(m.protect('test'), '#t#e#s#t#'); - deepEqual(m.protect('#t#e#s#t#'), '###t###e###s###t###'); - - deepEqual(m.unprotect('#t#e#s#t#'), 'test'); - deepEqual(m.unprotect('###t###e###s###t###'), '#t#e#s#t#'); - - var string = "this is a (complex) string, with some special chars like !:@.#ù%$^\ - and also a line break and des caractères français !"; - deepEqual(m.unprotect(m.protect(string)), string); - - deepEqual(m.protect(''), '#'); - deepEqual(m.unprotect('#'), ''); - deepEqual(m.protect('#'), '###'); - deepEqual(m.unprotect('###'), '#'); - }); - - test('protect_split', function () { - expect(10); - - deepEqual(m.protect_split(',', 'test'), ['test']); - deepEqual(m.protect_split(',', 'test(1,2)'), ['test(1,2)']); - deepEqual(m.protect_split(',', 'test1,test2'), ['test1', 'test2']); - deepEqual(m.protect_split(',', 'test1,(test2,test3)'), ['test1', '(test2,test3)']); - deepEqual(m.protect_split(';', 'test1;(test2;test3)'), ['test1', '(test2;test3)']); - deepEqual(m.protect_split(',', 'test1,"test2,test3"'), ['test1', '"test2,test3"']); - deepEqual(m.protect_split(',', 'test1,\'test2,test3\''), ['test1', '\'test2,test3\'']); - deepEqual(m.protect_split(',', 'test1,`test2,test3`'), ['test1', '`test2,test3`']); - deepEqual(m.protect_split(',', 'test1,function(test2,"test3(test4)\'test5\'"),test6()'), ['test1', 'function(test2,"test3(test4)\'test5\'")', 'test6()']); - deepEqual(m.protect_split(',', 'column1, column2, FUNCTION("string )\'\'", column3), column4 AS Test1,column5 AS "Test 2", column6 "Test,3" '), [ - 'column1', - 'column2', - 'FUNCTION("string )\'\'", column3)', - 'column4 AS Test1', - 'column5 AS "Test 2"', - 'column6 "Test,3"', - ]); - }); - - test('condition lexer', function () { - expect(19); - - deepEqual(m.CondLexer.tokenize('column = othercolumn'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - ]); - - deepEqual(m.CondLexer.tokenize('column=othercolumn'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = \ - othercolumn'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column = othertable.othercolumn'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othertable.othercolumn'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column <= othertable.othercolumn'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '<='}, - {type: 'word', value: 'othertable.othercolumn'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column = "string"'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column = FUNCTION("string")'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'FUNCTION("string")'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column = FUNCTION("string", columns,"otherstring" ,\ - "string with SQL stuff like = AND OR ()")'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'FUNCTION("string", columns,"otherstring" ,\ - "string with SQL stuff like = AND OR ()")'}, - ]); - - deepEqual(m.CondLexer.tokenize('table.column != "string with SQL stuff like = AND OR ()"'), [ - {type: 'word', value: 'table.column'}, - {type: 'operator', value: '!='}, - {type: 'string', value: '"string with SQL stuff like = AND OR ()"'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn AND column < 2'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn AND column < 2 OR column = "string"'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'logic', value: 'OR'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn OR column < 2 AND column = "string"'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'OR'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - ]); - - deepEqual(m.CondLexer.tokenize('(column = othercolumn AND column < 2) OR column = "string"'), [ - {type: 'group', value: '('}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'group', value: ')'}, - {type: 'logic', value: 'OR'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn AND (column < 2 OR column = "string")'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'group', value: '('}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'logic', value: 'OR'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - {type: 'group', value: ')'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn AND column < 2 AND column = "string"'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - ]); - - deepEqual(m.CondLexer.tokenize('(column = othercolumn)'), [ - {type: 'group', value: '('}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'group', value: ')'}, - ]); - - deepEqual(m.CondLexer.tokenize('column = othercolumn AND (column < 2 OR (column = "string" AND table.othercolumn))'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'word', value: 'othercolumn'}, - {type: 'logic', value: 'AND'}, - {type: 'group', value: '('}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '<'}, - {type: 'word', value: '2'}, - {type: 'logic', value: 'OR'}, - {type: 'group', value: '('}, - {type: 'word', value: 'column'}, - {type: 'operator', value: '='}, - {type: 'string', value: '"string"'}, - {type: 'logic', value: 'AND'}, - {type: 'word', value: 'table.othercolumn'}, - {type: 'group', value: ')'}, - {type: 'group', value: ')'}, - ]); - - deepEqual(m.CondLexer.tokenize('column IS NULL'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: 'IS'}, - {type: 'word', value: 'NULL'}, - ]); - - deepEqual(m.CondLexer.tokenize('column IS NOT NULL'), [ - {type: 'word', value: 'column'}, - {type: 'operator', value: 'IS'}, - {type: 'operator', value: 'NOT'}, - {type: 'word', value: 'NULL'}, - ]); - }); - - test('condition parser', function () { - expect(20); - - deepEqual(m.CondParser.parse('column = othercolumn'), {left: 'column', operator: '=', right: 'othercolumn'}); - - deepEqual(m.CondParser.parse('column=othercolumn'), {left: 'column', operator: '=', right: 'othercolumn'}); - - deepEqual(m.CondParser.parse('column = \ - othercolumn'), {left: 'column', operator: '=', right: 'othercolumn'}); - - deepEqual(m.CondParser.parse('table.column = othertable.othercolumn'), {left: 'table.column', operator: '=', right: 'othertable.othercolumn'}); - - deepEqual(m.CondParser.parse('table.column <= othertable.othercolumn'), {left: 'table.column', operator: '<=', right: 'othertable.othercolumn'}); - - deepEqual(m.CondParser.parse('table.column = "string"'), {left: 'table.column', operator: '=', right: '"string"'}); - - deepEqual(m.CondParser.parse('table.column = FUNCTION("string")'), {left: 'table.column', operator: '=', right: 'FUNCTION("string")'}); - - deepEqual(m.CondParser.parse('table.column = FUNCTION("string", columns,"otherstring" ,\ - "string with SQL stuff like = AND OR ()")'), {left: 'table.column', operator: '=', right: 'FUNCTION("string", columns,"otherstring" ,\ - "string with SQL stuff like = AND OR ()")'}); - - deepEqual(m.CondParser.parse('table.column != "string with SQL stuff like = AND OR ()"'), {left: 'table.column', operator: '!=', right: '"string with SQL stuff like = AND OR ()"'}); - - deepEqual(m.CondParser.parse('column = othercolumn AND column < 2'), { - logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {left: 'column', operator: '<', right: '2'}, - ]}); - - deepEqual(m.CondParser.parse('column = othercolumn AND column < 2 OR column = "string"'), { - logic: 'OR', terms: [ - {logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {left: 'column', operator: '<', right: '2'}, - ]}, - {left: 'column', operator: '=', right: '"string"'}, - ]}); - - deepEqual(m.CondParser.parse('column = othercolumn OR column < 2 AND column = "string"'), { - logic: 'AND', terms: [ - {logic: 'OR', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {left: 'column', operator: '<', right: '2'}, - ]}, - {left: 'column', operator: '=', right: '"string"'}, - ]}); - - deepEqual(m.CondParser.parse('(column = othercolumn AND column < 2) OR column = "string"'), { - logic: 'OR', terms: [ - {logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {left: 'column', operator: '<', right: '2'}, - ]}, - {left: 'column', operator: '=', right: '"string"'}, - ]}); - - deepEqual(m.CondParser.parse('column = othercolumn AND (column < 2 OR column = "string")'), { - logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {logic: 'OR', terms: [ - {left: 'column', operator: '<', right: '2'}, - {left: 'column', operator: '=', right: '"string"'}, - ]}, - ]}); - - deepEqual(m.CondParser.parse('column = othercolumn AND column < 2 AND column = "string"'), { - logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {left: 'column', operator: '<', right: '2'}, - {left: 'column', operator: '=', right: '"string"'}, - ]}); - - deepEqual(m.CondParser.parse('(column = othercolumn)'), {left: 'column', operator: '=', right: 'othercolumn'}); - - deepEqual(m.CondParser.parse('column = othercolumn AND (column < 2 OR (column = "string" AND table.othercolumn))'), { - logic: 'AND', terms: [ - {left: 'column', operator: '=', right: 'othercolumn'}, - {logic: 'OR', terms: [ - {left: 'column', operator: '<', right: '2'}, - {logic: 'AND', terms: [ - {left: 'column', operator: '=', right: '"string"'}, - 'table.othercolumn', - ]}, - ]}, - ]}); - - deepEqual(m.CondParser.parse('(a AND b) OR (c AND d)'), { - logic: 'OR', terms: [ - {logic: 'AND', terms: ['a', 'b']}, - {logic: 'AND', terms: ['c', 'd']}, - ]}); - - deepEqual(m.CondParser.parse('column IS NULL'), {left: 'column', operator: 'IS', right: 'NULL'}); - - deepEqual(m.CondParser.parse('column IS NOT NULL'), {left: 'column', operator: 'IS NOT', right: 'NULL'}); - }); - - test('parse SQL', function() { - expect(30); - var q; - - q = 'SELECT * FROM table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'select * from table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'SELECT * FROM table;'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - - q = 'SELECT * FROM table AS t'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: 't', - }], - }, q); - - q = 'SELECT * FROM table; SELECT * FROM table2'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'SELECT column1, column2, FUNCTION("string ()\'\'", column3), column4 AS Test1,column5 AS "Test 2", column6 "Test 3" FROM table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [ - {name: 'column1'}, - {name: 'column2'}, - {name: 'FUNCTION("string ()\'\'", column3)'}, - {name: 'column4 AS Test1'}, - {name: 'column5 AS "Test 2"'}, - {name: 'column6 "Test 3"'}, - ], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'SELECT FUCTION("SQL syntax like: FROM or LIMIT", \'SQL syntax like: FROM or LIMIT\', `SQL syntax like: FROM or LIMIT`) FROM table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: 'FUCTION("SQL syntax like: FROM or LIMIT", \'SQL syntax like: FROM or LIMIT\', `SQL syntax like: FROM or LIMIT`)'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'SELECT * FROM table1,table2 AS t2 , table3 AS "t 3"'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [ - {table: 'table1', as: ''}, - {table: 'table2', as: 't2'}, - {table: 'table3', as: 't 3'}, - ], - }, q); - - q = 'SELECT * FROM table LEFT JOIN table2 ON table.id = table2.id_table INNER JOIN table4 AS t4 ON table.id = FUNCTION(table4.id_table, "string()") JOIN table3 ON table.id=table3.id_table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'JOIN': [ - { - type: 'left', - table: 'table2', - as: '', - cond: {left: 'table.id', operator: '=', right: 'table2.id_table'}, - }, - { - type: 'inner', - table: 'table4', - as: 't4', - cond: {left: 'table.id', operator: '=', right: 'FUNCTION(table4.id_table, "string()")'}, - }, - { - type: 'inner', - table: 'table3', - as: '', - cond: {left: 'table.id', operator: '=', right: 'table3.id_table'}, - }, - ], - }, q); - - q = 'SELECT * FROM table LEFT JOIN table10 ON table.id = table10.id RIGHT JOIN table2 ON table.id = table2.id INNER JOIN table3 AS t3 ON table.id = FUNCTION(table4.id_table, "string()") JOIN table4 ON table.id=table4.id'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'JOIN': [ - { - type: 'left', - table: 'table10', - as: '', - cond: {left: 'table.id', operator: '=', right: 'table10.id'}, - }, - { - type: 'inner', - table: 'table3', - as: 't3', - cond: {left: 'table.id', operator: '=', right: 'FUNCTION(table4.id_table, "string()")'}, - }, - { - type: 'inner', - table: 'table4', - as: '', - cond: {left: 'table.id', operator: '=', right: 'table4.id'}, - }, - { - type: 'right', - table: 'table2', - as: '', - cond: {left: 'table.id', operator: '=', right: 'table2.id'}, - }, - ], - }, q); - - q = 'SELECT * FROM table LEFT JOIN table2 AS t2 ON table.id = t2.id_table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'JOIN': [ - { - type: 'left', - table: 'table2', - as: 't2', - cond: {left: 'table.id', operator: '=', right: 't2.id_table'}, - }, - ], - }, q); - - // This is an invalid query, but the parser should not crash - q = 'SELECT * FROM table LEFT JOIN table2 table.id = t2.id_table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'JOIN': [ - { - type: 'left', - table: 'table2 table.id = t2.id_table', - as: '', - cond: '', - }, - ], - }, q); - - q = 'SELECT * FROM table WHERE (column1 = "something ()" AND table.column2 != column3) AND (column4 OR column5 IS NOT NULL)'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'WHERE': { - logic: 'AND', terms: [ - {left: 'column1', operator: '=', right: '"something ()"'}, - {left: 'table.column2', operator: '!=', right: 'column3'}, - {logic: 'OR', terms: [ - 'column4', - {left: 'column5', operator: 'IS NOT', right: 'NULL'}, - ]}, - ], - }, - }, q); - - q = 'SELECT * FROM table ORDER BY column1 ASC, column2 DESC'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'ORDER BY': [ - {column: 'column1', order: 'ASC'}, - {column: 'column2', order: 'DESC'}, - ], - }, q); - - q = 'SELECT * FROM table ORDER BY column1, column2'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'ORDER BY': [ - {column: 'column1', order: 'ASC'}, - {column: 'column2', order: 'ASC'}, - ], - }, q); - - q = 'SELECT * FROM table GROUP BY column1, column2'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'GROUP BY': [ - {column: 'column1'}, - {column: 'column2'}, - ], - }, q); - - deepEqual(m.sql2ast('SELECT * FROM table LIMIT 5'), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'LIMIT': {nb: 5, from: 0}, - }, q); - - q = 'SELECT * FROM table LIMIT 10,20'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: '*'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'LIMIT': {nb: 20, from: 10}, - }, q); - - q = 'SELECT EXTRACT(MICROSECOND FROM "2003-01-02 10:30:00.00123") FROM table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: 'EXTRACT(MICROSECOND FROM "2003-01-02 10:30:00.00123")'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'SELECT column1, FROM table, ORDER BY id ASC,'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: 'column1'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'ORDER BY': [{column: 'id', order: 'ASC'}], - }, q); - - q = 'SELECT column1, FROM table, GROUP BY id,'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: 'column1'}], - 'FROM': [{ - table: 'table', - as: '', - }], - 'GROUP BY': [{column: 'id'}], - }, q); - - q = 'SELECT column1, setup, column2 FROM table'; - deepEqual(m.sql2ast(q), { - 'SELECT': [{name: 'column1'}, {name: 'setup'}, {name: 'column2'}], - 'FROM': [{ - table: 'table', - as: '', - }], - }, q); - - q = 'DELETE FROM table WHERE id = 5'; - deepEqual(m.sql2ast(q), { - 'DELETE FROM': [{ - table: 'table', - as: '', - }], - 'WHERE': {left: 'id', operator: '=', right: '5'}, - }, q); - - q = 'DELETE FROM table WHERE id = 5'; - deepEqual(m.sql2ast(q, false), { - 'DELETE FROM': [{ - table: 'table', - as: '', - }], - 'WHERE': 'id = 5', - }, q); - - q = 'INSERT INTO table (column1, column2) VALUES("test ()", CURDATE())'; - deepEqual(m.sql2ast(q), { - 'INSERT INTO': { - table: 'table', - columns: ['column1', 'column2'], - }, - 'VALUES': [['"test ()"', 'CURDATE()']], - }, q); - - q = 'INSERT INTO table (col_A,col_B,col_C) VALUES (1,2,3)'; - deepEqual(m.sql2ast(q), { - 'INSERT INTO': { - table: 'table', - columns: ['col_A', 'col_B', 'col_C'], - }, - 'VALUES': [['1', '2', '3']], - }, q); - - q = 'INSERT INTO table VALUES (1,2,3), (4,5,6), (7,8,9)'; - deepEqual(m.sql2ast(q), { - 'INSERT INTO': {table: 'table'}, - 'VALUES': [ - ['1', '2', '3'], - ['4', '5', '6'], - ['7', '8', '9'], - ], - }, q); - - q = 'INSERT INTO table (col_A,col_B,col_C) VALUES (1,2,3), (4,5,6), (7,8,9)'; - deepEqual(m.sql2ast(q), { - 'INSERT INTO': { - table: 'table', - columns: ['col_A', 'col_B', 'col_C'], - }, - 'VALUES': [ - ['1', '2', '3'], - ['4', '5', '6'], - ['7', '8', '9'], - ], - }, q); - - q = 'INSERT INTO table VALUES (1,2,3)'; - deepEqual(m.sql2ast(q), { - 'INSERT INTO': {table: 'table'}, - 'VALUES': [['1', '2', '3']], - }, q); - - q = 'UPDATE table SET column1 = "string ()", column2=5,column3=column4, column5 = CURDATE(), column6 = FUNCTION("string ()", column7) WHERE id = 5'; - deepEqual(m.sql2ast(q), { - 'UPDATE': [{ - table: 'table', - as: '', - }], - 'SET': [ - { expression: 'column1 = "string ()"'}, - { expression: 'column2=5'}, - { expression: 'column3=column4'}, - { expression: 'column5 = CURDATE()'}, - { expression: 'column6 = FUNCTION("string ()", column7)'}, - ], - 'WHERE': {left: 'id', operator: '=', right: '5'}, - }, q); - }); - - test('Infinite loop', function() { - m.sql2ast('SELECT * FROM foo where "a'); - ok(true); - }); - - - - - module('ast2sql'); - - test('SELECT query', function() { - expect(15); - var q; - - q = 'SELECT * FROM table'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table'; - deepEqual(m.ast2sql(m.sql2ast('select * from table')), q, q); - - q = 'SELECT * FROM table1, table2'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT t.column1, ot.column2 FROM table AS t LEFT JOIN othertable AS ot ON t.id = ot.id_table WHERE t.column3 = 5'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT t.column1, ot.column2 FROM table AS t RIGHT JOIN othertable AS ot ON t.id = ot.id_table WHERE t.column3 = 5'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table AS t LEFT JOIN othertable AS ot ON t.id = ot.id_table LEFT JOIN othertable2 AS ot2 ON t.id = ot2.id_table AND ot2.column INNER JOIN othertable3 AS ot3 ON t.id = ot3.id_table'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table WHERE (column1 = "something ()" AND table.column2 != column3) AND (column4 OR column5 IS NOT NULL)'; - deepEqual(m.ast2sql(m.sql2ast(q)), 'SELECT * FROM table WHERE column1 = "something ()" AND table.column2 != column3 AND (column4 OR column5 IS NOT NULL)', q); - - q = 'SELECT * FROM table ORDER BY a ASC, b DESC, c ASC'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table GROUP BY a, b'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table LIMIT 1'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table LIMIT 10,1'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'SELECT * FROM table LIMIT 1'; - deepEqual(m.ast2sql({'SELECT': [{name: '*'}], 'FROM': [{table: 'table', as: ''}], 'LIMIT': {'nb': '1'}}), q, q); - - q = 'SELECT * FROM table'; - deepEqual(m.ast2sql({'SELECT': [{name: '*'}], 'FROM': [{table: 'table', as: ''}], 'LIMIT': {'nb': ''}}), q, q); - - q = 'SELECT * FROM table LIMIT 1'; - deepEqual(m.ast2sql({'SELECT': [{name: '*'}], 'FROM': [{table: 'table', as: ''}], 'LIMIT': {'from': null, 'nb': '1'}}), q, q); - - q = 'SELECT * FROM table LIMIT 1'; - deepEqual(m.ast2sql({'SELECT': [{name: '*'}], 'FROM': [{table: 'table', as: ''}], 'LIMIT': {'from': '-1', 'nb': '1'}}), q, q); - }); - - test('INSERT query', function() { - expect(4); - var q; - - q = 'INSERT INTO table (col_A, col_B, col_C) VALUES (1, 2, 3)'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'INSERT INTO table VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'INSERT INTO table (col_A, col_B, col_C) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'INSERT INTO table VALUES (1, 2, 3)'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - }); - - test('DELETE query', function() { - expect(2); - var q; - - q = 'DELETE FROM table WHERE id = 5'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'DELETE FROM table1, table2 WHERE id = 5'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - }); - - test('UPDATE query', function() { - expect(3); - var q; - - q = 'UPDATE table SET column = 1'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'UPDATE table1, table2 SET column = 1'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - - q = 'UPDATE table SET column1 = "string ()", column2=5, column3=column4, column5 = CURDATE(), column6 = FUNCTION("string ()", column7) WHERE id = 5'; - deepEqual(m.ast2sql(m.sql2ast(q)), q, q); - }); - -}()); From 4f94050c3159af225572ccddc00367ea73f893a8 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 18:44:53 +0200 Subject: [PATCH 02/75] Add gulp.js and JSHint --- gulpfile.js | 11 +++++++++++ package.json | 4 ++++ simpleSqlParser.js | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 gulpfile.js diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..2169a62 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,11 @@ +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); + +gulp.task('lint', function() { + return gulp.src('./*.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')) + .pipe(jshint.reporter('fail')); +}); + +gulp.task('default', ['lint']); diff --git a/package.json b/package.json index 1e17796..e15f820 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,9 @@ "homepage": "https://github.com/dsferruzza/simpleSqlParser", "dependencies": { "parsimmon": "0.4.0" + }, + "devDependencies": { + "gulp": "3.6.2", + "gulp-jshint": "1.5.5" } } diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 2ac4dea..490d851 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -2,7 +2,7 @@ "use strict"; // Load Parsimmon - if (typeof Parsimmon != 'object') var Parsimmon = require('Parsimmon'); + var Parsimmon = (typeof Parsimmon != 'object') ? require('Parsimmon') : Parsimmon; From 2050a87cb9b641be83f23146bf47bb9dbda594eb Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:01:17 +0200 Subject: [PATCH 03/75] Fix use from browser --- simpleSqlParser.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 490d851..64c5238 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -1,11 +1,6 @@ -(function(exports) { +(function(exports, Parsimmon) { "use strict"; - // Load Parsimmon - var Parsimmon = (typeof Parsimmon != 'object') ? require('Parsimmon') : Parsimmon; - - - /******************************************************************************************** ALIASES ********************************************************************************************/ @@ -294,10 +289,10 @@ return p.parse(sql); }; -})(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports); +})(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports, typeof Parsimmon === 'object' ? Parsimmon : require('Parsimmon')); -var simpleSqlParser = require('./simpleSqlParser.js'); +/*var simpleSqlParser = require('./simpleSqlParser.js'); // Tests (will be rewritten/automatized/externalized) @@ -319,3 +314,4 @@ q.forEach(function(query) { console.log(); }); +*/ From f6d663b5af450b2807ecae393019d24019ee896b Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:02:05 +0200 Subject: [PATCH 04/75] Disable debug stuff --- simpleSqlParser.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 64c5238..f3d783a 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -148,7 +148,7 @@ expression: node.join(''), table: node[0], column: node[2], - TandC: true + //TandC: true }; }), colName.map(function(node) { @@ -156,7 +156,7 @@ expression: node, table: null, column: node, - col: true + //col: true }; }), mathExpression.map(function(node) { @@ -164,7 +164,7 @@ expression: node.trim(), table: null, column: null, - math: true + //math: true }; }), func.map(function(node) { @@ -172,7 +172,7 @@ expression: node, table: null, column: null, - func: true + //func: true }; }), str.map(function(node) { @@ -180,7 +180,7 @@ expression: node, table: null, column: null, - str: true + //str: true }; }) ); From 7133bb0df402dbe2328cb6cd7d8ab400852b404c Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:05:06 +0200 Subject: [PATCH 05/75] Add tests --- package.json | 3 ++- tests/tests.html | 15 +++++++++++++++ tests/tests.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/tests.html create mode 100644 tests/tests.js diff --git a/package.json b/package.json index e15f820..942d3a5 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "gulp": "3.6.2", - "gulp-jshint": "1.5.5" + "gulp-jshint": "1.5.5", + "qunit": "0.6.3" } } diff --git a/tests/tests.html b/tests/tests.html new file mode 100644 index 0000000..6aa5d72 --- /dev/null +++ b/tests/tests.html @@ -0,0 +1,15 @@ + + + + Tests - simpleSqlParser + + + + +
+ + + + + + diff --git a/tests/tests.js b/tests/tests.js new file mode 100644 index 0000000..eb73e72 --- /dev/null +++ b/tests/tests.js @@ -0,0 +1,31 @@ +(function () { + "use strict"; + + var m = simpleSqlParser; + + function ok(ast) { + return { + status: true, + value: ast + }; + } + + function testAst(query, ast) { + deepEqual(m.sql2ast(query), ok(ast), query); + } + + test('sql2ast', function() { + + testAst('SELECT * FROM table', { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null } + ], + from: [ + { table: 'table', alias: null } + ] + }); + + }); + +})(); From f79deb1692bccfbdf85af1efc57b84b876fd8558 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:22:45 +0200 Subject: [PATCH 06/75] Fix bug --- simpleSqlParser.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index f3d783a..80eb776 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -192,8 +192,7 @@ seq( optWhitespace, opt(regex(/AS\s/i)), - alt(colName, str), - optWhitespace + alt(colName, str) ).map(function(node) { var n = {}; n.alias = node[2]; From 065b263c01961074134a26c836e51d566213bc0d Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:22:49 +0200 Subject: [PATCH 07/75] Add more tests --- tests/tests.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/tests/tests.js b/tests/tests.js index eb73e72..50050a0 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -15,17 +15,95 @@ } test('sql2ast', function() { - + testAst('SELECT * FROM table', { type: 'select', select: [ - { expression: '*', column: '*', table: null, alias: null } + { expression: '*', column: '*', table: null, alias: null }, ], from: [ - { table: 'table', alias: null } + { table: 'table', alias: null }, ] }); + testAst('SELECT col1, `col2` FROM table', { + type: 'select', + select: [ + { expression: 'col1', column: 'col1', table: null, alias: null }, + { expression: '`col2`', column: '`col2`', table: null, alias: null }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + testAst('SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', { + type: 'select', + select: [ + { expression: 'table.col1', column: 'col1', table: 'table', alias: null }, + { expression: 'table.`col2`', column: '`col2`', table: 'table', alias: null }, + { expression: '`table`.col3', column: 'col3', table: '`table`', alias: null }, + { expression: '`table`.`col4`', column: '`col4`', table: '`table`', alias: null }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + testAst('SELECT * FROM table AS t', { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { table: 'table', alias: 't' }, + ] + }); + + testAst('SELECT "string", "\\"special\\" string" FROM table', { + type: 'select', + select: [ + { expression: '"string"', column: null, table: null, alias: null }, + { expression: '"\\"special\\" string"', column: null, table: null, alias: null }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + testAst('SELECT col1 AS alias, col2 AS "alias" FROM table', { + type: 'select', + select: [ + { expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' }, + { expression: 'col2 AS "alias"', column: 'col2', table: null, alias: '"alias"' }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + testAst('SELECT col1 alias, col2 "alias" FROM table', { + type: 'select', + select: [ + { expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' }, + { expression: 'col2 "alias"', column: 'col2', table: null, alias: '"alias"' }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + /*testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { + type: 'select', + select: [ + { expression: 'FUNC()', column: null, table: null, alias: null }, + { expression: 'OTHERFUN(col, FUNC(), "string")', column: null, table: null, alias: null }, + ], + from: [ + { table: 'table', alias: null }, + ] + });*/ + }); })(); From 4c9ccc2887297114384e5cd36231a08e00150603 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:37:35 +0200 Subject: [PATCH 08/75] Use gulp to run tests --- gulpfile.js | 8 +++++++- package.json | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 2169a62..5069209 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,5 +1,6 @@ var gulp = require('gulp'); var jshint = require('gulp-jshint'); +var qunit = require('gulp-qunit'); gulp.task('lint', function() { return gulp.src('./*.js') @@ -8,4 +9,9 @@ gulp.task('lint', function() { .pipe(jshint.reporter('fail')); }); -gulp.task('default', ['lint']); +gulp.task('test', function() { + return gulp.src('./tests/tests.html') + .pipe(qunit()); +}); + +gulp.task('default', ['lint', 'test']); diff --git a/package.json b/package.json index 942d3a5..abf9c46 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "devDependencies": { "gulp": "3.6.2", "gulp-jshint": "1.5.5", + "gulp-qunit": "0.3.3", "qunit": "0.6.3" } } From ac6f98aee7c40e988ad52eafd9380d28ab1bb734 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:50:51 +0200 Subject: [PATCH 09/75] Improve a regex --- simpleSqlParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 80eb776..9e94417 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -55,7 +55,7 @@ // The name of a column/table var colName = alt( - regex(/(?!(FROM|AS)\s)[a-zA-Z0-9_*]+/i), + regex(/(?!(FROM|AS)\s)[a-z*][a-z0-9_]*/i), regex(/`[^`\\]*(?:\\.[^`\\]*)*`/) ); From 0f5380441f66a8bbd990e878a7ddffe39606fa52 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Wed, 14 May 2014 19:59:43 +0200 Subject: [PATCH 10/75] Add more tests --- tests/tests.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/tests.js b/tests/tests.js index 50050a0..ccf0546 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -93,7 +93,17 @@ ] }); - /*testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { + /*testAst('SELECT 1 + 1 FROM table', { + type: 'select', + select: [ + { expression: '1 + 1', column: null, table: null, alias: null }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + + testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { type: 'select', select: [ { expression: 'FUNC()', column: null, table: null, alias: null }, From 3791192c9cde71c7b683bcab7ac888def52a2ba9 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 22:50:00 +0200 Subject: [PATCH 11/75] Update JSHint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index abf9c46..146b6c4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "devDependencies": { "gulp": "3.6.2", - "gulp-jshint": "1.5.5", + "gulp-jshint": "1.6.0", "gulp-qunit": "0.3.3", "qunit": "0.6.3" } From 2fb7aade8160683ba564c8d630cedbc0c9c0f57b Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 23:07:47 +0200 Subject: [PATCH 12/75] Fix expressions --- simpleSqlParser.js | 110 +++++++++++++++++++++++---------------------- tests/tests.js | 4 +- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 9e94417..ed1fd3a 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -126,68 +126,70 @@ EXPRESSION PARSERS ********************************************************************************************/ - // Mathematical expression - var mathExpression = seq( + // Expression + var expression = seq( alt( - tableAndColumn, - number, - func, - colName, - str + tableAndColumn.map(function(node) { + return { + expression: node.join(''), + table: node[0], + column: node[2] + }; + }), + colName.map(function(node) { + return { + expression: node, + table: null, + column: node + }; + }), + func.map(function(node) { + return { + expression: node, + table: null, + column: null + }; + }), + str.map(function(node) { + return { + expression: node, + table: null, + column: null + }; + }), + number.map(function(node) { + return { + expression: node, + table: null, + column: null + }; + }) ), - optWhitespace, - opt(operator, null), - optWhitespace, - opt(lazy(function() { return mathExpression; })) - ).map(mkString); - - // Column expresion - var colExpression = alt( - tableAndColumn.map(function(node) { + opt(seq( + optWhitespace, + operator, + opt(seq( + optWhitespace, + lazy(function() { return expression; }).map(function(node) { + return node.expression; + }) + ).map(mkString), null) + ).map(mkString), null) + ).map(function(node) { + if (node[1] !== null) { + node[0] = node[0].expression; return { expression: node.join(''), - table: node[0], - column: node[2], - //TandC: true - }; - }), - colName.map(function(node) { - return { - expression: node, - table: null, - column: node, - //col: true - }; - }), - mathExpression.map(function(node) { - return { - expression: node.trim(), - table: null, - column: null, - //math: true - }; - }), - func.map(function(node) { - return { - expression: node, table: null, - column: null, - //func: true + column: null }; - }), - str.map(function(node) { - return { - expression: node, - table: null, - column: null, - //str: true - }; - }) - ); + } + else return node[0]; + }); // Expression following a SELECT statement var colListExpression = seq( - colExpression, + expression, opt( // Alias seq( optWhitespace, @@ -212,7 +214,7 @@ var argListExpression = alt( str, func, - colExpression.map(function(node) { + expression.map(function(node) { return node.expression; }) ); diff --git a/tests/tests.js b/tests/tests.js index ccf0546..c891b84 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -93,7 +93,7 @@ ] }); - /*testAst('SELECT 1 + 1 FROM table', { + testAst('SELECT 1 + 1 FROM table', { type: 'select', select: [ { expression: '1 + 1', column: null, table: null, alias: null }, @@ -103,7 +103,7 @@ ] }); - testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { + /*testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { type: 'select', select: [ { expression: 'FUNC()', column: null, table: null, alias: null }, From a4c09539d20a042ee6d281a0a58410b962024bcb Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 23:16:39 +0200 Subject: [PATCH 13/75] Fix functions in expressions --- simpleSqlParser.js | 18 +++++++----------- tests/tests.js | 9 +++++---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index ed1fd3a..312ca2c 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -136,18 +136,18 @@ column: node[2] }; }), - colName.map(function(node) { + func.map(function(node) { return { expression: node, table: null, - column: node + column: null }; }), - func.map(function(node) { + colName.map(function(node) { return { expression: node, table: null, - column: null + column: node }; }), str.map(function(node) { @@ -211,13 +211,9 @@ }); // Expression inside a function - var argListExpression = alt( - str, - func, - expression.map(function(node) { - return node.expression; - }) - ); + var argListExpression = expression.map(function(node) { + return node.expression; + }); // Expression following a FROM statement var tableListExpression = seq( diff --git a/tests/tests.js b/tests/tests.js index c891b84..b237c55 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -93,26 +93,27 @@ ] }); - testAst('SELECT 1 + 1 FROM table', { + testAst('SELECT 1 + 1, col1*0.7 AS test FROM table', { type: 'select', select: [ { expression: '1 + 1', column: null, table: null, alias: null }, + { expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' }, ], from: [ { table: 'table', alias: null }, ] }); - /*testAst('SELECT FUNC(), OTHERFUN(col, FUNC(), "string") FROM table', { + testAst('SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', { type: 'select', select: [ { expression: 'FUNC()', column: null, table: null, alias: null }, - { expression: 'OTHERFUN(col, FUNC(), "string")', column: null, table: null, alias: null }, + { expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null }, ], from: [ { table: 'table', alias: null }, ] - });*/ + }); }); From 62380fcad4b8aa10526d17a7467cada9ac167564 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 23:18:44 +0200 Subject: [PATCH 14/75] Cleanup --- simpleSqlParser.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 312ca2c..f201a9a 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -287,28 +287,3 @@ }; })(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports, typeof Parsimmon === 'object' ? Parsimmon : require('Parsimmon')); - - -/*var simpleSqlParser = require('./simpleSqlParser.js'); - - -// Tests (will be rewritten/automatized/externalized) -var q = [ - 'SELECT colname1 AS col1, colname2 "col2", "k\'ge\\"rg",`gfrg` , y, \'other string\' FROM table', - 'SELECT FROM table1, table2 AS t2', - 'SELECT * FROM table', - 'SELECT a, NOW(), TRUC("test\\" ", table.col , MACHIN(NOW())), b FROM table', - 'SELECT table.col FROM table', - 'SELECT (1+2) / MACHIN(6.6, 2, table.`col`) AS truc FROM' -]; -q.forEach(function(query) { - var ast = simpleSqlParser.sql2ast(query); - if (ast.status) { - console.log('--> ' + query); - console.log(ast.value); - } - else console.log(ast); - console.log(); -}); - -*/ From 9ec5169b62ce8d9ca5ab0122dbb337b4e063ea1b Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 23:36:09 +0200 Subject: [PATCH 15/75] Remove quotes from parsed output --- simpleSqlParser.js | 15 ++++++++++----- tests/tests.js | 15 ++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index f201a9a..2ba205d 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -47,6 +47,11 @@ ).map(mergeOptionnalList); } + // Remove first and last character of a string + function removeQuotes(string) { + return string.replace(/^([`'"])(.*)\1$/, '$2'); + } + /******************************************************************************************** @@ -132,8 +137,8 @@ tableAndColumn.map(function(node) { return { expression: node.join(''), - table: node[0], - column: node[2] + table: removeQuotes(node[0]), + column: removeQuotes(node[2]) }; }), func.map(function(node) { @@ -147,7 +152,7 @@ return { expression: node, table: null, - column: node + column: removeQuotes(node) }; }), str.map(function(node) { @@ -197,7 +202,7 @@ alt(colName, str) ).map(function(node) { var n = {}; - n.alias = node[2]; + n.alias = removeQuotes(node[2]); n.expression = node.join(''); return n; }), @@ -229,7 +234,7 @@ alt(colName, str), optWhitespace ).map(function(node) { - return node[3]; + return removeQuotes(node[3]); }), null ) diff --git a/tests/tests.js b/tests/tests.js index b237c55..ed78fc5 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -30,7 +30,7 @@ type: 'select', select: [ { expression: 'col1', column: 'col1', table: null, alias: null }, - { expression: '`col2`', column: '`col2`', table: null, alias: null }, + { expression: '`col2`', column: 'col2', table: null, alias: null }, ], from: [ { table: 'table', alias: null }, @@ -41,22 +41,23 @@ type: 'select', select: [ { expression: 'table.col1', column: 'col1', table: 'table', alias: null }, - { expression: 'table.`col2`', column: '`col2`', table: 'table', alias: null }, - { expression: '`table`.col3', column: 'col3', table: '`table`', alias: null }, - { expression: '`table`.`col4`', column: '`col4`', table: '`table`', alias: null }, + { expression: 'table.`col2`', column: 'col2', table: 'table', alias: null }, + { expression: '`table`.col3', column: 'col3', table: 'table', alias: null }, + { expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null }, ], from: [ { table: 'table', alias: null }, ] }); - testAst('SELECT * FROM table AS t', { + testAst('SELECT * FROM table AS t, table2 AS "t2"', { type: 'select', select: [ { expression: '*', column: '*', table: null, alias: null }, ], from: [ { table: 'table', alias: 't' }, + { table: 'table2', alias: 't2' }, ] }); @@ -75,7 +76,7 @@ type: 'select', select: [ { expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' }, - { expression: 'col2 AS "alias"', column: 'col2', table: null, alias: '"alias"' }, + { expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' }, ], from: [ { table: 'table', alias: null }, @@ -86,7 +87,7 @@ type: 'select', select: [ { expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' }, - { expression: 'col2 "alias"', column: 'col2', table: null, alias: '"alias"' }, + { expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' }, ], from: [ { table: 'table', alias: null }, From fabd6f5eb61a3614c0ca3bfa91d31d2b04c6b3f0 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Thu, 15 May 2014 23:42:16 +0200 Subject: [PATCH 16/75] Improve column name detection --- simpleSqlParser.js | 2 +- tests/tests.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/simpleSqlParser.js b/simpleSqlParser.js index 2ba205d..cfe007f 100644 --- a/simpleSqlParser.js +++ b/simpleSqlParser.js @@ -60,7 +60,7 @@ // The name of a column/table var colName = alt( - regex(/(?!(FROM|AS)\s)[a-z*][a-z0-9_]*/i), + regex(/(?!FROM\s)[a-z*][a-z0-9_]*/i), regex(/`[^`\\]*(?:\\.[^`\\]*)*`/) ); diff --git a/tests/tests.js b/tests/tests.js index ed78fc5..90afc12 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -37,6 +37,17 @@ ] }); + testAst('SELECT fromage "from", asymetric AS as FROM table', { + type: 'select', + select: [ + { expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' }, + { expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' }, + ], + from: [ + { table: 'table', alias: null }, + ] + }); + testAst('SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', { type: 'select', select: [ From d081db8567eadd13c5a0d5fb11d7f1ce83e017ba Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Fri, 16 May 2014 09:24:28 +0200 Subject: [PATCH 17/75] Update JSHint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 146b6c4..5eba7bd 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "devDependencies": { "gulp": "3.6.2", - "gulp-jshint": "1.6.0", + "gulp-jshint": "1.6.1", "gulp-qunit": "0.3.3", "qunit": "0.6.3" } From b0dd923d1a97ff73ac7b8632b91dbd82e5ad34f7 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Fri, 16 May 2014 09:38:57 +0200 Subject: [PATCH 18/75] Add an interactive demo --- demo.html | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 demo.html diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..37d3375 --- /dev/null +++ b/demo.html @@ -0,0 +1,28 @@ + + + + + Demo - simpleSqlParser + + +

+
+ +

+

+ +

+

AST

+

+
+	
+	
+	
+
+

From a43186860d73b9bbbf1f63a4cf2af52602299bde Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 16 May 2014 09:43:45 +0200
Subject: [PATCH 19/75] Cleanup tests

---
 tests/tests.js | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/tests/tests.js b/tests/tests.js
index 90afc12..e23553f 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -16,6 +16,7 @@
 
 	test('sql2ast', function() {
 
+		// Simple select
 		testAst('SELECT * FROM table', {
 			type: 'select',
 			select: [
@@ -26,6 +27,7 @@
 			]
 		});
 
+		// Column quotes
 		testAst('SELECT col1, `col2` FROM table', {
 			type: 'select',
 			select: [
@@ -37,6 +39,7 @@
 			]
 		});
 
+		// Special words
 		testAst('SELECT fromage "from", asymetric AS as FROM table', {
 			type: 'select',
 			select: [
@@ -48,6 +51,7 @@
 			]
 		});
 
+		// "table.column" notation
 		testAst('SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', {
 			type: 'select',
 			select: [
@@ -61,17 +65,7 @@
 			]
 		});
 
-		testAst('SELECT * FROM table AS t, table2 AS "t2"', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: 't' },
-				{ table: 'table2', alias: 't2' },
-			]
-		});
-
+		// Strings
 		testAst('SELECT "string", "\\"special\\" string" FROM table', {
 			type: 'select',
 			select: [
@@ -83,6 +77,7 @@
 			]
 		});
 
+		// Column alias #1
 		testAst('SELECT col1 AS alias, col2 AS "alias" FROM table', {
 			type: 'select',
 			select: [
@@ -94,6 +89,7 @@
 			]
 		});
 
+		// Column alias #2
 		testAst('SELECT col1 alias, col2 "alias" FROM table', {
 			type: 'select',
 			select: [
@@ -105,6 +101,7 @@
 			]
 		});
 
+		// Mathematical expressions
 		testAst('SELECT 1 + 1, col1*0.7 AS test FROM table', {
 			type: 'select',
 			select: [
@@ -116,6 +113,7 @@
 			]
 		});
 
+		// Functions
 		testAst('SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', {
 			type: 'select',
 			select: [
@@ -127,6 +125,18 @@
 			]
 		});
 
+		// Table alias
+		testAst('SELECT * FROM table AS t, table2 AS "t2"', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: 't' },
+				{ table: 'table2', alias: 't2' },
+			]
+		});
+
 	});
 
 })();

From a4518c96ca3cad99dc90809edfc25ac79cb2c2c7 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 16 May 2014 10:07:09 +0200
Subject: [PATCH 20/75] Add basic WHERE support

---
 simpleSqlParser.js | 35 +++++++++++++++++-----------
 tests/tests.js     | 58 ++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index cfe007f..18a9c96 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!FROM\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -93,32 +93,32 @@
 		string('-'),
 		string('*'),
 		string('/'),
+		string('&&'),
 		string('&'),
 		string('~'),
+		string('||'),
 		string('|'),
 		string('^'),
 		regex(/XOR/i),
 		string('<=>'),
 		string('='),
 		string('!='),
-		string('>'),
 		string('>='),
-		string('<'),
+		string('>>'),
+		string('>'),
 		string('<='),
+		string('<<'),
+		string('<'),
 		regex(/IS NULL/i),
 		regex(/IS NOT/i),
 		regex(/IS NOT NULL/i),
 		regex(/IS/i),
-		string('>>'),
-		string('<<'),
 		regex(/LIKE/i),
 		regex(/NOT LIKE/i),
 		string('%'),
 		regex(/MOD/i),
 		regex(/NOT/i),
-		string('||'),
 		regex(/OR/i),
-		string('&&'),
 		regex(/AND/i)
 	);
 
@@ -229,12 +229,10 @@
 		opt(	// Alias
 			seq(
 				optWhitespace,
-				opt(regex(/AS/i)),
-				optWhitespace,
-				alt(colName, str),
-				optWhitespace
+				opt(regex(/AS\s/i)),
+				alt(colName, str)
 			).map(function(node) {
-				return removeQuotes(node[3]);
+				return removeQuotes(node[2]);
 			}),
 			null
 		)
@@ -245,6 +243,13 @@
 		return n;
 	});
 
+	// Expression following a WHERE statement
+	var whereExpression = expression.map(function(node) {
+		return {
+			expression: node.expression
+		};
+	});
+
 
 
 	/********************************************************************************************
@@ -272,12 +277,14 @@
 	// SELECT parser
 	var p = seq(
 		regex(/SELECT/i).skip(optWhitespace).then(opt(colList)),
-		regex(/FROM/i).skip(optWhitespace).then(opt(tableList))
+		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
+		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null)
 	).map(function(node) {
 		return {
 			type: 'select',
 			select: node[0],
-			from: node[1]
+			from: node[1],
+			where: node[2]
 		};
 	});
 
diff --git a/tests/tests.js b/tests/tests.js
index e23553f..caeceab 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -24,7 +24,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Column quotes
@@ -36,7 +37,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Special words
@@ -48,7 +50,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// "table.column" notation
@@ -62,7 +65,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Strings
@@ -74,7 +78,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Column alias #1
@@ -86,7 +91,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Column alias #2
@@ -98,7 +104,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Mathematical expressions
@@ -110,7 +117,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Functions
@@ -122,7 +130,8 @@
 			],
 			from: [
 				{ table: 'table', alias: null },
-			]
+			],
+			where: null,
 		});
 
 		// Table alias
@@ -134,7 +143,36 @@
 			from: [
 				{ table: 'table', alias: 't' },
 				{ table: 'table2', alias: 't2' },
-			]
+			],
+			where: null,
+		});
+
+		// Where #1
+		testAst('SELECT * FROM table WHERE this >= that AND col IS NOT NULL', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: {
+				expression: "this >= that AND col IS NOT NULL",
+			},
+		});
+
+		// Where #2
+		testAst('SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: {
+				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
+			},
 		});
 
 	});

From 6ad35e4d0371db85c5dd3c33195eb81f46e41374 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 16 May 2014 10:30:04 +0200
Subject: [PATCH 21/75] Add basic ORDER BY support

---
 simpleSqlParser.js | 27 ++++++++++++++++++++++++---
 tests/tests.js     | 29 +++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index 18a9c96..bd50f42 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|ORDER BY)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -250,6 +250,22 @@
 		};
 	});
 
+	// Expression following an ORDER BY statement
+	var orderListExpression = seq(
+		expression,
+		opt(seq(
+			optWhitespace,
+			regex(/ASC|DESC/i)
+		), null)
+	).map(function(node) {
+		return {
+			expression: node[0].expression + ((node[1] !== null) ? node[1].join('') : ''),
+			order: (node[1] !== null) ? node[1][1] : 'ASC',
+			table: node[0].table,
+			column: node[0].column,
+		};
+	});
+
 
 
 	/********************************************************************************************
@@ -268,6 +284,9 @@
 	// List of table following a FROM statement
 	var tableList = optionnalList(tableListExpression);
 
+	// List of table following an ORDER BY statement
+	var orderList = optionnalList(orderListExpression);
+
 
 
 	/********************************************************************************************
@@ -278,13 +297,15 @@
 	var p = seq(
 		regex(/SELECT/i).skip(optWhitespace).then(opt(colList)),
 		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
-		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null)
+		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null),
+		opt(regex(/ORDER BY/i).skip(optWhitespace).then(opt(orderList)))
 	).map(function(node) {
 		return {
 			type: 'select',
 			select: node[0],
 			from: node[1],
-			where: node[2]
+			where: node[2],
+			order: node[3],
 		};
 	});
 
diff --git a/tests/tests.js b/tests/tests.js
index caeceab..fb34417 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -26,6 +26,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Column quotes
@@ -39,6 +40,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Special words
@@ -52,6 +54,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// "table.column" notation
@@ -67,6 +70,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Strings
@@ -80,6 +84,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Column alias #1
@@ -93,6 +98,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Column alias #2
@@ -106,6 +112,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Mathematical expressions
@@ -119,6 +126,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Functions
@@ -132,6 +140,7 @@
 				{ table: 'table', alias: null },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Table alias
@@ -145,6 +154,7 @@
 				{ table: 'table2', alias: 't2' },
 			],
 			where: null,
+			order: [],
 		});
 
 		// Where #1
@@ -159,6 +169,7 @@
 			where: {
 				expression: "this >= that AND col IS NOT NULL",
 			},
+			order: [],
 		});
 
 		// Where #2
@@ -173,6 +184,24 @@
 			where: {
 				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
 			},
+			order: [],
+		});
+
+		// Order by
+		testAst('SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: null,
+			order: [
+				{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
+				{ expression: "col2 DESC", table: null, column: "col2", order: "DESC" },
+				{ expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" },
+			]
 		});
 
 	});

From 27e20cc4092dc95d2fa04d35b25f4c98f888fbfb Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 16 May 2014 10:39:18 +0200
Subject: [PATCH 22/75] Add basic LIMIT support

---
 simpleSqlParser.js | 26 ++++++++++++++++++++++++--
 tests/tests.js     | 43 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 66 insertions(+), 3 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index bd50f42..40e9632 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE|ORDER BY)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -266,6 +266,26 @@
 		};
 	});
 
+	// Expression following a LIMIT statement
+	var limitExpression = seq(
+		number,
+		opt(seq(
+			optWhitespace,
+			string(','),
+			optWhitespace,
+			number
+		), null)
+	).map(function(node) {
+		if (node[1] === null) return {
+			from: null,
+			nb: parseInt(node[0], 10),
+		};
+		else return {
+			from: parseInt(node[0], 10),
+			nb: parseInt(node[1][3], 10),
+		};
+	});
+
 
 
 	/********************************************************************************************
@@ -298,7 +318,8 @@
 		regex(/SELECT/i).skip(optWhitespace).then(opt(colList)),
 		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
 		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null),
-		opt(regex(/ORDER BY/i).skip(optWhitespace).then(opt(orderList)))
+		opt(regex(/ORDER BY/i).skip(optWhitespace).then(opt(orderList))),
+		opt(regex(/LIMIT/i).skip(optWhitespace).then(opt(limitExpression)), null)
 	).map(function(node) {
 		return {
 			type: 'select',
@@ -306,6 +327,7 @@
 			from: node[1],
 			where: node[2],
 			order: node[3],
+			limit: node[4],
 		};
 	});
 
diff --git a/tests/tests.js b/tests/tests.js
index fb34417..b81067a 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -27,6 +27,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Column quotes
@@ -41,6 +42,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Special words
@@ -55,6 +57,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// "table.column" notation
@@ -71,6 +74,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Strings
@@ -85,6 +89,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Column alias #1
@@ -99,6 +104,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Column alias #2
@@ -113,6 +119,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Mathematical expressions
@@ -127,6 +134,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Functions
@@ -141,6 +149,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Table alias
@@ -155,6 +164,7 @@
 			],
 			where: null,
 			order: [],
+			limit: null,
 		});
 
 		// Where #1
@@ -170,6 +180,7 @@
 				expression: "this >= that AND col IS NOT NULL",
 			},
 			order: [],
+			limit: null,
 		});
 
 		// Where #2
@@ -185,6 +196,7 @@
 				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
 			},
 			order: [],
+			limit: null,
 		});
 
 		// Order by
@@ -201,7 +213,36 @@
 				{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
 				{ expression: "col2 DESC", table: null, column: "col2", order: "DESC" },
 				{ expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" },
-			]
+			],
+			limit: null,
+		});
+
+		// Limit #1
+		testAst('SELECT * FROM table LIMIT 5', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: null,
+			order: [],
+			limit: { from: null, nb: 5 },
+		});
+
+		// Limit #2
+		testAst('SELECT * FROM table LIMIT 1, 2', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: null,
+			order: [],
+			limit: { from: 1, nb: 2 },
 		});
 
 	});

From b9d5df069b181fa45f9ac5568b318518f0233927 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 16 May 2014 11:07:58 +0200
Subject: [PATCH 23/75] Check AST's structure

---
 simpleSqlParser.js |  1 +
 tests/tests.js     | 52 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index 40e9632..b8c848d 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -325,6 +325,7 @@
 			type: 'select',
 			select: node[0],
 			from: node[1],
+			join: [],
 			where: node[2],
 			order: node[3],
 			limit: node[4],
diff --git a/tests/tests.js b/tests/tests.js
index b81067a..0468102 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -14,7 +14,42 @@
 		deepEqual(m.sql2ast(query), ok(ast), query);
 	}
 
-	test('sql2ast', function() {
+	function isArray(variable, message) {
+		strictEqual(typeof variable, "object", message);
+		strictEqual(Array.isArray(variable), true, message);
+	}
+
+	function isObject(variable, message) {
+		strictEqual(typeof variable, "object", message);
+		strictEqual(Array.isArray(variable), false, message);
+	}
+
+	test('sql2ast - API', function() {
+
+		var q = [
+			'SELECT * FROM table',
+		];
+		var ast = q.map(m.sql2ast);
+
+		var types = ['select'];
+
+		ast.forEach(function(a) {
+			strictEqual(a.status, true, "Parser must parse valid SQL");
+			notStrictEqual(types.indexOf(a.value.type), -1, "AST must contain a valid type");
+
+			if (a.value.type === types[0]) {
+				isArray(a.value.select, "(SELECT) AST must contain a 'select' array");
+				isArray(a.value.from, "(SELECT) AST must contain a 'from' array");
+				isArray(a.value.join, "(SELECT) AST must contain a 'join' array");
+				isObject(a.value.where, "(SELECT) AST must contain a 'where' object");
+				isArray(a.value.order, "(SELECT) AST must contain a 'order' array");
+				isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object");
+			}
+		});
+
+	});
+
+	test('sql2ast - select', function() {
 
 		// Simple select
 		testAst('SELECT * FROM table', {
@@ -25,6 +60,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -40,6 +76,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -55,6 +92,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -72,6 +110,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -87,6 +126,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -102,6 +142,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -117,6 +158,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -132,6 +174,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -147,6 +190,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -162,6 +206,7 @@
 				{ table: 'table', alias: 't' },
 				{ table: 'table2', alias: 't2' },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: null,
@@ -176,6 +221,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: {
 				expression: "this >= that AND col IS NOT NULL",
 			},
@@ -192,6 +238,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: {
 				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
 			},
@@ -208,6 +255,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [
 				{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
@@ -226,6 +274,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: { from: null, nb: 5 },
@@ -240,6 +289,7 @@
 			from: [
 				{ table: 'table', alias: null },
 			],
+			join: [],
 			where: null,
 			order: [],
 			limit: { from: 1, nb: 2 },

From 09dd03b4019c57a613cedf32784f13671c37ec18 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 19 May 2014 11:48:47 +0200
Subject: [PATCH 24/75] Improve tests display

---
 tests/tests.js | 49 +++++++++++++++++--------------------------------
 1 file changed, 17 insertions(+), 32 deletions(-)

diff --git a/tests/tests.js b/tests/tests.js
index 0468102..0e92cc0 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -10,8 +10,8 @@
 		};
 	}
 
-	function testAst(query, ast) {
-		deepEqual(m.sql2ast(query), ok(ast), query);
+	function testAst(comment, query, ast) {
+		deepEqual(m.sql2ast(query), ok(ast), comment + ': ' + query);
 	}
 
 	function isArray(variable, message) {
@@ -51,8 +51,7 @@
 
 	test('sql2ast - select', function() {
 
-		// Simple select
-		testAst('SELECT * FROM table', {
+		testAst('Simple select', 'SELECT * FROM table', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -66,8 +65,7 @@
 			limit: null,
 		});
 
-		// Column quotes
-		testAst('SELECT col1, `col2` FROM table', {
+		testAst('Column quotes', 'SELECT col1, `col2` FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'col1', column: 'col1', table: null, alias: null },
@@ -82,8 +80,7 @@
 			limit: null,
 		});
 
-		// Special words
-		testAst('SELECT fromage "from", asymetric AS as FROM table', {
+		testAst('Special words', 'SELECT fromage "from", asymetric AS as FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' },
@@ -98,8 +95,7 @@
 			limit: null,
 		});
 
-		// "table.column" notation
-		testAst('SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', {
+		testAst('"table.column" notation', 'SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'table.col1', column: 'col1', table: 'table', alias: null },
@@ -116,8 +112,7 @@
 			limit: null,
 		});
 
-		// Strings
-		testAst('SELECT "string", "\\"special\\" string" FROM table', {
+		testAst('Strings', 'SELECT "string", "\\"special\\" string" FROM table', {
 			type: 'select',
 			select: [
 				{ expression: '"string"', column: null, table: null, alias: null },
@@ -132,8 +127,7 @@
 			limit: null,
 		});
 
-		// Column alias #1
-		testAst('SELECT col1 AS alias, col2 AS "alias" FROM table', {
+		testAst('Column alias #1', 'SELECT col1 AS alias, col2 AS "alias" FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' },
@@ -148,8 +142,7 @@
 			limit: null,
 		});
 
-		// Column alias #2
-		testAst('SELECT col1 alias, col2 "alias" FROM table', {
+		testAst('Column alias #2', 'SELECT col1 alias, col2 "alias" FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' },
@@ -164,8 +157,7 @@
 			limit: null,
 		});
 
-		// Mathematical expressions
-		testAst('SELECT 1 + 1, col1*0.7 AS test FROM table', {
+		testAst('Mathematical expressions', 'SELECT 1 + 1, col1*0.7 AS test FROM table', {
 			type: 'select',
 			select: [
 				{ expression: '1 + 1', column: null, table: null, alias: null },
@@ -180,8 +172,7 @@
 			limit: null,
 		});
 
-		// Functions
-		testAst('SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', {
+		testAst('Functions', 'SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', {
 			type: 'select',
 			select: [
 				{ expression: 'FUNC()', column: null, table: null, alias: null },
@@ -196,8 +187,7 @@
 			limit: null,
 		});
 
-		// Table alias
-		testAst('SELECT * FROM table AS t, table2 AS "t2"', {
+		testAst('Table alias', 'SELECT * FROM table AS t, table2 AS "t2"', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -212,8 +202,7 @@
 			limit: null,
 		});
 
-		// Where #1
-		testAst('SELECT * FROM table WHERE this >= that AND col IS NOT NULL', {
+		testAst('Where #1', 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -229,8 +218,7 @@
 			limit: null,
 		});
 
-		// Where #2
-		testAst('SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
+		testAst('Where #2', 'SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -246,8 +234,7 @@
 			limit: null,
 		});
 
-		// Order by
-		testAst('SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', {
+		testAst('Order by', 'SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -265,8 +252,7 @@
 			limit: null,
 		});
 
-		// Limit #1
-		testAst('SELECT * FROM table LIMIT 5', {
+		testAst('Limit #1', 'SELECT * FROM table LIMIT 5', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },
@@ -280,8 +266,7 @@
 			limit: { from: null, nb: 5 },
 		});
 
-		// Limit #2
-		testAst('SELECT * FROM table LIMIT 1, 2', {
+		testAst('Limit #2', 'SELECT * FROM table LIMIT 1, 2', {
 			type: 'select',
 			select: [
 				{ expression: '*', column: '*', table: null, alias: null },

From 24af0e17c3aadd1ea3005ff9b49bacf1e29d80ea Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 19 May 2014 12:03:29 +0200
Subject: [PATCH 25/75] Add DELETE support

---
 simpleSqlParser.js | 17 +++++++++++++++-
 tests/tests.js     | 48 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index b8c848d..fdf08ce 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -314,7 +314,7 @@
 	********************************************************************************************/
 
 	// SELECT parser
-	var p = seq(
+	var selectParser = seq(
 		regex(/SELECT/i).skip(optWhitespace).then(opt(colList)),
 		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
 		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null),
@@ -332,6 +332,21 @@
 		};
 	});
 
+	// DELETE parser
+	var deleteParser = seq(
+		regex(/DELETE FROM/i).skip(optWhitespace).then(opt(tableList)),
+		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null)
+	).map(function(node) {
+		return {
+			type: 'delete',
+			from: node[0],
+			where: node[1],
+		};
+	});
+
+	// Main parser
+	var p = alt(selectParser, deleteParser);
+
 
 
 	/********************************************************************************************
diff --git a/tests/tests.js b/tests/tests.js
index 0e92cc0..101aa96 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -28,10 +28,11 @@
 
 		var q = [
 			'SELECT * FROM table',
+			'DELETE FROM table'
 		];
 		var ast = q.map(m.sql2ast);
 
-		var types = ['select'];
+		var types = ['select', 'delete'];
 
 		ast.forEach(function(a) {
 			strictEqual(a.status, true, "Parser must parse valid SQL");
@@ -45,6 +46,10 @@
 				isArray(a.value.order, "(SELECT) AST must contain a 'order' array");
 				isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object");
 			}
+			else if (a.value.type === types[0]) {
+				isArray(a.value.from, "(DELETE) AST must contain a 'from' array");
+				isObject(a.value.where, "(DELETE) AST must contain a 'where' object");
+			}
 		});
 
 	});
@@ -282,4 +287,45 @@
 
 	});
 
+	test('sql2ast - delete', function() {
+
+		testAst('Simple delete', 'DELETE FROM table', {
+			type: 'delete',
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: null,
+		});
+
+		testAst('Several tables with aliases', 'DELETE FROM table1 AS t1, table2 "t2"', {
+			type: 'delete',
+			from: [
+				{ table: 'table1', alias: 't1' },
+				{ table: 'table2', alias: 't2' },
+			],
+			where: null,
+		});
+
+		testAst('Where #1', 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', {
+			type: 'delete',
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: {
+				expression: "this >= that AND col IS NOT NULL",
+			},
+		});
+
+		testAst('Where #2', 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
+			type: 'delete',
+			from: [
+				{ table: 'table', alias: null },
+			],
+			where: {
+				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
+			},
+		});
+
+	});
+
 })();

From 1bf5fe946f96677cf4c6a20a0c9d1898e90d2069 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 19 May 2014 12:49:49 +0200
Subject: [PATCH 26/75] Fix typo

---
 tests/tests.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/tests.js b/tests/tests.js
index 101aa96..ca237ab 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -46,7 +46,7 @@
 				isArray(a.value.order, "(SELECT) AST must contain a 'order' array");
 				isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object");
 			}
-			else if (a.value.type === types[0]) {
+			else if (a.value.type === types[1]) {
 				isArray(a.value.from, "(DELETE) AST must contain a 'from' array");
 				isObject(a.value.where, "(DELETE) AST must contain a 'where' object");
 			}

From 3689db1c6a530f54e64d478d50d296436770e21c Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 19 May 2014 14:50:19 +0200
Subject: [PATCH 27/75] Add JOIN support

---
 simpleSqlParser.js | 40 +++++++++++++++++++++++++++++++++++-----
 tests/tests.js     | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 5 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index fdf08ce..bc7506d 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -243,6 +243,32 @@
 		return n;
 	});
 
+	// JOIN expression (including JOIN statements)
+	var joinExpression = seq(
+		opt(seq(
+			regex(/INNER|LEFT|RIGHT/i),
+			whitespace
+		).map(function(node) {
+			return node[0].toLowerCase();
+		}), null),
+		regex(/JOIN/i),
+		optWhitespace,
+		tableListExpression,
+		optWhitespace,
+		regex(/ON/i),
+		optWhitespace,
+		expression
+	).map(function(node) {
+		var n = {};
+		n.type = node[0] || 'inner';
+		n.table = node[3].table;
+		n.alias = node[3].alias;
+		n.condition = {
+			expression: node[7].expression,
+		};
+		return n;
+	});
+
 	// Expression following a WHERE statement
 	var whereExpression = expression.map(function(node) {
 		return {
@@ -307,6 +333,9 @@
 	// List of table following an ORDER BY statement
 	var orderList = optionnalList(orderListExpression);
 
+	// List of joins (including JOIN statements)
+	var joinList = optWhitespace.then(joinExpression).skip(optWhitespace).many();
+
 
 
 	/********************************************************************************************
@@ -317,6 +346,7 @@
 	var selectParser = seq(
 		regex(/SELECT/i).skip(optWhitespace).then(opt(colList)),
 		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
+		opt(joinList),
 		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null),
 		opt(regex(/ORDER BY/i).skip(optWhitespace).then(opt(orderList))),
 		opt(regex(/LIMIT/i).skip(optWhitespace).then(opt(limitExpression)), null)
@@ -325,10 +355,10 @@
 			type: 'select',
 			select: node[0],
 			from: node[1],
-			join: [],
-			where: node[2],
-			order: node[3],
-			limit: node[4],
+			join: node[2],
+			where: node[3],
+			order: node[4],
+			limit: node[5],
 		};
 	});
 
diff --git a/tests/tests.js b/tests/tests.js
index ca237ab..d3496a9 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -207,6 +207,40 @@
 			limit: null,
 		});
 
+		testAst('Simple inner join', 'SELECT * FROM table INNER JOIN table2 ON table2.id = id_table2', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			join: [
+				{ type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } },
+			],
+			where: null,
+			order: [],
+			limit: null,
+		});
+
+		testAst('Several joins', 'SELECT * FROM table JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)', {
+			type: 'select',
+			select: [
+				{ expression: '*', column: '*', table: null, alias: null },
+			],
+			from: [
+				{ table: 'table', alias: null },
+			],
+			join: [
+				{ type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } },
+				{ type: 'left', table: 't2', alias: null, condition: { expression: 't2.id = t1.id_t2' } },
+				{ type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } },
+			],
+			where: null,
+			order: [],
+			limit: null,
+		});
+
 		testAst('Where #1', 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL', {
 			type: 'select',
 			select: [

From d9b198f70629bdc8e57a51b974f5cd543aac309c Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Wed, 21 May 2014 10:46:49 +0200
Subject: [PATCH 28/75] Add INSERT support

---
 simpleSqlParser.js | 65 ++++++++++++++++++++++++++++++++++++++++++++--
 tests/tests.js     | 53 +++++++++++++++++++++++++++++++++++--
 2 files changed, 114 insertions(+), 4 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index bc7506d..2dddec0 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON|VALUES)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -312,6 +312,27 @@
 		};
 	});
 
+	// Expression designating a column before VALUES in INSERT query
+	var insertColListExpression = alt(
+		tableAndColumn.map(function(node) {
+			return {
+				expression: node.join(''),
+				column: removeQuotes(node[2])
+			};
+		}),
+		colName.map(function(node) {
+			return {
+				expression: node,
+				column: removeQuotes(node)
+			};
+		})
+	);
+
+	// Expression following a VALUES statement
+	var valueExpression = expression.map(function(node) {
+		return node.expression;
+	});
+
 
 
 	/********************************************************************************************
@@ -336,6 +357,12 @@
 	// List of joins (including JOIN statements)
 	var joinList = optWhitespace.then(joinExpression).skip(optWhitespace).many();
 
+	// List of columns before VALUES in INSERT query
+	var insertColList = optionnalList(insertColListExpression);
+
+	// Lisf of values following a VALUES statement
+	var valuesList = optionnalList(valueExpression);
+
 
 
 	/********************************************************************************************
@@ -362,6 +389,40 @@
 		};
 	});
 
+	// INSERT parser
+	var insertParser = seq(
+		regex(/INSERT INTO/i).skip(optWhitespace).then(tableListExpression),
+		optWhitespace,
+		opt(
+			seq(
+				string('('),
+				insertColList,
+				string(')')
+			).map(function(node) {
+				return node[1];
+			})
+		),
+		optWhitespace,
+		regex(/VALUES\s?\(/i).skip(optWhitespace).then(valuesList),
+		string(')')
+	).map(function(node) {
+		var values = [];
+		var bigger = Math.max(node[2].length, node[4].length);
+
+		for (var i = 0; i < bigger; ++i) {
+			values[i] = {
+				target: node[2][i] || null,
+				value: node[4][i] || null,
+			};
+		}
+
+		return {
+			type: 'insert',
+			into: node[0],
+			values: values,
+		};
+	});
+
 	// DELETE parser
 	var deleteParser = seq(
 		regex(/DELETE FROM/i).skip(optWhitespace).then(opt(tableList)),
@@ -375,7 +436,7 @@
 	});
 
 	// Main parser
-	var p = alt(selectParser, deleteParser);
+	var p = alt(selectParser, insertParser, deleteParser);
 
 
 
diff --git a/tests/tests.js b/tests/tests.js
index d3496a9..b8a4136 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -28,11 +28,12 @@
 
 		var q = [
 			'SELECT * FROM table',
-			'DELETE FROM table'
+			'INSERT INTO table VALUES (1, 2, 3)',
+			'DELETE FROM table',
 		];
 		var ast = q.map(m.sql2ast);
 
-		var types = ['select', 'delete'];
+		var types = ['select', 'delete', 'insert'];
 
 		ast.forEach(function(a) {
 			strictEqual(a.status, true, "Parser must parse valid SQL");
@@ -50,6 +51,10 @@
 				isArray(a.value.from, "(DELETE) AST must contain a 'from' array");
 				isObject(a.value.where, "(DELETE) AST must contain a 'where' object");
 			}
+			else if (a.value.type === types[2]) {
+				isObject(a.value.into, "(INSERT) AST must contain a 'into' object");
+				isArray(a.value.values, "(INSERT) AST must contain a 'values' array");
+			}
 		});
 
 	});
@@ -321,6 +326,50 @@
 
 	});
 
+	test('sql2ast - insert', function() {
+
+		testAst('Simple insert', 'INSERT INTO table VALUES (1, 2, 3)', {
+			type: 'insert',
+			into: {
+				table: 'table',
+				alias: null,
+			},
+			values: [
+				{ target: null, value: '1'},
+				{ target: null, value: '2'},
+				{ target: null, value: '3'},
+			],
+		});
+
+		testAst('Complex values', 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', {
+			type: 'insert',
+			into: {
+				table: 'table',
+				alias: null,
+			},
+			values: [
+				{ target: null, value: '1 + 9'},
+				{ target: null, value: 'FUNC(2, col)'},
+				{ target: null, value: '"string"'},
+			],
+		});
+
+		testAst('Insert with columns', 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', {
+			type: 'insert',
+			into: {
+				table: 'table',
+				alias: null,
+			},
+			values: [
+				{ target: { expression: 'col1', column: 'col1' }, value: '1'},
+				{ target: { expression: '`col2`', column: 'col2' }, value: '2'},
+				{ target: { expression: 'col3', column: 'col3' }, value: '3'},
+			],
+		});
+
+
+	});
+
 	test('sql2ast - delete', function() {
 
 		testAst('Simple delete', 'DELETE FROM table', {

From e99fde1049fa0993f16924b09184b0e225ed123c Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 26 May 2014 10:54:56 +0200
Subject: [PATCH 29/75] Lint tests

---
 gulpfile.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gulpfile.js b/gulpfile.js
index 5069209..2988dc5 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -3,7 +3,7 @@ var jshint = require('gulp-jshint');
 var qunit = require('gulp-qunit');
 
 gulp.task('lint', function() {
-	return gulp.src('./*.js')
+	return gulp.src(['*.js', 'tests/*.js'])
 		.pipe(jshint())
 		.pipe(jshint.reporter('default'))
 		.pipe(jshint.reporter('fail'));

From 727924daf3ae5052782b7ca328292f5505ee0fca Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Mon, 26 May 2014 11:14:04 +0200
Subject: [PATCH 30/75] Add UPDATE support

---
 simpleSqlParser.js | 38 ++++++++++++++++++++++++++++++---
 tests/tests.js     | 52 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 86 insertions(+), 4 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index 2dddec0..f7066d4 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON|VALUES)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON|VALUES|SET)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -333,6 +333,20 @@
 		return node.expression;
 	});
 
+	// Expression that assign a value to a column
+	var assignExpression = seq(
+		insertColListExpression,
+		optWhitespace,
+		string('='),
+		optWhitespace,
+		expression
+	).map(function(node) {
+		return {
+			target: node[0],
+			value: node[4].expression,
+		};
+	});
+
 
 
 	/********************************************************************************************
@@ -360,9 +374,12 @@
 	// List of columns before VALUES in INSERT query
 	var insertColList = optionnalList(insertColListExpression);
 
-	// Lisf of values following a VALUES statement
+	// List of values following a VALUES statement
 	var valuesList = optionnalList(valueExpression);
 
+	// List of assign expression following a SET statement
+	var assignList = optionnalList(assignExpression);
+
 
 
 	/********************************************************************************************
@@ -423,6 +440,21 @@
 		};
 	});
 
+	var updateParser = seq(
+		regex(/UPDATE/i).skip(optWhitespace).then(tableListExpression),
+		optWhitespace,
+		regex(/SET/i).skip(optWhitespace).then(assignList),
+		optWhitespace,
+		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null)
+	).map(function(node) {
+		return {
+			type: 'update',
+			table: node[0],
+			values: node[2],
+			where: node[4],
+		};
+	});
+
 	// DELETE parser
 	var deleteParser = seq(
 		regex(/DELETE FROM/i).skip(optWhitespace).then(opt(tableList)),
@@ -436,7 +468,7 @@
 	});
 
 	// Main parser
-	var p = alt(selectParser, insertParser, deleteParser);
+	var p = alt(selectParser, insertParser, updateParser, deleteParser);
 
 
 
diff --git a/tests/tests.js b/tests/tests.js
index b8a4136..d617e46 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -29,11 +29,12 @@
 		var q = [
 			'SELECT * FROM table',
 			'INSERT INTO table VALUES (1, 2, 3)',
+			'UPDATE table SET col = "value"',
 			'DELETE FROM table',
 		];
 		var ast = q.map(m.sql2ast);
 
-		var types = ['select', 'delete', 'insert'];
+		var types = ['select', 'delete', 'insert', 'update'];
 
 		ast.forEach(function(a) {
 			strictEqual(a.status, true, "Parser must parse valid SQL");
@@ -55,6 +56,11 @@
 				isObject(a.value.into, "(INSERT) AST must contain a 'into' object");
 				isArray(a.value.values, "(INSERT) AST must contain a 'values' array");
 			}
+			else if (a.value.type === types[3]) {
+				isObject(a.value.table, "(UPDATE) AST must contain a 'table' object");
+				isArray(a.value.values, "(UPDATE) AST must contain a 'values' array");
+				isObject(a.value.where, "(UPDATE) AST must contain a 'where' object");
+			}
 		});
 
 	});
@@ -370,6 +376,50 @@
 
 	});
 
+	test('sql2ast - update', function() {
+
+		testAst('Simple update', 'UPDATE table SET col = "value"', {
+			type: 'update',
+			table: {
+				table: 'table',
+				alias: null,
+			},
+			where: null,
+			values: [
+				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+			],
+		});
+
+		testAst('Several columns', 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', {
+			type: 'update',
+			table: {
+				table: 'table',
+				alias: null,
+			},
+			where: null,
+			values: [
+				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+				{ target: { expression: 'col2', column: 'col2' }, value: 'NULL'},
+				{ target: { expression: 'table.col3', column: 'col3' }, value: 'col'},
+			],
+		});
+
+		testAst('Where #1', 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', {
+			type: 'update',
+			table: {
+				table: 'table',
+				alias: null,
+			},
+			where: {
+				expression: "this >= that AND col IS NOT NULL",
+			},
+			values: [
+				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+			],
+		});
+
+	});
+
 	test('sql2ast - delete', function() {
 
 		testAst('Simple delete', 'DELETE FROM table', {

From 0d841e3546a5404831dc322dc2b094c33afddbd2 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 10:16:55 +0200
Subject: [PATCH 31/75] Update dependencies

---
 package.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 5eba7bd..888f7f2 100644
--- a/package.json
+++ b/package.json
@@ -30,12 +30,12 @@
   "license": "MIT",
   "homepage": "https://github.com/dsferruzza/simpleSqlParser",
   "dependencies": {
-    "parsimmon": "0.4.0"
+    "parsimmon": "0.5.1"
   },
   "devDependencies": {
-    "gulp": "3.6.2",
-    "gulp-jshint": "1.6.1",
+    "gulp": "3.8.5",
+    "gulp-jshint": "1.6.3",
     "gulp-qunit": "0.3.3",
-    "qunit": "0.6.3"
+    "qunit": "0.6.4"
   }
 }

From 0162f55741bac16181b0e09d11dd1d0f6a560f10 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 10:54:27 +0200
Subject: [PATCH 32/75] Refactor tests

---
 tests/tests.js | 854 +++++++++++++++++++++++++++----------------------
 1 file changed, 477 insertions(+), 377 deletions(-)

diff --git a/tests/tests.js b/tests/tests.js
index d617e46..75338aa 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -65,400 +65,500 @@
 
 	});
 
-	test('sql2ast - select', function() {
-
-		testAst('Simple select', 'SELECT * FROM table', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Column quotes', 'SELECT col1, `col2` FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'col1', column: 'col1', table: null, alias: null },
-				{ expression: '`col2`', column: 'col2', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Special words', 'SELECT fromage "from", asymetric AS as FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' },
-				{ expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('"table.column" notation', 'SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'table.col1', column: 'col1', table: 'table', alias: null },
-				{ expression: 'table.`col2`', column: 'col2', table: 'table', alias: null },
-				{ expression: '`table`.col3', column: 'col3', table: 'table', alias: null },
-				{ expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Strings', 'SELECT "string", "\\"special\\" string" FROM table', {
-			type: 'select',
-			select: [
-				{ expression: '"string"', column: null, table: null, alias: null },
-				{ expression: '"\\"special\\" string"', column: null, table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Column alias #1', 'SELECT col1 AS alias, col2 AS "alias" FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' },
-				{ expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Column alias #2', 'SELECT col1 alias, col2 "alias" FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' },
-				{ expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Mathematical expressions', 'SELECT 1 + 1, col1*0.7 AS test FROM table', {
-			type: 'select',
-			select: [
-				{ expression: '1 + 1', column: null, table: null, alias: null },
-				{ expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Functions', 'SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', {
-			type: 'select',
-			select: [
-				{ expression: 'FUNC()', column: null, table: null, alias: null },
-				{ expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Table alias', 'SELECT * FROM table AS t, table2 AS "t2"', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: 't' },
-				{ table: 'table2', alias: 't2' },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Simple inner join', 'SELECT * FROM table INNER JOIN table2 ON table2.id = id_table2', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [
-				{ type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } },
-			],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Several joins', 'SELECT * FROM table JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [
-				{ type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } },
-				{ type: 'left', table: 't2', alias: null, condition: { expression: 't2.id = t1.id_t2' } },
-				{ type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } },
-			],
-			where: null,
-			order: [],
-			limit: null,
-		});
-
-		testAst('Where #1', 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: {
-				expression: "this >= that AND col IS NOT NULL",
+	var Select = [
+		{
+			c: 'Simple select',
+			q: 'SELECT * FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			order: [],
-			limit: null,
-		});
-
-		testAst('Where #2', 'SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: {
-				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
+		},
+		{
+			c: 'Column quotes',
+			q: 'SELECT col1, `col2` FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'col1', column: 'col1', table: null, alias: null },
+					{ expression: '`col2`', column: 'col2', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			order: [],
-			limit: null,
-		});
-
-		testAst('Order by', 'SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [
-				{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
-				{ expression: "col2 DESC", table: null, column: "col2", order: "DESC" },
-				{ expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" },
-			],
-			limit: null,
-		});
-
-		testAst('Limit #1', 'SELECT * FROM table LIMIT 5', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: { from: null, nb: 5 },
-		});
-
-		testAst('Limit #2', 'SELECT * FROM table LIMIT 1, 2', {
-			type: 'select',
-			select: [
-				{ expression: '*', column: '*', table: null, alias: null },
-			],
-			from: [
-				{ table: 'table', alias: null },
-			],
-			join: [],
-			where: null,
-			order: [],
-			limit: { from: 1, nb: 2 },
-		});
-
-	});
-
-	test('sql2ast - insert', function() {
-
-		testAst('Simple insert', 'INSERT INTO table VALUES (1, 2, 3)', {
-			type: 'insert',
-			into: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: 'Special words',
+			q: 'SELECT fromage "from", asymetric AS as FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' },
+					{ expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			values: [
-				{ target: null, value: '1'},
-				{ target: null, value: '2'},
-				{ target: null, value: '3'},
-			],
-		});
-
-		testAst('Complex values', 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', {
-			type: 'insert',
-			into: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: '"table.column" notation',
+			q: 'SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'table.col1', column: 'col1', table: 'table', alias: null },
+					{ expression: 'table.`col2`', column: 'col2', table: 'table', alias: null },
+					{ expression: '`table`.col3', column: 'col3', table: 'table', alias: null },
+					{ expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			values: [
-				{ target: null, value: '1 + 9'},
-				{ target: null, value: 'FUNC(2, col)'},
-				{ target: null, value: '"string"'},
-			],
-		});
-
-		testAst('Insert with columns', 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', {
-			type: 'insert',
-			into: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: 'Strings',
+			q: 'SELECT "string", "\\"special\\" string" FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '"string"', column: null, table: null, alias: null },
+					{ expression: '"\\"special\\" string"', column: null, table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			values: [
-				{ target: { expression: 'col1', column: 'col1' }, value: '1'},
-				{ target: { expression: '`col2`', column: 'col2' }, value: '2'},
-				{ target: { expression: 'col3', column: 'col3' }, value: '3'},
-			],
-		});
-
-
-	});
-
-	test('sql2ast - update', function() {
-
-		testAst('Simple update', 'UPDATE table SET col = "value"', {
-			type: 'update',
-			table: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: 'Column alias #1',
+			q: 'SELECT col1 AS alias, col2 AS "alias" FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' },
+					{ expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			where: null,
-			values: [
-				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
-			],
-		});
-
-		testAst('Several columns', 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', {
-			type: 'update',
-			table: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: 'Column alias #2',
+			q: 'SELECT col1 alias, col2 "alias" FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' },
+					{ expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			where: null,
-			values: [
-				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
-				{ target: { expression: 'col2', column: 'col2' }, value: 'NULL'},
-				{ target: { expression: 'table.col3', column: 'col3' }, value: 'col'},
-			],
-		});
-
-		testAst('Where #1', 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', {
-			type: 'update',
-			table: {
-				table: 'table',
-				alias: null,
+		},
+		{
+			c: 'Mathematical expressions',
+			q: 'SELECT 1 + 1, col1*0.7 AS test FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '1 + 1', column: null, table: null, alias: null },
+					{ expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			where: {
-				expression: "this >= that AND col IS NOT NULL",
+		},
+		{
+			c: 'Functions',
+			q: 'SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: 'FUNC()', column: null, table: null, alias: null },
+					{ expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
 			},
-			values: [
-				{ target: { expression: 'col', column: 'col' }, value: '"value"'},
-			],
-		});
-
-	});
-
-	test('sql2ast - delete', function() {
+		},
+		{
+			c: 'Table alias',
+			q: 'SELECT * FROM table AS t, table2 AS "t2"',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: 't' },
+					{ table: 'table2', alias: 't2' },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Simple inner join',
+			q: 'SELECT * FROM table INNER JOIN table2 ON table2.id = id_table2',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [
+					{ type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } },
+				],
+				where: null,
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Several joins',
+			q: 'SELECT * FROM table JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [
+					{ type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } },
+					{ type: 'left', table: 't2', alias: null, condition: { expression: 't2.id = t1.id_t2' } },
+					{ type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } },
+				],
+				where: null,
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Where #1',
+			q: 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: {
+					expression: "this >= that AND col IS NOT NULL",
+				},
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Where #2',
+			q: 'SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: {
+					expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
+				},
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Order by',
+			q: 'SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [
+					{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
+					{ expression: "col2 DESC", table: null, column: "col2", order: "DESC" },
+					{ expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" },
+				],
+				limit: null,
+			},
+		},
+		{
+			c: 'Limit #1',
+			q: 'SELECT * FROM table LIMIT 5',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: { from: null, nb: 5 },
+			},
+		},
+		{
+			c: 'Limit #2',
+			q: 'SELECT * FROM table LIMIT 1, 2',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				order: [],
+				limit: { from: 1, nb: 2 },
+			},
+		},
+	];
+
+	var Insert = [
+		{
+			c: 'Simple insert',
+			q: 'INSERT INTO table VALUES (1, 2, 3)',
+			a: {
+				type: 'insert',
+				into: {
+					table: 'table',
+					alias: null,
+				},
+				values: [
+					{ target: null, value: '1'},
+					{ target: null, value: '2'},
+					{ target: null, value: '3'},
+				],
+			},
+		},
+		{
+			c: 'Complex values',
+			q: 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")',
+			a: {
+				type: 'insert',
+				into: {
+					table: 'table',
+					alias: null,
+				},
+				values: [
+					{ target: null, value: '1 + 9'},
+					{ target: null, value: 'FUNC(2, col)'},
+					{ target: null, value: '"string"'},
+				],
+			},
+		},
+		{
+			c: 'Insert with columns',
+			q: 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)',
+			a: {
+				type: 'insert',
+				into: {
+					table: 'table',
+					alias: null,
+				},
+				values: [
+					{ target: { expression: 'col1', column: 'col1' }, value: '1'},
+					{ target: { expression: '`col2`', column: 'col2' }, value: '2'},
+					{ target: { expression: 'col3', column: 'col3' }, value: '3'},
+				],
+			},
+		},
+	];
+
+	var Update = [
+		{
+			c: 'Simple update',
+			q: 'UPDATE table SET col = "value"',
+			a: {
+				type: 'update',
+				table: {
+					table: 'table',
+					alias: null,
+				},
+				where: null,
+				values: [
+					{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+				],
+			},
+		},
+		{
+			c: 'Several columns',
+			q: 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col',
+			a: {
+				type: 'update',
+				table: {
+					table: 'table',
+					alias: null,
+				},
+				where: null,
+				values: [
+					{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+					{ target: { expression: 'col2', column: 'col2' }, value: 'NULL'},
+					{ target: { expression: 'table.col3', column: 'col3' }, value: 'col'},
+				],
+			},
+		},
+		{
+			c: 'Where #1',
+			q: 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL',
+			a: {
+				type: 'update',
+				table: {
+					table: 'table',
+					alias: null,
+				},
+				where: {
+					expression: "this >= that AND col IS NOT NULL",
+				},
+				values: [
+					{ target: { expression: 'col', column: 'col' }, value: '"value"'},
+				],
+			},
+		},
+	];
+
+	var Delete = [
+		{
+			c: 'Simple delete',
+			q: 'DELETE FROM table',
+			a: {
+				type: 'delete',
+				from: [
+					{ table: 'table', alias: null },
+				],
+				where: null,
+			},
+		},
+		{
+			c: 'Several tables with aliases',
+			q: 'DELETE FROM table1 AS t1, table2 "t2"',
+			a: {
+				type: 'delete',
+				from: [
+					{ table: 'table1', alias: 't1' },
+					{ table: 'table2', alias: 't2' },
+				],
+				where: null,
+			},
+		},
+		{
+			c: 'Where #1',
+			q: 'DELETE FROM table WHERE this >= that AND col IS NOT NULL',
+			a: {
+				type: 'delete',
+				from: [
+					{ table: 'table', alias: null },
+				],
+				where: {
+					expression: "this >= that AND col IS NOT NULL",
+				},
+			},
+		},
+		{
+			c: 'Where #2',
+			q: 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)',
+			a: {
+				type: 'delete',
+				from: [
+					{ table: 'table', alias: null },
+				],
+				where: {
+					expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
+				},
+			},
+		},
+	];
 
-		testAst('Simple delete', 'DELETE FROM table', {
-			type: 'delete',
-			from: [
-				{ table: 'table', alias: null },
-			],
-			where: null,
+	test('sql2ast - select', function() {
+		Select.forEach(function(test) {
+			testAst(test.c, test.q, test.a);
 		});
+	});
 
-		testAst('Several tables with aliases', 'DELETE FROM table1 AS t1, table2 "t2"', {
-			type: 'delete',
-			from: [
-				{ table: 'table1', alias: 't1' },
-				{ table: 'table2', alias: 't2' },
-			],
-			where: null,
+	test('sql2ast - insert', function() {
+		Insert.forEach(function(test) {
+			testAst(test.c, test.q, test.a);
 		});
+	});
 
-		testAst('Where #1', 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', {
-			type: 'delete',
-			from: [
-				{ table: 'table', alias: null },
-			],
-			where: {
-				expression: "this >= that AND col IS NOT NULL",
-			},
+	test('sql2ast - update', function() {
+		Update.forEach(function(test) {
+			testAst(test.c, test.q, test.a);
 		});
+	});
 
-		testAst('Where #2', 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', {
-			type: 'delete',
-			from: [
-				{ table: 'table', alias: null },
-			],
-			where: {
-				expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
-			},
+	test('sql2ast - delete', function() {
+		Delete.forEach(function(test) {
+			testAst(test.c, test.q, test.a);
 		});
-
 	});
 
 })();

From 6c8f6dfa1e6f48b29208efd2720b449e38e39d63 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 15:12:55 +0200
Subject: [PATCH 33/75] Implement ast2ql for insert queries

---
 simpleSqlParser.js | 39 +++++++++++++++++++++++++++++++++++++++
 tests/tests.js     | 10 ++++++++++
 2 files changed, 49 insertions(+)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index f7066d4..6cdbdae 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -480,4 +480,43 @@
 		return p.parse(sql);
 	};
 
+	exports.ast2sql = function(ast) {
+		if (typeof ast === 'object' && ast.status === true) ast = ast.value;
+		else return false;
+
+		function into(ast) {
+			var result = 'INSERT INTO ' + ast.into.table;
+			if (ast.into.alias !== null) result += ' AS ' + ast.into.alias;
+			return result;
+		}
+
+		function values(ast) {
+			var result = '';
+			var targets = ast.values.filter(function(item) {
+				return item.target !== null;
+			});
+			if (targets.length > 0) {
+				result += '(';
+				result += targets.map(function(item) {
+					return item.target.expression;
+				}).join(', ');
+				result += ') ';
+			}
+			result += 'VALUES (';
+			result += ast.values.map(function(item) {
+				return item.value;
+			}).join(', ');
+			result += ')';
+			return result;
+		}
+
+		var parts = [];
+		if (ast.type === 'insert') {
+			parts.push(into(ast));
+			parts.push(values(ast));
+		}
+
+		return parts.join(' ');
+	};
+
 })(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports, typeof Parsimmon === 'object' ? Parsimmon : require('Parsimmon'));
diff --git a/tests/tests.js b/tests/tests.js
index 75338aa..227d352 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -14,6 +14,10 @@
 		deepEqual(m.sql2ast(query), ok(ast), comment + ': ' + query);
 	}
 
+	function testBackAndForth(comment, query) {
+		deepEqual(query, m.ast2sql(m.sql2ast(query)), comment + ': ' + query);
+	}
+
 	function isArray(variable, message) {
 		strictEqual(typeof variable, "object", message);
 		strictEqual(Array.isArray(variable), true, message);
@@ -561,4 +565,10 @@
 		});
 	});
 
+	test('ast2sql - insert', function() {
+		Insert.forEach(function(test) {
+			testBackAndForth(test.c, test.q);
+		});
+	});
+
 })();

From 89f851450a0e3b5293945fd820f90bed7d13dfdd Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 15:52:05 +0200
Subject: [PATCH 34/75] Implement ast2sql for select queries

---
 simpleSqlParser.js | 75 ++++++++++++++++++++++++++++++++++++++++++++--
 tests/tests.js     | 10 +++++--
 2 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index 6cdbdae..b192921 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -484,6 +484,66 @@
 		if (typeof ast === 'object' && ast.status === true) ast = ast.value;
 		else return false;
 
+		function select(ast) {
+			var result = 'SELECT ';
+			result += ast.select.map(function(item) {
+				return item.expression;
+			}).join(', ');
+			return result;
+		}
+
+		function from(ast) {
+			var result = 'FROM ';
+			result += ast.from.map(function(item) {
+				var newItem = item.table;
+				if (item.alias !== null) newItem += ' AS ' + item.alias;
+				return newItem;
+			}).join(', ');
+			return result;
+		}
+
+		function join(ast) {
+			return ast.join.map(function(item) {
+				var result = '';
+				if (item.type === 'inner') result += 'INNER JOIN ';
+				else if (item.type === 'left') result += 'LEFT JOIN ';
+				else if (item.type === 'right') result += 'RIGHT JOIN ';
+				else return '';
+				result += item.table;
+				if (item.alias !== null) result += ' AS ' + item.alias;
+				result += ' ON ';
+				result += item.condition.expression;
+				return result;
+			}).join(' ');
+		}
+
+		function where(ast) {
+			var result = '';
+			if (ast.where !== null) result += 'WHERE ' + ast.where.expression;
+			return result;
+		}
+
+		function order(ast) {
+			var result = '';
+			if (ast.order.length > 0) {
+				result += 'ORDER BY ';
+				result += ast.order.map(function(item) {
+					return item.expression;
+				}).join(', ');
+			}
+			return result;
+		}
+
+		function limit(ast) {
+			var result = '';
+			if (ast.limit !== null) {
+				result += 'LIMIT ';
+				if (ast.limit.from !== null) result += ast.limit.from + ', ';
+				result += ast.limit.nb;
+			}
+			return result;
+		}
+
 		function into(ast) {
 			var result = 'INSERT INTO ' + ast.into.table;
 			if (ast.into.alias !== null) result += ' AS ' + ast.into.alias;
@@ -511,12 +571,23 @@
 		}
 
 		var parts = [];
-		if (ast.type === 'insert') {
+		if (ast.type === 'select') {
+			parts.push(select(ast));
+			parts.push(from(ast));
+			parts.push(join(ast));
+			parts.push(where(ast));
+			parts.push(order(ast));
+			parts.push(limit(ast));
+		}
+		else if (ast.type === 'insert') {
 			parts.push(into(ast));
 			parts.push(values(ast));
 		}
+		else return false;
 
-		return parts.join(' ');
+		return parts.filter(function(item) {
+			return item != '';
+		}).join(' ');
 	};
 
 })(typeof exports === "undefined" ? (this.simpleSqlParser = {}) : exports, typeof Parsimmon === 'object' ? Parsimmon : require('Parsimmon'));
diff --git a/tests/tests.js b/tests/tests.js
index 227d352..ca5dd0c 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -15,7 +15,7 @@
 	}
 
 	function testBackAndForth(comment, query) {
-		deepEqual(query, m.ast2sql(m.sql2ast(query)), comment + ': ' + query);
+		deepEqual(m.ast2sql(m.sql2ast(query)), query, comment + ': ' + query);
 	}
 
 	function isArray(variable, message) {
@@ -272,7 +272,7 @@
 		},
 		{
 			c: 'Several joins',
-			q: 'SELECT * FROM table JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)',
+			q: 'SELECT * FROM table INNER JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)',
 			a: {
 				type: 'select',
 				select: [
@@ -565,6 +565,12 @@
 		});
 	});
 
+	test('ast2sql - select', function() {
+		Select.forEach(function(test) {
+			testBackAndForth(test.c, test.q);
+		});
+	});
+
 	test('ast2sql - insert', function() {
 		Insert.forEach(function(test) {
 			testBackAndForth(test.c, test.q);

From b6efcc3c8cc3b8ac5bbb7554401f2ce99086052e Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 16:08:40 +0200
Subject: [PATCH 35/75] Add an expression field to table/into properties

---
 simpleSqlParser.js | 18 ++++++++--------
 tests/tests.js     | 52 ++++++++++++++++++++++++++--------------------
 2 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index b192921..e8a2085 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -232,14 +232,18 @@
 				opt(regex(/AS\s/i)),
 				alt(colName, str)
 			).map(function(node) {
-				return removeQuotes(node[2]);
+				return {
+					alias: removeQuotes(node[2]),
+					expression : node.join(''),
+				};
 			}),
 			null
 		)
 	).map(function(node) {
 		var n = {};
 		n.table = node[0];
-		n.alias = node[1];
+		n.alias = (node[1] !== null) ? node[1].alias : null;
+		n.expression = node[0] + ((node[1] !== null) ? node[1].expression : '');
 		return n;
 	});
 
@@ -495,9 +499,7 @@
 		function from(ast) {
 			var result = 'FROM ';
 			result += ast.from.map(function(item) {
-				var newItem = item.table;
-				if (item.alias !== null) newItem += ' AS ' + item.alias;
-				return newItem;
+				return item.expression;
 			}).join(', ');
 			return result;
 		}
@@ -545,9 +547,7 @@
 		}
 
 		function into(ast) {
-			var result = 'INSERT INTO ' + ast.into.table;
-			if (ast.into.alias !== null) result += ' AS ' + ast.into.alias;
-			return result;
+			return 'INSERT INTO ' + ast.into.expression;
 		}
 
 		function values(ast) {
@@ -586,7 +586,7 @@
 		else return false;
 
 		return parts.filter(function(item) {
-			return item != '';
+			return item !== '';
 		}).join(' ');
 	};
 
diff --git a/tests/tests.js b/tests/tests.js
index ca5dd0c..ca0d8f9 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -79,7 +79,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -97,7 +97,7 @@
 					{ expression: '`col2`', column: 'col2', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -115,7 +115,7 @@
 					{ expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -135,7 +135,7 @@
 					{ expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -153,7 +153,7 @@
 					{ expression: '"\\"special\\" string"', column: null, table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -171,7 +171,7 @@
 					{ expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -189,7 +189,7 @@
 					{ expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -207,7 +207,7 @@
 					{ expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -225,7 +225,7 @@
 					{ expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -242,8 +242,8 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: 't' },
-					{ table: 'table2', alias: 't2' },
+					{ expression: 'table AS t', table: 'table', alias: 't' },
+					{ expression: 'table2 AS "t2"', table: 'table2', alias: 't2' },
 				],
 				join: [],
 				where: null,
@@ -260,7 +260,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [
 					{ type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } },
@@ -279,7 +279,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [
 					{ type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } },
@@ -300,7 +300,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: {
@@ -319,7 +319,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: {
@@ -338,7 +338,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -359,7 +359,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -376,7 +376,7 @@
 					{ expression: '*', column: '*', table: null, alias: null },
 				],
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				join: [],
 				where: null,
@@ -393,6 +393,7 @@
 			a: {
 				type: 'insert',
 				into: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -409,6 +410,7 @@
 			a: {
 				type: 'insert',
 				into: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -425,6 +427,7 @@
 			a: {
 				type: 'insert',
 				into: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -444,6 +447,7 @@
 			a: {
 				type: 'update',
 				table: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -459,6 +463,7 @@
 			a: {
 				type: 'update',
 				table: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -476,6 +481,7 @@
 			a: {
 				type: 'update',
 				table: {
+					expression: 'table',
 					table: 'table',
 					alias: null,
 				},
@@ -496,7 +502,7 @@
 			a: {
 				type: 'delete',
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				where: null,
 			},
@@ -507,8 +513,8 @@
 			a: {
 				type: 'delete',
 				from: [
-					{ table: 'table1', alias: 't1' },
-					{ table: 'table2', alias: 't2' },
+					{ expression: 'table1 AS t1', table: 'table1', alias: 't1' },
+					{ expression: 'table2 "t2"', table: 'table2', alias: 't2' },
 				],
 				where: null,
 			},
@@ -519,7 +525,7 @@
 			a: {
 				type: 'delete',
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				where: {
 					expression: "this >= that AND col IS NOT NULL",
@@ -532,7 +538,7 @@
 			a: {
 				type: 'delete',
 				from: [
-					{ table: 'table', alias: null },
+					{ expression: 'table', table: 'table', alias: null },
 				],
 				where: {
 					expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",

From 934eccff27dc08b289c55d5e07a5440b5404b09a Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 16:14:32 +0200
Subject: [PATCH 36/75] Implement ast2sql for update queries

---
 simpleSqlParser.js | 17 +++++++++++++++++
 tests/tests.js     |  6 ++++++
 2 files changed, 23 insertions(+)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index e8a2085..365a392 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -570,6 +570,18 @@
 			return result;
 		}
 
+		function table(ast) {
+			return 'UPDATE ' + ast.table.expression;
+		}
+
+		function update(ast) {
+			var result = 'SET ';
+			result += ast.values.map(function(item) {
+				return item.target.expression + ' = ' + item.value;
+			}).join(', ');
+			return result;
+		}
+
 		var parts = [];
 		if (ast.type === 'select') {
 			parts.push(select(ast));
@@ -583,6 +595,11 @@
 			parts.push(into(ast));
 			parts.push(values(ast));
 		}
+		else if (ast.type === 'update') {
+			parts.push(table(ast));
+			parts.push(update(ast));
+			parts.push(where(ast));
+		}
 		else return false;
 
 		return parts.filter(function(item) {
diff --git a/tests/tests.js b/tests/tests.js
index ca0d8f9..0c57d28 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -583,4 +583,10 @@
 		});
 	});
 
+	test('ast2sql - update', function() {
+		Update.forEach(function(test) {
+			testBackAndForth(test.c, test.q);
+		});
+	});
+
 })();

From cc3233e652268eba68924f0731b932c9428ca33e Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 16:17:28 +0200
Subject: [PATCH 37/75] Implement ast2sql for delete queries

---
 simpleSqlParser.js | 5 +++++
 tests/tests.js     | 6 ++++++
 2 files changed, 11 insertions(+)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index 365a392..e2c720c 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -600,6 +600,11 @@
 			parts.push(update(ast));
 			parts.push(where(ast));
 		}
+		else if (ast.type === 'delete') {
+			parts.push('DELETE');
+			parts.push(from(ast));
+			parts.push(where(ast));
+		}
 		else return false;
 
 		return parts.filter(function(item) {
diff --git a/tests/tests.js b/tests/tests.js
index 0c57d28..b09be54 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -589,4 +589,10 @@
 		});
 	});
 
+	test('ast2sql - delete', function() {
+		Delete.forEach(function(test) {
+			testBackAndForth(test.c, test.q);
+		});
+	});
+
 })();

From 11f9c249dba3f27edd9204809a2c12e4ddd48c4e Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Tue, 1 Jul 2014 16:40:32 +0200
Subject: [PATCH 38/75] Add GROUP BY support

---
 simpleSqlParser.js | 23 ++++++++++++++++++++---
 tests/tests.js     | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index e2c720c..fa8e6e5 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -60,7 +60,7 @@
 
 	// The name of a column/table
 	var colName = alt(
-		regex(/(?!(FROM|WHERE|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON|VALUES|SET)\s)[a-z*][a-z0-9_]*/i),
+		regex(/(?!(FROM|WHERE|GROUP BY|ORDER BY|LIMIT|INNER|LEFT|RIGHT|JOIN|ON|VALUES|SET)\s)[a-z*][a-z0-9_]*/i),
 		regex(/`[^`\\]*(?:\\.[^`\\]*)*`/)
 	);
 
@@ -369,6 +369,9 @@
 	// List of table following a FROM statement
 	var tableList = optionnalList(tableListExpression);
 
+	// List of table following an GROUP BY statement
+	var groupList = optionnalList(expression);
+
 	// List of table following an ORDER BY statement
 	var orderList = optionnalList(orderListExpression);
 
@@ -396,6 +399,7 @@
 		regex(/FROM/i).skip(optWhitespace).then(opt(tableList)),
 		opt(joinList),
 		opt(regex(/WHERE/i).skip(optWhitespace).then(opt(whereExpression)), null),
+		opt(regex(/GROUP BY/i).skip(optWhitespace).then(opt(groupList))),
 		opt(regex(/ORDER BY/i).skip(optWhitespace).then(opt(orderList))),
 		opt(regex(/LIMIT/i).skip(optWhitespace).then(opt(limitExpression)), null)
 	).map(function(node) {
@@ -405,8 +409,9 @@
 			from: node[1],
 			join: node[2],
 			where: node[3],
-			order: node[4],
-			limit: node[5],
+			group: node[4],
+			order: node[5],
+			limit: node[6],
 		};
 	});
 
@@ -525,6 +530,17 @@
 			return result;
 		}
 
+		function group(ast) {
+			var result = '';
+			if (ast.group.length > 0) {
+				result += 'GROUP BY ';
+				result += ast.group.map(function(item) {
+					return item.expression;
+				}).join(', ');
+			}
+			return result;
+		}
+
 		function order(ast) {
 			var result = '';
 			if (ast.order.length > 0) {
@@ -588,6 +604,7 @@
 			parts.push(from(ast));
 			parts.push(join(ast));
 			parts.push(where(ast));
+			parts.push(group(ast));
 			parts.push(order(ast));
 			parts.push(limit(ast));
 		}
diff --git a/tests/tests.js b/tests/tests.js
index b09be54..0ec9f99 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -83,6 +83,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -101,6 +102,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -119,6 +121,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -139,6 +142,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -157,6 +161,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -175,6 +180,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -193,6 +199,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -211,6 +218,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -229,6 +237,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -247,6 +256,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -266,6 +276,7 @@
 					{ type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } },
 				],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -287,6 +298,7 @@
 					{ type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } },
 				],
 				where: null,
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -306,6 +318,7 @@
 				where: {
 					expression: "this >= that AND col IS NOT NULL",
 				},
+				group: [],
 				order: [],
 				limit: null,
 			},
@@ -325,6 +338,29 @@
 				where: {
 					expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)",
 				},
+				group: [],
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Group by',
+			q: 'SELECT * FROM table GROUP BY col1, MONTH(col2), table.col3',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ expression: 'table', table: 'table', alias: null },
+				],
+				join: [],
+				where: null,
+				group: [
+					{ expression: 'col1', table: null, column: 'col1' },
+					{ expression: 'MONTH(col2)', table: null, column: null },
+					{ expression: 'table.col3', table: 'table', column: 'col3' },
+				],
 				order: [],
 				limit: null,
 			},
@@ -342,6 +378,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [
 					{ expression: "table.col1", table: "table", column: "col1", order: "ASC" },
 					{ expression: "col2 DESC", table: null, column: "col2", order: "DESC" },
@@ -363,6 +400,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: { from: null, nb: 5 },
 			},
@@ -380,6 +418,7 @@
 				],
 				join: [],
 				where: null,
+				group: [],
 				order: [],
 				limit: { from: 1, nb: 2 },
 			},

From 9bf20af99803b5f17a816818e5ef159aa0c0bf8a Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 4 Jul 2014 12:12:08 +0200
Subject: [PATCH 39/75] Update dependencies

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 888f7f2..f759995 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
   },
   "devDependencies": {
     "gulp": "3.8.5",
-    "gulp-jshint": "1.6.3",
+    "gulp-jshint": "1.6.4",
     "gulp-qunit": "0.3.3",
     "qunit": "0.6.4"
   }

From 6cbdc3afc75c7038d6aa65f0a21839e85a5cfa5e Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Fri, 4 Jul 2014 12:30:07 +0200
Subject: [PATCH 40/75] Fix API test

---
 tests/tests.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/tests.js b/tests/tests.js
index 0ec9f99..cb7367a 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -49,6 +49,7 @@
 				isArray(a.value.from, "(SELECT) AST must contain a 'from' array");
 				isArray(a.value.join, "(SELECT) AST must contain a 'join' array");
 				isObject(a.value.where, "(SELECT) AST must contain a 'where' object");
+				isArray(a.value.group, "(SELECT) AST must contain a 'group' array");
 				isArray(a.value.order, "(SELECT) AST must contain a 'order' array");
 				isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object");
 			}

From f242c3f81fdd7a723a56f75aed81be7cdaf2cad9 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Thu, 10 Jul 2014 00:47:59 +0200
Subject: [PATCH 41/75] Add IN support

---
 simpleSqlParser.js | 33 ++++++++++++++++++++++++++++++++-
 tests/tests.js     | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 72 insertions(+), 1 deletion(-)

diff --git a/simpleSqlParser.js b/simpleSqlParser.js
index fa8e6e5..e181eaa 100644
--- a/simpleSqlParser.js
+++ b/simpleSqlParser.js
@@ -119,7 +119,8 @@
 		regex(/MOD/i),
 		regex(/NOT/i),
 		regex(/OR/i),
-		regex(/AND/i)
+		regex(/AND/i),
+		regex(/IN/i)
 	);
 
 	// A number
@@ -131,6 +132,29 @@
 		EXPRESSION PARSERS
 	********************************************************************************************/
 
+	// List (following IN, for example)
+	var list = seq(
+		string('('),
+		optWhitespace,
+		seq(
+			alt(
+				number,
+				str
+			),
+			optWhitespace,
+			opt(string(',')),
+			optWhitespace,
+			opt(
+				alt(
+					number,
+					str
+				)
+			)
+		).map(mkString),
+		optWhitespace,
+		string(')')
+	).map(mkString);
+
 	// Expression
 	var expression = seq(
 		alt(
@@ -168,6 +192,13 @@
 					table: null,
 					column: null
 				};
+			}),
+			list.map(function(node) {
+				return {
+					expression: node,
+					table: null,
+					column: null
+				};
 			})
 		),
 		opt(seq(
diff --git a/tests/tests.js b/tests/tests.js
index cb7367a..daf1078 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -344,6 +344,46 @@
 				limit: null,
 			},
 		},
+		{
+			c: 'Where #3',
+			q: 'SELECT * FROM table WHERE column IN ("val1", "val2")',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ expression: 'table', table: 'table', alias: null },
+				],
+				join: [],
+				where: {
+					expression: "column IN (\"val1\", \"val2\")",
+				},
+				group: [],
+				order: [],
+				limit: null,
+			},
+		},
+		{
+			c: 'Where #3 with spaces',
+			q: 'SELECT * FROM table WHERE column IN ( "val1", "val2" )',
+			a: {
+				type: 'select',
+				select: [
+					{ expression: '*', column: '*', table: null, alias: null },
+				],
+				from: [
+					{ expression: 'table', table: 'table', alias: null },
+				],
+				join: [],
+				where: {
+					expression: "column IN ( \"val1\", \"val2\" )",
+				},
+				group: [],
+				order: [],
+				limit: null,
+			},
+		},
 		{
 			c: 'Group by',
 			q: 'SELECT * FROM table GROUP BY col1, MONTH(col2), table.col3',

From 09e879945d5a9f73d2819e968881fc6aa03b9324 Mon Sep 17 00:00:00 2001
From: David Sferruzza 
Date: Sat, 12 Jul 2014 21:11:00 +0200
Subject: [PATCH 42/75] Replace QUnit by Mocha

---
 .jshintrc        |    4 +
 gulpfile.js      |    9 +-
 package.json     |    4 +-
 tests/tests.html |   15 -
 tests/tests.js   | 1205 +++++++++++++++++++++++-----------------------
 5 files changed, 613 insertions(+), 624 deletions(-)
 create mode 100644 .jshintrc
 delete mode 100644 tests/tests.html

diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..ef8dbdd
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,4 @@
+{
+	"node": true,
+	"expr": true
+}
diff --git a/gulpfile.js b/gulpfile.js
index 2988dc5..da42acb 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,6 +1,6 @@
 var gulp = require('gulp');
 var jshint = require('gulp-jshint');
-var qunit = require('gulp-qunit');
+var mocha = require('gulp-mocha');
 
 gulp.task('lint', function() {
 	return gulp.src(['*.js', 'tests/*.js'])
@@ -10,8 +10,11 @@ gulp.task('lint', function() {
 });
 
 gulp.task('test', function() {
-	return gulp.src('./tests/tests.html')
-		.pipe(qunit());
+	return gulp.src('./tests/tests.js')
+		.pipe(mocha({
+			ui: 'qunit',
+			reporter: 'spec',
+		}));
 });
 
 gulp.task('default', ['lint', 'test']);
diff --git a/package.json b/package.json
index f759995..11ac27d 100644
--- a/package.json
+++ b/package.json
@@ -33,9 +33,9 @@
     "parsimmon": "0.5.1"
   },
   "devDependencies": {
+    "chai": "1.9.1",
     "gulp": "3.8.5",
     "gulp-jshint": "1.6.4",
-    "gulp-qunit": "0.3.3",
-    "qunit": "0.6.4"
+    "gulp-mocha": "0.5.1"
   }
 }
diff --git a/tests/tests.html b/tests/tests.html
deleted file mode 100644
index 6aa5d72..0000000
--- a/tests/tests.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-	
-		Tests - simpleSqlParser
-		
-		
-	
-	
-		
- - - - - - diff --git a/tests/tests.js b/tests/tests.js index daf1078..d50c021 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -1,678 +1,675 @@ -(function () { - "use strict"; +/*global test:true*/ +"use strict"; - var m = simpleSqlParser; +var expect = require('chai').expect; +var m = require('../simpleSqlParser'); - function ok(ast) { - return { - status: true, - value: ast - }; - } +function ok(ast) { + return { + status: true, + value: ast + }; +} - function testAst(comment, query, ast) { - deepEqual(m.sql2ast(query), ok(ast), comment + ': ' + query); - } +function testAst(comment, query, ast) { + expect(m.sql2ast(query)).to.eql(ok(ast), comment + ': ' + query); +} - function testBackAndForth(comment, query) { - deepEqual(m.ast2sql(m.sql2ast(query)), query, comment + ': ' + query); - } +function testBackAndForth(comment, query) { + expect(m.ast2sql(m.sql2ast(query))).to.eql(query, comment + ': ' + query); +} - function isArray(variable, message) { - strictEqual(typeof variable, "object", message); - strictEqual(Array.isArray(variable), true, message); - } +function isArray(variable, message) { + expect(variable).to.be.instanceOf(Array, message); +} - function isObject(variable, message) { - strictEqual(typeof variable, "object", message); - strictEqual(Array.isArray(variable), false, message); - } +function isObject(variable, message) { + expect(variable).not.to.be.instanceOf(Array, message); +} - test('sql2ast - API', function() { +test('sql2ast - API', function() { - var q = [ - 'SELECT * FROM table', - 'INSERT INTO table VALUES (1, 2, 3)', - 'UPDATE table SET col = "value"', - 'DELETE FROM table', - ]; - var ast = q.map(m.sql2ast); - - var types = ['select', 'delete', 'insert', 'update']; + var q = [ + 'SELECT * FROM table', + 'INSERT INTO table VALUES (1, 2, 3)', + 'UPDATE table SET col = "value"', + 'DELETE FROM table', + ]; + var ast = q.map(m.sql2ast); - ast.forEach(function(a) { - strictEqual(a.status, true, "Parser must parse valid SQL"); - notStrictEqual(types.indexOf(a.value.type), -1, "AST must contain a valid type"); + var types = ['select', 'delete', 'insert', 'update']; - if (a.value.type === types[0]) { - isArray(a.value.select, "(SELECT) AST must contain a 'select' array"); - isArray(a.value.from, "(SELECT) AST must contain a 'from' array"); - isArray(a.value.join, "(SELECT) AST must contain a 'join' array"); - isObject(a.value.where, "(SELECT) AST must contain a 'where' object"); - isArray(a.value.group, "(SELECT) AST must contain a 'group' array"); - isArray(a.value.order, "(SELECT) AST must contain a 'order' array"); - isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object"); - } - else if (a.value.type === types[1]) { - isArray(a.value.from, "(DELETE) AST must contain a 'from' array"); - isObject(a.value.where, "(DELETE) AST must contain a 'where' object"); - } - else if (a.value.type === types[2]) { - isObject(a.value.into, "(INSERT) AST must contain a 'into' object"); - isArray(a.value.values, "(INSERT) AST must contain a 'values' array"); - } - else if (a.value.type === types[3]) { - isObject(a.value.table, "(UPDATE) AST must contain a 'table' object"); - isArray(a.value.values, "(UPDATE) AST must contain a 'values' array"); - isObject(a.value.where, "(UPDATE) AST must contain a 'where' object"); - } - }); + ast.forEach(function(a) { + expect(a.status).to.equal(true, 'Parser must parse valid SQL'); + expect(types.indexOf(a.value.type)).not.to.equal(-1, 'AST must contain a valid type'); + if (a.value.type === types[0]) { + isArray(a.value.select, "(SELECT) AST must contain a 'select' array"); + isArray(a.value.from, "(SELECT) AST must contain a 'from' array"); + isArray(a.value.join, "(SELECT) AST must contain a 'join' array"); + isObject(a.value.where, "(SELECT) AST must contain a 'where' object"); + isArray(a.value.group, "(SELECT) AST must contain a 'group' array"); + isArray(a.value.order, "(SELECT) AST must contain a 'order' array"); + isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object"); + } + else if (a.value.type === types[1]) { + isArray(a.value.from, "(DELETE) AST must contain a 'from' array"); + isObject(a.value.where, "(DELETE) AST must contain a 'where' object"); + } + else if (a.value.type === types[2]) { + isObject(a.value.into, "(INSERT) AST must contain a 'into' object"); + isArray(a.value.values, "(INSERT) AST must contain a 'values' array"); + } + else if (a.value.type === types[3]) { + isObject(a.value.table, "(UPDATE) AST must contain a 'table' object"); + isArray(a.value.values, "(UPDATE) AST must contain a 'values' array"); + isObject(a.value.where, "(UPDATE) AST must contain a 'where' object"); + } }); - var Select = [ - { - c: 'Simple select', - q: 'SELECT * FROM table', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, +}); + +var Select = [ + { + c: 'Simple select', + q: 'SELECT * FROM table', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Column quotes', - q: 'SELECT col1, `col2` FROM table', - a: { - type: 'select', - select: [ - { expression: 'col1', column: 'col1', table: null, alias: null }, - { expression: '`col2`', column: 'col2', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Column quotes', + q: 'SELECT col1, `col2` FROM table', + a: { + type: 'select', + select: [ + { expression: 'col1', column: 'col1', table: null, alias: null }, + { expression: '`col2`', column: 'col2', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Special words', - q: 'SELECT fromage "from", asymetric AS as FROM table', - a: { - type: 'select', - select: [ - { expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' }, - { expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Special words', + q: 'SELECT fromage "from", asymetric AS as FROM table', + a: { + type: 'select', + select: [ + { expression: 'fromage "from"', column: 'fromage', table: null, alias: 'from' }, + { expression: 'asymetric AS as', column: 'asymetric', table: null, alias: 'as' }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: '"table.column" notation', - q: 'SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', - a: { - type: 'select', - select: [ - { expression: 'table.col1', column: 'col1', table: 'table', alias: null }, - { expression: 'table.`col2`', column: 'col2', table: 'table', alias: null }, - { expression: '`table`.col3', column: 'col3', table: 'table', alias: null }, - { expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: '"table.column" notation', + q: 'SELECT table.col1, table.`col2`, `table`.col3, `table`.`col4` FROM table', + a: { + type: 'select', + select: [ + { expression: 'table.col1', column: 'col1', table: 'table', alias: null }, + { expression: 'table.`col2`', column: 'col2', table: 'table', alias: null }, + { expression: '`table`.col3', column: 'col3', table: 'table', alias: null }, + { expression: '`table`.`col4`', column: 'col4', table: 'table', alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Strings', - q: 'SELECT "string", "\\"special\\" string" FROM table', - a: { - type: 'select', - select: [ - { expression: '"string"', column: null, table: null, alias: null }, - { expression: '"\\"special\\" string"', column: null, table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Strings', + q: 'SELECT "string", "\\"special\\" string" FROM table', + a: { + type: 'select', + select: [ + { expression: '"string"', column: null, table: null, alias: null }, + { expression: '"\\"special\\" string"', column: null, table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Column alias #1', - q: 'SELECT col1 AS alias, col2 AS "alias" FROM table', - a: { - type: 'select', - select: [ - { expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' }, - { expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Column alias #1', + q: 'SELECT col1 AS alias, col2 AS "alias" FROM table', + a: { + type: 'select', + select: [ + { expression: 'col1 AS alias', column: 'col1', table: null, alias: 'alias' }, + { expression: 'col2 AS "alias"', column: 'col2', table: null, alias: 'alias' }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Column alias #2', - q: 'SELECT col1 alias, col2 "alias" FROM table', - a: { - type: 'select', - select: [ - { expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' }, - { expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Column alias #2', + q: 'SELECT col1 alias, col2 "alias" FROM table', + a: { + type: 'select', + select: [ + { expression: 'col1 alias', column: 'col1', table: null, alias: 'alias' }, + { expression: 'col2 "alias"', column: 'col2', table: null, alias: 'alias' }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Mathematical expressions', - q: 'SELECT 1 + 1, col1*0.7 AS test FROM table', - a: { - type: 'select', - select: [ - { expression: '1 + 1', column: null, table: null, alias: null }, - { expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Mathematical expressions', + q: 'SELECT 1 + 1, col1*0.7 AS test FROM table', + a: { + type: 'select', + select: [ + { expression: '1 + 1', column: null, table: null, alias: null }, + { expression: 'col1*0.7 AS test', column: null, table: null, alias: 'test' }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Functions', - q: 'SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', - a: { - type: 'select', - select: [ - { expression: 'FUNC()', column: null, table: null, alias: null }, - { expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Functions', + q: 'SELECT FUNC(), OTHERFUN(col, FUNC(1/4, -3.05), "string") FROM table', + a: { + type: 'select', + select: [ + { expression: 'FUNC()', column: null, table: null, alias: null }, + { expression: 'OTHERFUN(col, FUNC(1/4, -3.05), "string")', column: null, table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Table alias', - q: 'SELECT * FROM table AS t, table2 AS "t2"', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table AS t', table: 'table', alias: 't' }, - { expression: 'table2 AS "t2"', table: 'table2', alias: 't2' }, - ], - join: [], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Table alias', + q: 'SELECT * FROM table AS t, table2 AS "t2"', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table AS t', table: 'table', alias: 't' }, + { expression: 'table2 AS "t2"', table: 'table2', alias: 't2' }, + ], + join: [], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Simple inner join', - q: 'SELECT * FROM table INNER JOIN table2 ON table2.id = id_table2', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [ - { type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } }, - ], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Simple inner join', + q: 'SELECT * FROM table INNER JOIN table2 ON table2.id = id_table2', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [ + { type: 'inner', table: 'table2', alias: null, condition: { expression: 'table2.id = id_table2' } }, + ], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Several joins', - q: 'SELECT * FROM table INNER JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [ - { type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } }, - { type: 'left', table: 't2', alias: null, condition: { expression: 't2.id = t1.id_t2' } }, - { type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } }, - ], - where: null, - group: [], - order: [], - limit: null, - }, + }, + { + c: 'Several joins', + q: 'SELECT * FROM table INNER JOIN t1 ON t1.id = id_table2 AND t1.bool LEFT JOIN t2 ON t2.id = t1.id_t2 RIGHT JOIN t3 AS table3 ON t3.id = FUNC(t1.stuff)', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [ + { type: 'inner', table: 't1', alias: null, condition: { expression: 't1.id = id_table2 AND t1.bool' } }, + { type: 'left', table: 't2', alias: null, condition: { expression: 't2.id = t1.id_t2' } }, + { type: 'right', table: 't3', alias: 'table3', condition: { expression: 't3.id = FUNC(t1.stuff)' } }, + ], + where: null, + group: [], + order: [], + limit: null, }, - { - c: 'Where #1', - q: 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: { - expression: "this >= that AND col IS NOT NULL", - }, - group: [], - order: [], - limit: null, + }, + { + c: 'Where #1', + q: 'SELECT * FROM table WHERE this >= that AND col IS NOT NULL', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: { + expression: "this >= that AND col IS NOT NULL", }, + group: [], + order: [], + limit: null, }, - { - c: 'Where #2', - q: 'SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: { - expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", - }, - group: [], - order: [], - limit: null, + }, + { + c: 'Where #2', + q: 'SELECT * FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: { + expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", }, + group: [], + order: [], + limit: null, }, - { - c: 'Where #3', - q: 'SELECT * FROM table WHERE column IN ("val1", "val2")', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: { - expression: "column IN (\"val1\", \"val2\")", - }, - group: [], - order: [], - limit: null, + }, + { + c: 'Where #3', + q: 'SELECT * FROM table WHERE column IN ("val1", "val2")', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: { + expression: "column IN (\"val1\", \"val2\")", }, + group: [], + order: [], + limit: null, }, - { - c: 'Where #3 with spaces', - q: 'SELECT * FROM table WHERE column IN ( "val1", "val2" )', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: { - expression: "column IN ( \"val1\", \"val2\" )", - }, - group: [], - order: [], - limit: null, + }, + { + c: 'Where #3 with spaces', + q: 'SELECT * FROM table WHERE column IN ( "val1", "val2" )', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: { + expression: "column IN ( \"val1\", \"val2\" )", }, + group: [], + order: [], + limit: null, }, - { - c: 'Group by', - q: 'SELECT * FROM table GROUP BY col1, MONTH(col2), table.col3', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [ - { expression: 'col1', table: null, column: 'col1' }, - { expression: 'MONTH(col2)', table: null, column: null }, - { expression: 'table.col3', table: 'table', column: 'col3' }, - ], - order: [], - limit: null, - }, + }, + { + c: 'Group by', + q: 'SELECT * FROM table GROUP BY col1, MONTH(col2), table.col3', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [ + { expression: 'col1', table: null, column: 'col1' }, + { expression: 'MONTH(col2)', table: null, column: null }, + { expression: 'table.col3', table: 'table', column: 'col3' }, + ], + order: [], + limit: null, }, - { - c: 'Order by', - q: 'SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [ - { expression: "table.col1", table: "table", column: "col1", order: "ASC" }, - { expression: "col2 DESC", table: null, column: "col2", order: "DESC" }, - { expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" }, - ], - limit: null, - }, + }, + { + c: 'Order by', + q: 'SELECT * FROM table ORDER BY table.col1, col2 DESC, FUNC(col3 + 7) ASC', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [ + { expression: "table.col1", table: "table", column: "col1", order: "ASC" }, + { expression: "col2 DESC", table: null, column: "col2", order: "DESC" }, + { expression: "FUNC(col3 + 7) ASC", table: null, column: null, order: "ASC" }, + ], + limit: null, }, - { - c: 'Limit #1', - q: 'SELECT * FROM table LIMIT 5', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: { from: null, nb: 5 }, - }, + }, + { + c: 'Limit #1', + q: 'SELECT * FROM table LIMIT 5', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: { from: null, nb: 5 }, }, - { - c: 'Limit #2', - q: 'SELECT * FROM table LIMIT 1, 2', - a: { - type: 'select', - select: [ - { expression: '*', column: '*', table: null, alias: null }, - ], - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - join: [], - where: null, - group: [], - order: [], - limit: { from: 1, nb: 2 }, - }, + }, + { + c: 'Limit #2', + q: 'SELECT * FROM table LIMIT 1, 2', + a: { + type: 'select', + select: [ + { expression: '*', column: '*', table: null, alias: null }, + ], + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + join: [], + where: null, + group: [], + order: [], + limit: { from: 1, nb: 2 }, }, - ]; + }, +]; - var Insert = [ - { - c: 'Simple insert', - q: 'INSERT INTO table VALUES (1, 2, 3)', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: null, value: '1'}, - { target: null, value: '2'}, - { target: null, value: '3'}, - ], +var Insert = [ + { + c: 'Simple insert', + q: 'INSERT INTO table VALUES (1, 2, 3)', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, }, + values: [ + { target: null, value: '1'}, + { target: null, value: '2'}, + { target: null, value: '3'}, + ], }, - { - c: 'Complex values', - q: 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: null, value: '1 + 9'}, - { target: null, value: 'FUNC(2, col)'}, - { target: null, value: '"string"'}, - ], + }, + { + c: 'Complex values', + q: 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, }, + values: [ + { target: null, value: '1 + 9'}, + { target: null, value: 'FUNC(2, col)'}, + { target: null, value: '"string"'}, + ], }, - { - c: 'Insert with columns', - q: 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: { expression: 'col1', column: 'col1' }, value: '1'}, - { target: { expression: '`col2`', column: 'col2' }, value: '2'}, - { target: { expression: 'col3', column: 'col3' }, value: '3'}, - ], + }, + { + c: 'Insert with columns', + q: 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, }, + values: [ + { target: { expression: 'col1', column: 'col1' }, value: '1'}, + { target: { expression: '`col2`', column: 'col2' }, value: '2'}, + { target: { expression: 'col3', column: 'col3' }, value: '3'}, + ], }, - ]; + }, +]; - var Update = [ - { - c: 'Simple update', - q: 'UPDATE table SET col = "value"', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: null, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - ], +var Update = [ + { + c: 'Simple update', + q: 'UPDATE table SET col = "value"', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, }, + where: null, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + ], }, - { - c: 'Several columns', - q: 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: null, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - { target: { expression: 'col2', column: 'col2' }, value: 'NULL'}, - { target: { expression: 'table.col3', column: 'col3' }, value: 'col'}, - ], + }, + { + c: 'Several columns', + q: 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, }, + where: null, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + { target: { expression: 'col2', column: 'col2' }, value: 'NULL'}, + { target: { expression: 'table.col3', column: 'col3' }, value: 'col'}, + ], }, - { - c: 'Where #1', - q: 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: { - expression: "this >= that AND col IS NOT NULL", - }, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - ], + }, + { + c: 'Where #1', + q: 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, }, + where: { + expression: "this >= that AND col IS NOT NULL", + }, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + ], }, - ]; + }, +]; - var Delete = [ - { - c: 'Simple delete', - q: 'DELETE FROM table', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: null, - }, +var Delete = [ + { + c: 'Simple delete', + q: 'DELETE FROM table', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: null, }, - { - c: 'Several tables with aliases', - q: 'DELETE FROM table1 AS t1, table2 "t2"', - a: { - type: 'delete', - from: [ - { expression: 'table1 AS t1', table: 'table1', alias: 't1' }, - { expression: 'table2 "t2"', table: 'table2', alias: 't2' }, - ], - where: null, - }, + }, + { + c: 'Several tables with aliases', + q: 'DELETE FROM table1 AS t1, table2 "t2"', + a: { + type: 'delete', + from: [ + { expression: 'table1 AS t1', table: 'table1', alias: 't1' }, + { expression: 'table2 "t2"', table: 'table2', alias: 't2' }, + ], + where: null, }, - { - c: 'Where #1', - q: 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: { - expression: "this >= that AND col IS NOT NULL", - }, + }, + { + c: 'Where #1', + q: 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: { + expression: "this >= that AND col IS NOT NULL", }, }, - { - c: 'Where #2', - q: 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: { - expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", - }, + }, + { + c: 'Where #2', + q: 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: { + expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", }, }, - ]; + }, +]; - test('sql2ast - select', function() { - Select.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); +test('sql2ast - select', function() { + Select.forEach(function(test) { + testAst(test.c, test.q, test.a); }); +}); - test('sql2ast - insert', function() { - Insert.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); +test('sql2ast - insert', function() { + Insert.forEach(function(test) { + testAst(test.c, test.q, test.a); }); +}); - test('sql2ast - update', function() { - Update.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); +test('sql2ast - update', function() { + Update.forEach(function(test) { + testAst(test.c, test.q, test.a); }); +}); - test('sql2ast - delete', function() { - Delete.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); +test('sql2ast - delete', function() { + Delete.forEach(function(test) { + testAst(test.c, test.q, test.a); }); +}); - test('ast2sql - select', function() { - Select.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); +test('ast2sql - select', function() { + Select.forEach(function(test) { + testBackAndForth(test.c, test.q); }); +}); - test('ast2sql - insert', function() { - Insert.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); +test('ast2sql - insert', function() { + Insert.forEach(function(test) { + testBackAndForth(test.c, test.q); }); +}); - test('ast2sql - update', function() { - Update.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); +test('ast2sql - update', function() { + Update.forEach(function(test) { + testBackAndForth(test.c, test.q); }); +}); - test('ast2sql - delete', function() { - Delete.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); +test('ast2sql - delete', function() { + Delete.forEach(function(test) { + testBackAndForth(test.c, test.q); }); - -})(); +}); From addfecd48e295f6c194082b6a73efeb7018e45b1 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Sat, 12 Jul 2014 21:15:20 +0200 Subject: [PATCH 43/75] Update dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 11ac27d..77046d2 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ }, "devDependencies": { "chai": "1.9.1", - "gulp": "3.8.5", - "gulp-jshint": "1.6.4", + "gulp": "3.8.6", + "gulp-jshint": "1.7.1", "gulp-mocha": "0.5.1" } } From 90e7a44874b77f166a52da8a4aa75a2f1069e316 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Sat, 12 Jul 2014 21:23:06 +0200 Subject: [PATCH 44/75] Update README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index e521461..e7a3d28 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,21 @@ Javascript library to parse CRUD (Create Retrieve Update Delete) SQL queries. **This is a work in progress!** +## How to install dev tools + +If you want to contribute, please write tests and respect the coding style. + +To install dev tools: + +- install Node.js (http://nodejs.org/) +- install **gulp** globally: `npm i -g gulp` +- install dev dependencies: `npm i` +- use `gulp` to check your code! + +To update dev tools (because `package.json` has changed): + +- install new dev dependencies: `npm i` + ## License The MIT License (MIT) From 4d7db7da2f67c40b2599fd7962d179a206ac20b4 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Sat, 12 Jul 2014 22:53:04 +0200 Subject: [PATCH 45/75] Split tests into several files --- gulpfile.js | 2 +- tests/helpers.js | 35 ++++ tests/tests-api.js | 48 +++++ tests/tests-delete.js | 69 +++++++ tests/tests-insert.js | 71 ++++++++ tests/{tests.js => tests-select.js} | 268 +--------------------------- tests/tests-update.js | 72 ++++++++ 7 files changed, 299 insertions(+), 266 deletions(-) create mode 100644 tests/helpers.js create mode 100644 tests/tests-api.js create mode 100644 tests/tests-delete.js create mode 100644 tests/tests-insert.js rename tests/{tests.js => tests-select.js} (60%) create mode 100644 tests/tests-update.js diff --git a/gulpfile.js b/gulpfile.js index da42acb..cc7c5f9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,7 +10,7 @@ gulp.task('lint', function() { }); gulp.task('test', function() { - return gulp.src('./tests/tests.js') + return gulp.src('./tests/test*.js') .pipe(mocha({ ui: 'qunit', reporter: 'spec', diff --git a/tests/helpers.js b/tests/helpers.js new file mode 100644 index 0000000..9da9c0c --- /dev/null +++ b/tests/helpers.js @@ -0,0 +1,35 @@ +"use strict"; + +var expect = require('chai').expect; +var m = require('../simpleSqlParser'); + +function ok(ast) { + return { + status: true, + value: ast + }; +} + +function testAst(comment, query, ast) { + expect(m.sql2ast(query)).to.eql(ok(ast), comment + ': ' + query); +} + +function testBackAndForth(comment, query) { + expect(m.ast2sql(m.sql2ast(query))).to.eql(query, comment + ': ' + query); +} + +function isArray(variable, message) { + expect(variable).to.be.instanceOf(Array, message); +} + +function isObject(variable, message) { + expect(variable).not.to.be.instanceOf(Array, message); +} + +module.exports = { + ok: ok, + testAst: testAst, + testBackAndForth: testBackAndForth, + isArray: isArray, + isObject: isObject, +}; diff --git a/tests/tests-api.js b/tests/tests-api.js new file mode 100644 index 0000000..39c8490 --- /dev/null +++ b/tests/tests-api.js @@ -0,0 +1,48 @@ +/*global test:true*/ +"use strict"; + +var expect = require('chai').expect; +var m = require('../simpleSqlParser'); +var h = require('./helpers.js'); + +test('sql2ast - API', function() { + + var q = [ + 'SELECT * FROM table', + 'INSERT INTO table VALUES (1, 2, 3)', + 'UPDATE table SET col = "value"', + 'DELETE FROM table', + ]; + var ast = q.map(m.sql2ast); + + var types = ['select', 'delete', 'insert', 'update']; + + ast.forEach(function(a) { + expect(a.status).to.equal(true, 'Parser must parse valid SQL'); + expect(types.indexOf(a.value.type)).not.to.equal(-1, 'AST must contain a valid type'); + + if (a.value.type === types[0]) { + h.isArray(a.value.select, "(SELECT) AST must contain a 'select' array"); + h.isArray(a.value.from, "(SELECT) AST must contain a 'from' array"); + h.isArray(a.value.join, "(SELECT) AST must contain a 'join' array"); + h.isObject(a.value.where, "(SELECT) AST must contain a 'where' object"); + h.isArray(a.value.group, "(SELECT) AST must contain a 'group' array"); + h.isArray(a.value.order, "(SELECT) AST must contain a 'order' array"); + h.isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object"); + } + else if (a.value.type === types[1]) { + h.isArray(a.value.from, "(DELETE) AST must contain a 'from' array"); + h.isObject(a.value.where, "(DELETE) AST must contain a 'where' object"); + } + else if (a.value.type === types[2]) { + h.isObject(a.value.into, "(INSERT) AST must contain a 'into' object"); + h.isArray(a.value.values, "(INSERT) AST must contain a 'values' array"); + } + else if (a.value.type === types[3]) { + h.isObject(a.value.table, "(UPDATE) AST must contain a 'table' object"); + h.isArray(a.value.values, "(UPDATE) AST must contain a 'values' array"); + h.isObject(a.value.where, "(UPDATE) AST must contain a 'where' object"); + } + }); + +}); diff --git a/tests/tests-delete.js b/tests/tests-delete.js new file mode 100644 index 0000000..3331877 --- /dev/null +++ b/tests/tests-delete.js @@ -0,0 +1,69 @@ +/*global test:true*/ +"use strict"; + +var expect = require('chai').expect; +var h = require('./helpers.js'); + +var Delete = [ + { + c: 'Simple delete', + q: 'DELETE FROM table', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: null, + }, + }, + { + c: 'Several tables with aliases', + q: 'DELETE FROM table1 AS t1, table2 "t2"', + a: { + type: 'delete', + from: [ + { expression: 'table1 AS t1', table: 'table1', alias: 't1' }, + { expression: 'table2 "t2"', table: 'table2', alias: 't2' }, + ], + where: null, + }, + }, + { + c: 'Where #1', + q: 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: { + expression: "this >= that AND col IS NOT NULL", + }, + }, + }, + { + c: 'Where #2', + q: 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', + a: { + type: 'delete', + from: [ + { expression: 'table', table: 'table', alias: null }, + ], + where: { + expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", + }, + }, + }, +]; + +test('sql2ast - delete', function() { + Delete.forEach(function(test) { + h.testAst(test.c, test.q, test.a); + }); +}); + +test('ast2sql - delete', function() { + Delete.forEach(function(test) { + h.testBackAndForth(test.c, test.q); + }); +}); diff --git a/tests/tests-insert.js b/tests/tests-insert.js new file mode 100644 index 0000000..a00535b --- /dev/null +++ b/tests/tests-insert.js @@ -0,0 +1,71 @@ +/*global test:true*/ +"use strict"; + +var expect = require('chai').expect; +var h = require('./helpers.js'); + +var Insert = [ + { + c: 'Simple insert', + q: 'INSERT INTO table VALUES (1, 2, 3)', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, + }, + values: [ + { target: null, value: '1'}, + { target: null, value: '2'}, + { target: null, value: '3'}, + ], + }, + }, + { + c: 'Complex values', + q: 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, + }, + values: [ + { target: null, value: '1 + 9'}, + { target: null, value: 'FUNC(2, col)'}, + { target: null, value: '"string"'}, + ], + }, + }, + { + c: 'Insert with columns', + q: 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', + a: { + type: 'insert', + into: { + expression: 'table', + table: 'table', + alias: null, + }, + values: [ + { target: { expression: 'col1', column: 'col1' }, value: '1'}, + { target: { expression: '`col2`', column: 'col2' }, value: '2'}, + { target: { expression: 'col3', column: 'col3' }, value: '3'}, + ], + }, + }, +]; + +test('sql2ast - insert', function() { + Insert.forEach(function(test) { + h.testAst(test.c, test.q, test.a); + }); +}); + +test('ast2sql - insert', function() { + Insert.forEach(function(test) { + h.testBackAndForth(test.c, test.q); + }); +}); diff --git a/tests/tests.js b/tests/tests-select.js similarity index 60% rename from tests/tests.js rename to tests/tests-select.js index d50c021..9053701 100644 --- a/tests/tests.js +++ b/tests/tests-select.js @@ -2,72 +2,7 @@ "use strict"; var expect = require('chai').expect; -var m = require('../simpleSqlParser'); - -function ok(ast) { - return { - status: true, - value: ast - }; -} - -function testAst(comment, query, ast) { - expect(m.sql2ast(query)).to.eql(ok(ast), comment + ': ' + query); -} - -function testBackAndForth(comment, query) { - expect(m.ast2sql(m.sql2ast(query))).to.eql(query, comment + ': ' + query); -} - -function isArray(variable, message) { - expect(variable).to.be.instanceOf(Array, message); -} - -function isObject(variable, message) { - expect(variable).not.to.be.instanceOf(Array, message); -} - -test('sql2ast - API', function() { - - var q = [ - 'SELECT * FROM table', - 'INSERT INTO table VALUES (1, 2, 3)', - 'UPDATE table SET col = "value"', - 'DELETE FROM table', - ]; - var ast = q.map(m.sql2ast); - - var types = ['select', 'delete', 'insert', 'update']; - - ast.forEach(function(a) { - expect(a.status).to.equal(true, 'Parser must parse valid SQL'); - expect(types.indexOf(a.value.type)).not.to.equal(-1, 'AST must contain a valid type'); - - if (a.value.type === types[0]) { - isArray(a.value.select, "(SELECT) AST must contain a 'select' array"); - isArray(a.value.from, "(SELECT) AST must contain a 'from' array"); - isArray(a.value.join, "(SELECT) AST must contain a 'join' array"); - isObject(a.value.where, "(SELECT) AST must contain a 'where' object"); - isArray(a.value.group, "(SELECT) AST must contain a 'group' array"); - isArray(a.value.order, "(SELECT) AST must contain a 'order' array"); - isObject(a.value.limit, "(SELECT) AST must contain a 'limit' object"); - } - else if (a.value.type === types[1]) { - isArray(a.value.from, "(DELETE) AST must contain a 'from' array"); - isObject(a.value.where, "(DELETE) AST must contain a 'where' object"); - } - else if (a.value.type === types[2]) { - isObject(a.value.into, "(INSERT) AST must contain a 'into' object"); - isArray(a.value.values, "(INSERT) AST must contain a 'values' array"); - } - else if (a.value.type === types[3]) { - isObject(a.value.table, "(UPDATE) AST must contain a 'table' object"); - isArray(a.value.values, "(UPDATE) AST must contain a 'values' array"); - isObject(a.value.where, "(UPDATE) AST must contain a 'where' object"); - } - }); - -}); +var h = require('./helpers.js'); var Select = [ { @@ -465,211 +400,14 @@ var Select = [ }, ]; -var Insert = [ - { - c: 'Simple insert', - q: 'INSERT INTO table VALUES (1, 2, 3)', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: null, value: '1'}, - { target: null, value: '2'}, - { target: null, value: '3'}, - ], - }, - }, - { - c: 'Complex values', - q: 'INSERT INTO table VALUES (1 + 9, FUNC(2, col), "string")', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: null, value: '1 + 9'}, - { target: null, value: 'FUNC(2, col)'}, - { target: null, value: '"string"'}, - ], - }, - }, - { - c: 'Insert with columns', - q: 'INSERT INTO table (col1, `col2`, col3) VALUES (1, 2, 3)', - a: { - type: 'insert', - into: { - expression: 'table', - table: 'table', - alias: null, - }, - values: [ - { target: { expression: 'col1', column: 'col1' }, value: '1'}, - { target: { expression: '`col2`', column: 'col2' }, value: '2'}, - { target: { expression: 'col3', column: 'col3' }, value: '3'}, - ], - }, - }, -]; - -var Update = [ - { - c: 'Simple update', - q: 'UPDATE table SET col = "value"', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: null, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - ], - }, - }, - { - c: 'Several columns', - q: 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: null, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - { target: { expression: 'col2', column: 'col2' }, value: 'NULL'}, - { target: { expression: 'table.col3', column: 'col3' }, value: 'col'}, - ], - }, - }, - { - c: 'Where #1', - q: 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', - a: { - type: 'update', - table: { - expression: 'table', - table: 'table', - alias: null, - }, - where: { - expression: "this >= that AND col IS NOT NULL", - }, - values: [ - { target: { expression: 'col', column: 'col' }, value: '"value"'}, - ], - }, - }, -]; - -var Delete = [ - { - c: 'Simple delete', - q: 'DELETE FROM table', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: null, - }, - }, - { - c: 'Several tables with aliases', - q: 'DELETE FROM table1 AS t1, table2 "t2"', - a: { - type: 'delete', - from: [ - { expression: 'table1 AS t1', table: 'table1', alias: 't1' }, - { expression: 'table2 "t2"', table: 'table2', alias: 't2' }, - ], - where: null, - }, - }, - { - c: 'Where #1', - q: 'DELETE FROM table WHERE this >= that AND col IS NOT NULL', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: { - expression: "this >= that AND col IS NOT NULL", - }, - }, - }, - { - c: 'Where #2', - q: 'DELETE FROM table WHERE (FUNC(this) = "string") AND (1+5 OR col1)', - a: { - type: 'delete', - from: [ - { expression: 'table', table: 'table', alias: null }, - ], - where: { - expression: "(FUNC(this) = \"string\") AND (1+5 OR col1)", - }, - }, - }, -]; - test('sql2ast - select', function() { Select.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); -}); - -test('sql2ast - insert', function() { - Insert.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); -}); - -test('sql2ast - update', function() { - Update.forEach(function(test) { - testAst(test.c, test.q, test.a); - }); -}); - -test('sql2ast - delete', function() { - Delete.forEach(function(test) { - testAst(test.c, test.q, test.a); + h.testAst(test.c, test.q, test.a); }); }); test('ast2sql - select', function() { Select.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); -}); - -test('ast2sql - insert', function() { - Insert.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); -}); - -test('ast2sql - update', function() { - Update.forEach(function(test) { - testBackAndForth(test.c, test.q); - }); -}); - -test('ast2sql - delete', function() { - Delete.forEach(function(test) { - testBackAndForth(test.c, test.q); + h.testBackAndForth(test.c, test.q); }); }); diff --git a/tests/tests-update.js b/tests/tests-update.js new file mode 100644 index 0000000..2a0cc38 --- /dev/null +++ b/tests/tests-update.js @@ -0,0 +1,72 @@ +/*global test:true*/ +"use strict"; + +var expect = require('chai').expect; +var h = require('./helpers.js'); + +var Update = [ + { + c: 'Simple update', + q: 'UPDATE table SET col = "value"', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, + }, + where: null, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + ], + }, + }, + { + c: 'Several columns', + q: 'UPDATE table SET col = "value", col2 = NULL, table.col3 = col', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, + }, + where: null, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + { target: { expression: 'col2', column: 'col2' }, value: 'NULL'}, + { target: { expression: 'table.col3', column: 'col3' }, value: 'col'}, + ], + }, + }, + { + c: 'Where #1', + q: 'UPDATE table SET col = "value" WHERE this >= that AND col IS NOT NULL', + a: { + type: 'update', + table: { + expression: 'table', + table: 'table', + alias: null, + }, + where: { + expression: "this >= that AND col IS NOT NULL", + }, + values: [ + { target: { expression: 'col', column: 'col' }, value: '"value"'}, + ], + }, + }, +]; + +test('sql2ast - update', function() { + Update.forEach(function(test) { + h.testAst(test.c, test.q, test.a); + }); +}); + +test('ast2sql - update', function() { + Update.forEach(function(test) { + h.testBackAndForth(test.c, test.q); + }); +}); From f313f933df714cdbb36190a57eed91c0ee888dc6 Mon Sep 17 00:00:00 2001 From: David Sferruzza Date: Sat, 12 Jul 2014 23:54:25 +0200 Subject: [PATCH 46/75] Use CommonJS + Browserify --- demo.html | 3 +- dist/simpleSqlParser.js | 1282 +++++++++++++++++++++++++++++++++++++++ gulpfile.js | 12 +- package.json | 4 +- simpleSqlParser.js | 1162 ++++++++++++++++++----------------- 5 files changed, 1877 insertions(+), 586 deletions(-) create mode 100644 dist/simpleSqlParser.js diff --git a/demo.html b/demo.html index 37d3375..0a4b099 100644 --- a/demo.html +++ b/demo.html @@ -15,8 +15,7 @@

AST


 
-	
-	
+	
 	
+