From e55cb2f8784902653e351b67702e8f46d060c4f4 Mon Sep 17 00:00:00 2001 From: Paul Winkler Date: Thu, 7 Mar 2013 00:09:13 +0000 Subject: [PATCH] add basic mysql support --- lib/dialect/mysql.js | 338 +++++++++++++++++++++++++++++++++++++++++++ lib/index.js | 54 +++---- lib/table.js | 4 +- 3 files changed, 369 insertions(+), 27 deletions(-) create mode 100644 lib/dialect/mysql.js diff --git a/lib/dialect/mysql.js b/lib/dialect/mysql.js new file mode 100644 index 0000000..f06cd94 --- /dev/null +++ b/lib/dialect/mysql.js @@ -0,0 +1,338 @@ +'use strict'; + +var util = require('util'); +var assert = require('assert'); +var From = require(__dirname + '/../node/from'); +var Parameter = require(__dirname + '/../node/parameter'); +var Mysql = function() { + this.output = []; + this.params = []; +}; + +Mysql.prototype.getQuery = function(queryNode) { + this.visitQuery(queryNode); + return { text: this.output.join(' '), values: this.params }; +}; + + +Mysql.prototype.visit = function(node) { + switch(node.type) { + case 'QUERY': return this.visitQuery(node); + case 'SUBQUERY': return this.visitSubquery(node); + case 'SELECT': return this.visitSelect(node); + case 'INSERT': return this.visitInsert(node); + case 'UPDATE': return this.visitUpdate(node); + case 'DELETE': return this.visitDelete(); + case 'CREATE': return this.visitCreate(node); + case 'DROP': return this.visitDrop(node); + case 'ALTER': return this.visitAlter(node); + case 'FROM': return this.visitFrom(node); + case 'WHERE': return this.visitWhere(node); + case 'ORDER BY': return this.visitOrderBy(node); + case 'GROUP BY': return this.visitGroupBy(node); + case 'RETURNING': return this.visitReturning(node); + case 'BINARY': return this.visitBinary(node); + case 'TABLE': return this.visitTable(node); + case 'COLUMN': return this.visitColumn(node); + case 'JOIN': return this.visitJoin(node); + case 'TEXT': return node.text; + case 'UNARY': return this.visitUnary(node); + case 'PARAMETER': return this.visitParameter(node); + case 'DEFAULT': return this.visitDefault(node); + case 'IF EXISTS': return this.visitIfExists(); + case 'IF NOT EXISTS': return this.visitIfNotExists(); + case 'ADD COLUMN': return this.visitAddColumn(node); + case 'DROP COLUMN': return this.visitDropColumn(node); + case 'RENAME COLUMN': return this.visitRenameColumn(node); + case 'LIMIT': + case 'OFFSET': + return this.visitModifier(node); + default: throw new Error("Unrecognized node type " + node.type); + } +}; + +Mysql.prototype.quote = function(word) { + return '`' + word + '`'; +}; + +Mysql.prototype.visitSelect = function(select) { + var result = ['SELECT', select.nodes.map(this.visit.bind(this)).join(', ')]; + this._selectOrDeleteEndIndex = this.output.length + result.length; + return result; +}; + +Mysql.prototype.visitInsert = function(insert) { + var self = this; + this._visitedFrom = true; + //don't use table.column for inserts + this._visitedInsert = true; + + var paramNodes = insert.getParameters() + .map(function (paramSet) { + return paramSet.map(function (param) { + return self.visit(param); + }).join(', '); + }).map(function (param) { + return '('+param+')'; + }).join(', '); + + var result = [ + 'INSERT INTO', + this.visit(this._queryNode.table.toNode()), + '(' + insert.columns.map(this.visit.bind(this)).join(', ') + ')', + 'VALUES', paramNodes + ]; + return result; +}; + +Mysql.prototype.visitUpdate = function(update) { + //don't auto-generate from clause + this._visitedFrom = true; + var params = []; + /*jshint boss: true */ + for(var i = 0, node; node = update.nodes[i]; i++) { + this._visitingUpdateTargetColumn = true; + var target_col = this.visit(node); + this._visitingUpdateTargetColumn = false; + params = params.concat(target_col + ' = ' + this.visit(node.value)); + } + var result = [ + 'UPDATE', + this.visit(this._queryNode.table.toNode()), + 'SET', + params.join(', ') + ]; + return result; +}; + +Mysql.prototype.visitDelete = function() { + this._selectOrDeleteEndIndex = 1; + return ['DELETE']; +}; + +Mysql.prototype.visitCreate = function(create) { + this._visitingCreate = true; + //don't auto-generate from clause + this._visitedFrom = true; + var table = this._queryNode.table; + var col_nodes = table.columns.map(function(col) { return col.toNode(); }); + + var result = ['CREATE TABLE']; + result = result.concat(create.nodes.map(this.visit.bind(this))); + result.push(this.visit(table.toNode())); + result.push('(' + col_nodes.map(this.visit.bind(this)).join(', ') + ')'); + this._visitingCreate = false; + return result; +}; + +Mysql.prototype.visitDrop = function(drop) { + //don't auto-generate from clause + this._visitedFrom = true; + var result = ['DROP TABLE']; + result = result.concat(drop.nodes.map(this.visit.bind(this))); + result.push(this.visit(this._queryNode.table.toNode())); + return result; +}; + +Mysql.prototype.visitAlter = function(alter) { + this._visitingAlter = true; + //don't auto-generate from clause + this._visitedFrom = true; + var table = this._queryNode.table; + var col_nodes = table.columns.map(function(col) { return col.toNode(); }); + var result = [ + 'ALTER TABLE', + this.visit(table.toNode()), + alter.nodes.map(this.visit.bind(this)).join(', ') + ]; + this._visitingAlter = false; + return result; +}; + +Mysql.prototype.visitFrom = function(from) { + this._visitedFrom = true; + var result = []; + result.push('FROM'); + for(var i = 0; i < from.nodes.length; i++) { + result = result.concat(this.visit(from.nodes[i])); + } + return result; +}; + +Mysql.prototype.visitWhere = function(where) { + var result = ['WHERE', where.nodes.map(this.visit.bind(this)).join(', ')]; + return result; +}; + +Mysql.prototype.visitOrderBy = function(orderBy) { + var result = ['ORDER BY', orderBy.nodes.map(this.visit.bind(this)).join(', ')]; + return result; +}; + +Mysql.prototype.visitGroupBy = function(groupBy) { + var result = ['GROUP BY', groupBy.nodes.map(this.visit.bind(this)).join(', ')]; + return result; +}; + +Mysql.prototype.visitBinary = function(binary) { + var self = this; + var result = '(' + this.visit(binary.left) + ' ' + binary.operator + ' '; + if (Array.isArray(binary.right)) { + result += '(' + binary.right.map(function (node) { + return self.visit(node); + }).join(', ') + ')'; + } + else { + result += this.visit(binary.right); + } + result += ')'; + return result; +}; + +Mysql.prototype.visitUnary = function(unary) { + return '(' + this.visit(unary.left) + ' ' + unary.operator + ')'; +}; + +Mysql.prototype.visitQuery = function(queryNode) { + this._queryNode = queryNode; + for(var i = 0; i < queryNode.nodes.length; i ++) { + var res = this.visit(queryNode.nodes[i]); + this.output = this.output.concat(res); + } + //implicit 'from' + if(!this._visitedFrom) { + var select = this.output.slice(0, this._selectOrDeleteEndIndex); + var from = this.visitFrom(new From().add(queryNode.table.toNode())); + var rest = this.output.slice(this._selectOrDeleteEndIndex); + this.output = select.concat(from).concat(rest); + } + return this; +}; + +Mysql.prototype.visitSubquery = function(queryNode) { + var result = []; + for(var i = 0; i < queryNode.nodes.length; i ++) { + var res = this.visit(queryNode.nodes[i]); + result = result.concat(res); + } + //implicit 'from' + if(!this._visitedFrom) { + var select = result.slice(0, this._selectOrDeleteEndIndex); + var from = this.visitFrom(new From().add(queryNode.table.toNode())); + var rest = result.slice(this._selectOrDeleteEndIndex); + result = select.concat(from).concat(rest); + } + result[0] = '('+result[0]; + result[result.length-1] = result[result.length-1] + ') ' + queryNode.alias; + return result; +}; + +Mysql.prototype.visitTable = function(tableNode) { + var table = tableNode.table; + var txt=""; + if(table.getSchema()) { + txt = this.quote(table.getSchema()); + txt += '.'; + } + txt += this.quote(table.getName()); + if(table.alias) { + txt += ' AS ' + table.alias; + } + return txt; +}; + +Mysql.prototype.visitColumn = function(columnNode) { + var table = columnNode.table; + var inSelectClause = !this._selectOrDeleteEndIndex; + var txt = ""; + if(inSelectClause) { + if (columnNode.asArray) { + txt += 'array_agg('; + } else if (columnNode.aggCount) { + txt += 'COUNT('; + } + } + if(!this._visitedInsert && !this._visitingUpdateTargetColumn && !this._visitingCreate && !this._visitingAlter) { + if(table.alias) { + txt = table.alias; + } else { + if(table.getSchema()) { + txt = this.quote(table.getSchema()); + txt += '.'; + } + txt += this.quote(table.getName()); + } + txt += '.'; + } + if (columnNode.star) { + txt += '*'; + } else { + txt += this.quote(columnNode.name); + } + if(inSelectClause && (columnNode.asArray || columnNode.aggCount)) { + txt += ')'; + } + if(inSelectClause && columnNode.alias) { + txt += ' as ' + this.quote(columnNode.alias); + } + if(this._visitingCreate || this._visitingAddColumn) { + assert(columnNode.dataType, 'dataType missing for column ' + columnNode.name + + ' (CREATE TABLE and ADD COLUMN statements require a dataType)'); + txt += ' ' + columnNode.dataType; + } + return txt; +}; + +Mysql.prototype.visitParameter = function(parameter) { + this.params.push(parameter.value()); + return "?"; +}; + +Mysql.prototype.visitDefault = function(parameter) { + var params = this.params; + this.params.push('DEFAULT'); + return "?"; +}; + +Mysql.prototype.visitAddColumn = function(addColumn) { + this._visitingAddColumn = true; + var result = ['ADD COLUMN ' + this.visit(addColumn.nodes[0])]; + this._visitingAddColumn = false; + return result; +}; + +Mysql.prototype.visitDropColumn = function(dropColumn) { + return ['DROP COLUMN ' + this.visit(dropColumn.nodes[0])]; +}; + +Mysql.prototype.visitRenameColumn = function(renameColumn) { + return ['RENAME COLUMN ' + this.visit(renameColumn.nodes[0]) + ' TO ' + this.visit(renameColumn.nodes[1])]; +}; + +Mysql.prototype.visitIfExists = function() { + return ['IF EXISTS']; +}; + +Mysql.prototype.visitIfNotExists = function() { + return ['IF NOT EXISTS']; +}; + +Mysql.prototype.visitJoin = function(join) { + var result = []; + result = result.concat(this.visit(join.from)); + result = result.concat(join.subType + ' JOIN'); + result = result.concat(this.visit(join.to)); + result = result.concat('ON'); + result = result.concat(this.visit(join.on)); + return result; +}; + +Mysql.prototype.visitReturning = function(returning) { + return ['RETURNING', returning.nodes.map(this.visit.bind(this)).join(', ')]; +}; + +Mysql.prototype.visitModifier = function(node) { + return [node.type, node.count]; +}; + +module.exports = Mysql; diff --git a/lib/index.js b/lib/index.js index ae9ac34..6873185 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,26 +1,28 @@ -'use strict'; - -var Table = require(__dirname + '/table'); - -var sql = { - Table: Table, - define: Table.define, - select: function() { - var Query = require(__dirname + '/node/query'); - var query = new Query(); - query.select.apply(query, arguments); - return query; - }, - setDialect: function(dialect) { - switch(dialect.toLowerCase()) { - case 'postgres': - this.dialect = require(__dirname + '/dialect/postgres'); - break; - default: - throw new Error(dialect + ' is unsupported'); - } - return this; - } -}; - -module.exports = sql.setDialect('postgres'); +'use strict'; + +var Table = require(__dirname + '/table'); + +var sql = { + Table: Table, + define: Table.define, + select: function() { + var Query = require(__dirname + '/node/query'); + var query = new Query(); + query.select.apply(query, arguments); + return query; + }, + setDialect: function(dialect) { + switch(dialect.toLowerCase()) { + case 'postgres': + this.dialect = require(__dirname + '/dialect/postgres'); + case 'mysql': + this.dialect = require(__dirname + '/dialect/mysql'); + break; + default: + throw new Error(dialect + ' is unsupported'); + } + return this; + } +}; + +module.exports = sql.setDialect('postgres'); diff --git a/lib/table.js b/lib/table.js index 821d9f5..2a4100d 100644 --- a/lib/table.js +++ b/lib/table.js @@ -47,7 +47,9 @@ Table.prototype.getName = function() { Table.prototype.star = function() { var name = this.alias || this._name; - return new TextNode('"'+name+'".*'); + var Dialect = require(__dirname + '/../').dialect; + var dialect = new Dialect(); + return new TextNode(dialect.quote(name)+'.*'); }; Table.prototype.count = function(alias) {