From 0a57142edf1e007d5988250426ebc6c3044a7052 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 15 Jul 2014 15:26:59 -0400 Subject: [PATCH 001/200] Added history file and added ability to do SELECT DISTINCT queries. --- HISTORY.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 HISTORY.md diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..cd12d6f --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,11 @@ +# Changes + +This file is a manually maintained list of changes for each release. Feel free +to add your changes here when sending pull requests. Also send corrections if +you spot any mistakes. + +## v0.9.0 (2014-07-15) + +* Added this history file +* Added the ability to do `SELECT DISTINCT` queries +* Added the ability to do `LIKE` statements directly From 28eecca27fd300bf70ce8fbf253bb56b2ee2c198 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 14:52:20 -0400 Subject: [PATCH 002/200] Added history file. --- HISTORY.md | 3 +-- index.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) mode change 100644 => 100755 HISTORY.md diff --git a/HISTORY.md b/HISTORY.md old mode 100644 new mode 100755 index cd12d6f..3895f2a --- a/HISTORY.md +++ b/HISTORY.md @@ -7,5 +7,4 @@ you spot any mistakes. ## v0.9.0 (2014-07-15) * Added this history file -* Added the ability to do `SELECT DISTINCT` queries -* Added the ability to do `LIKE` statements directly +* Added the ability to do `SELECT DISTINCT` queries \ No newline at end of file diff --git a/index.js b/index.js index cfa7b1a..0e6ba28 100755 --- a/index.js +++ b/index.js @@ -540,4 +540,4 @@ var Pool = function (settings) { }; exports.Adapter = Adapter; -exports.Pool = Pool; +exports.Pool = Pool; \ No newline at end of file From 6f3e8ef7c473bbf257cd2257755cf0c4a349963c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 14:56:38 -0400 Subject: [PATCH 003/200] Added ability to do DISTINCT statements. --- index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 0e6ba28..ae3c918 100755 --- a/index.js +++ b/index.js @@ -84,6 +84,7 @@ var Adapter = function(settings) { offsetClause = -1, joinClause = [], lastQuery = ''; + distinctClause = '', var resetQuery = function(newLastQuery) { whereClause = {}; @@ -95,6 +96,7 @@ var Adapter = function(settings) { offsetClause = -1; joinClause = []; lastQuery = (typeof newLastQuery === 'string' ? newLastQuery : ''); + distinctClause = ''; rawWhereClause = {}; rawWhereString = {}; }; @@ -218,7 +220,7 @@ var Adapter = function(settings) { this.count = function(tableName, responseCallback) { if (typeof tableName === 'string') { - var combinedQueryString = 'SELECT COUNT(*) as count FROM ' + escapeFieldName(tableName) + var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count FROM ' + protectIdentifiers(tableName) + buildJoinString() + buildDataString(whereClause, ' AND ', 'WHERE'); @@ -259,6 +261,11 @@ var Adapter = function(settings) { } return that; }; + + this.distinct = function() { + distinctClause = 'DISTINCT '; + return that; + }; this.comma_separated_arguments = function(set) { var clause = ''; @@ -374,7 +381,7 @@ var Adapter = function(settings) { this.get = function(tableName, responseCallback) { if (typeof tableName === 'string') { - var combinedQueryString = 'SELECT ' + (selectClause.length === 0 ? '*' : selectClause.join(',')) + var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) + ' FROM ' + escapeFieldName(tableName) + buildJoinString() + buildDataString(whereClause, ' AND ', 'WHERE') From e6f33de71bf1a9ed5c1691f14d70b0e3a51ea867 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 15:18:59 -0400 Subject: [PATCH 004/200] Added the ability to handle aliases and we're now escaping identifiers better. --- HISTORY.md | 4 +- index.js | 140 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3895f2a..9f47500 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,4 +7,6 @@ you spot any mistakes. ## v0.9.0 (2014-07-15) * Added this history file -* Added the ability to do `SELECT DISTINCT` queries \ No newline at end of file +* Added the ability to do `SELECT DISTINCT` queries +* We're doing better escaping of identifiers now +* Added the ability to use table/view prefixes directly (ex. `select * from users u`) \ No newline at end of file diff --git a/index.js b/index.js index ae3c918..64eb981 100755 --- a/index.js +++ b/index.js @@ -85,6 +85,7 @@ var Adapter = function(settings) { joinClause = [], lastQuery = ''; distinctClause = '', + aliasedTables = [] var resetQuery = function(newLastQuery) { whereClause = {}; @@ -99,13 +100,128 @@ var Adapter = function(settings) { distinctClause = ''; rawWhereClause = {}; rawWhereString = {}; + aliasedTables = []; }; var rawWhereClause = {}; var rawWhereString = {}; - var escapeFieldName = function(str) { - return (typeof rawWhereString[str] === 'undefined' && typeof rawWhereClause[str] === 'undefined' ? '`' + str.replace('.','`.`') + '`' : str); + var trackAliases = function(table) { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (var i in table) { + var t = table[i]; + track_aliases(t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return track_aliases(table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + alias = table.slice(table.lastIndexOf(' ')).trim(); + + // Store the alias, if it doesn't already exist + if(aliasedTables.indexOf(table) == -1) { + aliasedTables.push(table); + } + } + } + + var escapeIdentifiers = function(item) { + if (!item || item === '*') { + return item; + } + + var str; + if (item.indexOf('.') !== -1) { + str = '`' + item.replace(/\./g,'`.`') + '`'; + } + else { + str = '`' + item + '`'; + } + + // remove duplicates if the user already included the escape + return str.replace(/[`]+/,'`'); + } + + var protectIdentifiers = function(item,escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + var escaped_array = {}; + + for (k in item) { + var v = item[k]; + escaped_array[protectIdentifiers(k)] = protectIdentifiers(v); + } + + return escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + item = item.replace(/\s+/, ' '); + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (item.indexOf(' ') !== -1) { + var alias_index = item.lastIndexOf(' '); + var alias = item.slice(alias_index); + item = item.slice(0,alias_index); + } + else { + alias = ''; + } + + // This is basically a bug fix for queries that use MAX, MIN, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1) { + return item + alias; + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + parts = item.split('.'); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (aliasedTables.indexOf(parts[0]) !== -1) { + if (escape === true) { + for (var key in parts) { + var val = parts[key]; + if (val !== '*') { + parts[key] = escapeIdentifiers(val); + } + } + + item = parts.join('.'); + } + return item + alias; + } + + if (escape === true) { + item = escapeIdentifiers(item); + } + + return item + alias; + } + if (escape === true) { + item = escapeIdentifiers(item); + } + + return item + alias; }; var buildDataString = function(dataSet, separator, clause) { @@ -128,13 +244,13 @@ var Adapter = function(settings) { queryString += key; } else if (dataSet[key] === null) { - queryString += escapeFieldName(key) + (clause == 'WHERE' ? " is NULL" : "=NULL"); + queryString += protectIdentifiers(key) + (clause == 'WHERE' ? " is NULL" : "=NULL"); } else if (typeof dataSet[key] !== 'object') { - queryString += escapeFieldName(key) + "=" + connection.escape(dataSet[key]); + queryString += protectIdentifiers(key) + "=" + connection.escape(dataSet[key]); } else if (typeof dataSet[key] === 'object' && Object.prototype.toString.call(dataSet[key]) === '[object Array]' && dataSet[key].length > 0) { - queryString += escapeFieldName(key) + ' in ("' + dataSet[key].join('", "') + '")'; + queryString += protectIdentifiers(key) + ' in ("' + dataSet[key].join('", "') + '")'; } else { useSeparator = false; @@ -157,7 +273,7 @@ var Adapter = function(settings) { var joinString = ''; for (var i = 0; i < joinClause.length; i++) { - joinString += (joinClause[i].direction !== '' ? ' ' + joinClause[i].direction : '') + ' JOIN ' + escapeFieldName(joinClause[i].table) + ' ON ' + joinClause[i].relation; + joinString += (joinClause[i].direction !== '' ? ' ' + joinClause[i].direction : '') + ' JOIN ' + protectIdentifiers(joinClause[i].table) + ' ON ' + joinClause[i].relation; } return joinString; @@ -321,7 +437,7 @@ var Adapter = function(settings) { } if (typeof tableName === 'string') { - var combinedQueryString = verb + ' into ' + escapeFieldName(tableName) + var combinedQueryString = verb + ' into ' + protectIdentifiers(tableName) + buildDataString(dataSet, ', ', 'SET'); if (querySuffix != '') { @@ -355,7 +471,7 @@ var Adapter = function(settings) { for (var key in dataSet[0]) { if (dataSet[0].hasOwnProperty(key)) { if (columns.indexOf(key) == -1) { - columns.push(escapeFieldName(key)); + columns.push(protectIdentifiers(key)); } } } @@ -375,14 +491,14 @@ var Adapter = function(settings) { })(i); } - that.query(verb + ' INTO ' + escapeFieldName(tableName) + ' (' + columns.join(', ') + ') VALUES' + map.join(','), responseCallback); + that.query(verb + ' INTO ' + protectIdentifiers(tableName) + ' (' + columns.join(', ') + ') VALUES' + map.join(','), responseCallback); return that; }; this.get = function(tableName, responseCallback) { if (typeof tableName === 'string') { var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) - + ' FROM ' + escapeFieldName(tableName) + + ' FROM ' + protectIdentifiers(tableName) + buildJoinString() + buildDataString(whereClause, ' AND ', 'WHERE') + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') @@ -400,7 +516,7 @@ var Adapter = function(settings) { this.update = function(tableName, newData, responseCallback) { if (typeof tableName === 'string') { - var combinedQueryString = 'UPDATE ' + escapeFieldName(tableName) + var combinedQueryString = 'UPDATE ' + protectIdentifiers(tableName) + buildDataString(newData, ', ', 'SET') + buildDataString(whereClause, ' AND ', 'WHERE') + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); @@ -418,7 +534,7 @@ var Adapter = function(settings) { this.delete = function(tableName, responseCallback) { if (typeof tableName === 'string') { - var combinedQueryString = 'DELETE FROM ' + escapeFieldName(tableName) + var combinedQueryString = 'DELETE FROM ' + protectIdentifiers(tableName) + buildDataString(whereClause, ' AND ', 'WHERE') + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); From 10239544a6782a84611ab0548726231359deaf6a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 15:26:35 -0400 Subject: [PATCH 005/200] Added ability to do OR WHERE statements with or_where() method. --- HISTORY.md | 3 +- index.js | 88 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 9f47500..9ea5b75 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,4 +9,5 @@ you spot any mistakes. * Added this history file * Added the ability to do `SELECT DISTINCT` queries * We're doing better escaping of identifiers now -* Added the ability to use table/view prefixes directly (ex. `select * from users u`) \ No newline at end of file +* Added the ability to use table/view prefixes directly (ex. `select * from users u`) +* Added the ability to do `OR WHERE` statements with `or_where()` method. \ No newline at end of file diff --git a/index.js b/index.js index 64eb981..22e08a9 100755 --- a/index.js +++ b/index.js @@ -76,6 +76,7 @@ var Adapter = function(settings) { } var whereClause = {}, + whereArray = [], selectClause = [], orderByClause = '', groupByClause = '', @@ -88,6 +89,7 @@ var Adapter = function(settings) { aliasedTables = [] var resetQuery = function(newLastQuery) { + whereArray = []; whereClause = {}; selectClause = []; orderByClause = ''; @@ -224,6 +226,15 @@ var Adapter = function(settings) { return item + alias; }; + var buildWhereClause = function() { + var sql = ''; + if(whereArray.length > 0 || this.like_array.length > 0) { + sql += " WHERE "; + } + sql += whereArray.join(" "); + return sql; + } + var buildDataString = function(dataSet, separator, clause) { if (!clause) { clause = 'WHERE'; @@ -311,34 +322,73 @@ var Adapter = function(settings) { return s.substring(l, r + 1); }; + var hasOperator = function (str) { + if(typeof str === 'string') + if(str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)); + } + this.connectionSettings = function() { return connectionSettings; }; this.connection = function() { return connection; }; - this.where = function(whereSet, whereValue, isRaw) { - if (typeof whereSet === 'object' && typeof whereValue === 'undefined') { - whereClause = mergeObjects(whereClause, whereSet); + this.where = function(key, value, isRaw) { + isRaw = isRaw || false; + value = value || null; + + var escape = (isRaw ? false : true); + return this._where(key, value, 'AND ', escape); + }; + + this.or_where = function(key, value, isRaw) { + isRaw = isRaw || false; + value = value || null; + + var escape = (isRaw ? false : true); + return this._where(key, value, 'OR ', escape); + }; + + this._where = function(key, value, type, escape) { + value = value || null; + type = type || 'AND '; + + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + key_array = {}; + key_array[key] = value; + key = key_array; } - else if ((typeof whereSet === 'string' || typeof whereSet === 'number') && typeof whereValue != 'undefined') { - if (isRaw) { - rawWhereClause[whereSet] = true; + + for (var k in key) { + var v = key[k]; + var prefix = (whereArray.length == 0 ? '' : type); + + if (v === null && !hasOperator(k)) { + k += ' IS NULL'; } - whereClause[whereSet] = whereValue; - } - else if ((typeof whereSet === 'string' || typeof whereSet === 'number') && typeof whereValue === 'object' && Object.prototype.toString.call(whereValue) === '[object Array]' && whereValue.length > 0) { - whereClause[whereSet] = whereValue; - } - else if (typeof whereSet === 'string' && typeof whereValue === 'undefined') { - rawWhereString[whereSet] = true; - whereClause[whereSet] = whereValue; + + if (v !== null) { + if (escape === true) { + k = protectIdentifiers(k); + v = ' ' + this.escape(v); + } + + if (!hasOperator(k)) { + k += ' ='; + } + } + else { + k = protectIdentifiers(k); + } + + whereArray.push(prefix+k+v); } + return that; - }; + } this.count = function(tableName, responseCallback) { if (typeof tableName === 'string') { var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count FROM ' + protectIdentifiers(tableName) + buildJoinString() - + buildDataString(whereClause, ' AND ', 'WHERE'); + + buildWhereClause(); connection.query(combinedQueryString, function(err, res) { if (err) @@ -500,7 +550,7 @@ var Adapter = function(settings) { var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) + ' FROM ' + protectIdentifiers(tableName) + buildJoinString() - + buildDataString(whereClause, ' AND ', 'WHERE') + + buildWhereClause() + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') + (havingClause !== '' ? ' HAVING ' + havingClause : '') + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') @@ -518,7 +568,7 @@ var Adapter = function(settings) { if (typeof tableName === 'string') { var combinedQueryString = 'UPDATE ' + protectIdentifiers(tableName) + buildDataString(newData, ', ', 'SET') - + buildDataString(whereClause, ' AND ', 'WHERE') + + buildWhereClause() + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); connection.query(combinedQueryString, responseCallback); @@ -535,7 +585,7 @@ var Adapter = function(settings) { this.delete = function(tableName, responseCallback) { if (typeof tableName === 'string') { var combinedQueryString = 'DELETE FROM ' + protectIdentifiers(tableName) - + buildDataString(whereClause, ' AND ', 'WHERE') + + buildWhereClause() + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); connection.query(combinedQueryString, responseCallback); From 0a0dfbd7b29922e69c68d778a4d5082a95afafb9 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 15:30:51 -0400 Subject: [PATCH 006/200] Added the ability to directly do LIKE statements with the methods: like, not_like, or_like, and or_not_like. --- HISTORY.md | 3 ++- index.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 9ea5b75..42218e4 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,4 +10,5 @@ you spot any mistakes. * Added the ability to do `SELECT DISTINCT` queries * We're doing better escaping of identifiers now * Added the ability to use table/view prefixes directly (ex. `select * from users u`) -* Added the ability to do `OR WHERE` statements with `or_where()` method. \ No newline at end of file +* Added the ability to do `OR WHERE` statements with `or_where()` method. +* Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) \ No newline at end of file diff --git a/index.js b/index.js index 22e08a9..f4a3b10 100755 --- a/index.js +++ b/index.js @@ -384,6 +384,69 @@ var Adapter = function(settings) { return that; } + this.like = function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'AND ', side, ''); + }; + + this.not_like = function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'AND ', side, ' NOT'); + }; + + this.or_like = function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'OR ', side, ''); + }; + + this.or_not_like = function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'OR ', side, ' NOT'); + }; + + this._like = function(field, match, type, side, not) { + match = match || ''; + type = type || 'AND '; + side = side || 'both'; + not = not || ''; + + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + field_array = {}; + field_array[field] = match; + field = field_array; + } + + for(k in field) { + v = field[k]; + k = protectIdentifiers(k.trim()); + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + + this._where(like_statement,null,type,false); + } + + return that; + } + this.count = function(tableName, responseCallback) { if (typeof tableName === 'string') { var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count FROM ' + protectIdentifiers(tableName) From 6745b94d4fd25b996bf303cbe58711f5668d4815 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 17:01:39 -0400 Subject: [PATCH 007/200] Added the ability to do myriad WHERE IN statements directly and restored the ability to do them by passing an array to the second param of where(). --- HISTORY.md | 4 +- index.js | 118 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 90 insertions(+), 32 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 42218e4..d742f90 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,4 +11,6 @@ you spot any mistakes. * We're doing better escaping of identifiers now * Added the ability to use table/view prefixes directly (ex. `select * from users u`) * Added the ability to do `OR WHERE` statements with `or_where()` method. -* Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) \ No newline at end of file +* Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) +* Restored ability to do `WHERE IN(...)` statements by passing an array as the second param to `where()` +* Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) \ No newline at end of file diff --git a/index.js b/index.js index f4a3b10..96e481e 100755 --- a/index.js +++ b/index.js @@ -74,9 +74,9 @@ var Adapter = function(settings) { if (settings.charset) { connection.query('SET NAMES ' + settings.charset); } - - var whereClause = {}, - whereArray = [], + + var whereArray = [], + whereInArray = [], selectClause = [], orderByClause = '', groupByClause = '', @@ -84,13 +84,13 @@ var Adapter = function(settings) { limitClause = -1, offsetClause = -1, joinClause = [], - lastQuery = ''; + lastQuery = '', distinctClause = '', aliasedTables = [] var resetQuery = function(newLastQuery) { whereArray = []; - whereClause = {}; + whereInArray = []; selectClause = []; orderByClause = ''; groupByClause = ''; @@ -98,16 +98,11 @@ var Adapter = function(settings) { limitClause = -1; offsetClause = -1; joinClause = []; - lastQuery = (typeof newLastQuery === 'string' ? newLastQuery : ''); distinctClause = ''; - rawWhereClause = {}; - rawWhereString = {}; + lastQuery = (typeof newLastQuery === 'string' ? newLastQuery : ''); aliasedTables = []; }; - var rawWhereClause = {}; - var rawWhereString = {}; - var trackAliases = function(table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (var i in table) { @@ -228,7 +223,7 @@ var Adapter = function(settings) { var buildWhereClause = function() { var sql = ''; - if(whereArray.length > 0 || this.like_array.length > 0) { + if(whereArray.length > 0) { sql += " WHERE "; } sql += whereArray.join(" "); @@ -236,13 +231,10 @@ var Adapter = function(settings) { } var buildDataString = function(dataSet, separator, clause) { - if (!clause) { - clause = 'WHERE'; - } + clause = clause || 'SET'; + separator = separator || ', '; + var queryString = '', y = 1; - if (!separator) { - separator = ', '; - } var useSeparator = true; var datasetSize = getObjectSize(dataSet); @@ -251,18 +243,12 @@ var Adapter = function(settings) { useSeparator = true; if (dataSet.hasOwnProperty(key)) { - if (clause == 'WHERE' && rawWhereString[key] == true) { - queryString += key; - } - else if (dataSet[key] === null) { - queryString += protectIdentifiers(key) + (clause == 'WHERE' ? " is NULL" : "=NULL"); + if (dataSet[key] === null) { + queryString += protectIdentifiers(key) + "=NULL"; } else if (typeof dataSet[key] !== 'object') { queryString += protectIdentifiers(key) + "=" + connection.escape(dataSet[key]); } - else if (typeof dataSet[key] === 'object' && Object.prototype.toString.call(dataSet[key]) === '[object Array]' && dataSet[key].length > 0) { - queryString += protectIdentifiers(key) + ' in ("' + dataSet[key].join('", "') + '")'; - } else { useSeparator = false; datasetSize = datasetSize - 1; @@ -327,28 +313,65 @@ var Adapter = function(settings) { if(str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)); } + var qb_escape = function(str) { + if (typeof str === 'string') { + str = "'" + this.escape(str) + "'"; + } + else if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } + else if (str === null) { + str = 'NULL'; + } + + return str; + } + this.connectionSettings = function() { return connectionSettings; }; this.connection = function() { return connection; }; - + this.where = function(key, value, isRaw) { - isRaw = isRaw || false; + isRaw = (typeof isRaw === 'boolean' ? isRaw : false); value = value || null; var escape = (isRaw ? false : true); + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } return this._where(key, value, 'AND ', escape); }; this.or_where = function(key, value, isRaw) { - isRaw = isRaw || false; + isRaw = (typeof isRaw === 'boolean' ? isRaw : false); value = value || null; var escape = (isRaw ? false : true); + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } return this._where(key, value, 'OR ', escape); }; + this.where_in = function(key, values) { + return this._where_in(key,values,false,'AND '); + } + + this.or_where_in = function(key, values) { + return this._where_in(key,values,false,'OR '); + } + + this.where_not_in = function(key, values) { + return this._where_in(key,values,true,'AND '); + } + + this.or_where_not_in = function(key, values) { + return this._where_in(key,values,true,'OR '); + } + this._where = function(key, value, type, escape) { value = value || null; type = type || 'AND '; + escape = (typeof escape === 'boolean' ? escape : true); if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { key_array = {}; @@ -358,6 +381,11 @@ var Adapter = function(settings) { for (var k in key) { var v = key[k]; + + if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { + return this._where_in(k,v,false,type); + } + var prefix = (whereArray.length == 0 ? '' : type); if (v === null && !hasOperator(k)) { @@ -382,7 +410,35 @@ var Adapter = function(settings) { } return that; - } + }; + + this._where_in = function(key, values, not, type) { + values = values || []; + type = type || 'AND '; + not = (not ? ' NOT' : ''); + + if(key === null || values.length === 0) return; + + // Values must be an array... + if(Object.prototype.toString.call(value) === Object.prototype.toString.call([])) { + values = [values]; + } + + for (var i in values) { + var value = values[i]; + whereInArray = qb_escape(value); + } + + prefix = (whereArray.length == 0 ? '' : type); + + where_in = prefix + protect_identifiers(key) + not + " IN (" + whereInArray.join(', ') + ") "; + + whereArray.push(where_in); + + // reset the array for multiple calls + whereInArray = []; + return that; + }; this.like = function(field, match, side) { match = match || ''; @@ -776,4 +832,4 @@ var Pool = function (settings) { }; exports.Adapter = Adapter; -exports.Pool = Pool; \ No newline at end of file +exports.Pool = Pool; From fe03b5eb15b49d646037e604042874ec78c0366b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 16 Jul 2014 17:55:18 -0400 Subject: [PATCH 008/200] Added the ability to do FROM statements directly to do things like db.from('foo').get(function() { ... }); Also, we are now officially tracking aliases for proper escaping. --- HISTORY.md | 3 +- index.js | 114 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index d742f90..879f0b5 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -13,4 +13,5 @@ you spot any mistakes. * Added the ability to do `OR WHERE` statements with `or_where()` method. * Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) * Restored ability to do `WHERE IN(...)` statements by passing an array as the second param to `where()` -* Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) \ No newline at end of file +* Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) +* Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) \ No newline at end of file diff --git a/index.js b/index.js index 96e481e..88a439d 100755 --- a/index.js +++ b/index.js @@ -77,6 +77,7 @@ var Adapter = function(settings) { var whereArray = [], whereInArray = [], + fromArray = [], selectClause = [], orderByClause = '', groupByClause = '', @@ -91,6 +92,7 @@ var Adapter = function(settings) { var resetQuery = function(newLastQuery) { whereArray = []; whereInArray = []; + fromArray = []; selectClause = []; orderByClause = ''; groupByClause = ''; @@ -107,7 +109,7 @@ var Adapter = function(settings) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (var i in table) { var t = table[i]; - track_aliases(t); + trackAliases(t); } return; } @@ -115,7 +117,7 @@ var Adapter = function(settings) { // Does the string contain a comma? If so, we need to separate // the string into discreet statements if (table.indexOf(',') !== -1) { - return track_aliases(table.split(',')); + return trackAliases(table.split(',')); } // if a table alias is used we can recognize it by a space @@ -230,6 +232,15 @@ var Adapter = function(settings) { return sql; } + var buildFromClause = function() { + var sql = ''; + if(fromArray.length > 0) { + sql += " FROM "; + } + sql += '(' + fromArray.join(', ') + ')'; + return sql; + }; + var buildDataString = function(dataSet, separator, clause) { clause = clause || 'SET'; separator = separator || ', '; @@ -505,7 +516,8 @@ var Adapter = function(settings) { this.count = function(tableName, responseCallback) { if (typeof tableName === 'string') { - var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count FROM ' + protectIdentifiers(tableName) + var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count' + + buildFromClause() + buildJoinString() + buildWhereClause(); @@ -520,6 +532,37 @@ var Adapter = function(settings) { return that; }; + + this.from = function(from) { + if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { + from = [from]; + } + for (var i in from) { + var val = from[i]; + + if (val.indexOf(',') !== -1) { + var objects = val.split(','); + for (var j in objects) { + var v = objects[j].trim(); + + trackAliases(v); + + fromArray.push(protectIdentifiers(v, true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protectIdentifiers function to know whether to add a table prefix + trackAliases(val); + + fromArray.push(protectIdentifiers(val, true)); + } + } + + return that; + } this.join = function(tableName, relation, direction) { joinClause.push({ @@ -665,20 +708,32 @@ var Adapter = function(settings) { }; this.get = function(tableName, responseCallback) { - if (typeof tableName === 'string') { - var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) - + ' FROM ' + protectIdentifiers(tableName) - + buildJoinString() - + buildWhereClause() - + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') - + (havingClause !== '' ? ' HAVING ' + havingClause : '') - + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') - + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') - + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); - - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); + if (typeof tableName !== 'function') { + trackAliases(tableName); + this.from(tableName); } + else { + if (fromArray.length == 0) { + throw new Error('You have not specified any tables to select from!'); + return that; + } + else { + responseCallback = tableName; + } + } + + var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) + + buildFromClause() + + buildJoinString() + + buildWhereClause() + + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') + + (havingClause !== '' ? ' HAVING ' + havingClause : '') + + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') + + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') + + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); + + connection.query(combinedQueryString, responseCallback); + resetQuery(combinedQueryString); return that; }; @@ -702,14 +757,27 @@ var Adapter = function(settings) { }; this.delete = function(tableName, responseCallback) { - if (typeof tableName === 'string') { - var combinedQueryString = 'DELETE FROM ' + protectIdentifiers(tableName) - + buildWhereClause() - + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); - - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); + if (typeof tableName !== 'function') { + trackAliases(tableName); + this.from(tableName); } + else { + if (fromArray.length == 0) { + throw new Error('You have not specified any tables to delete from!'); + return that; + } + else { + responseCallback = tableName; + } + } + + var combinedQueryString = 'DELETE' + + buildFromClause() + + buildWhereClause() + + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); + + connection.query(combinedQueryString, responseCallback); + resetQuery(combinedQueryString); return that; }; From 786cff272ba6a655cba280bd2663e60b68aa5313 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 17 Jul 2014 17:01:30 -0400 Subject: [PATCH 009/200] Enhanced security of join() statements. --- HISTORY.md | 3 ++- index.js | 48 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 879f0b5..69867c4 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -14,4 +14,5 @@ you spot any mistakes. * Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) * Restored ability to do `WHERE IN(...)` statements by passing an array as the second param to `where()` * Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) -* Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) \ No newline at end of file +* Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) +* Identifiers will now be properly escaped in `JOIN` statements. \ No newline at end of file diff --git a/index.js b/index.js index 88a439d..21606c7 100755 --- a/index.js +++ b/index.js @@ -78,6 +78,7 @@ var Adapter = function(settings) { var whereArray = [], whereInArray = [], fromArray = [], + joinArray = [], selectClause = [], orderByClause = '', groupByClause = '', @@ -93,6 +94,7 @@ var Adapter = function(settings) { whereArray = []; whereInArray = []; fromArray = []; + joinArray = []; selectClause = []; orderByClause = ''; groupByClause = ''; @@ -278,13 +280,9 @@ var Adapter = function(settings) { }; var buildJoinString = function() { - var joinString = ''; - - for (var i = 0; i < joinClause.length; i++) { - joinString += (joinClause[i].direction !== '' ? ' ' + joinClause[i].direction : '') + ' JOIN ' + protectIdentifiers(joinClause[i].table) + ' ON ' + joinClause[i].relation; - } - - return joinString; + var sql = ' '; + sql += joinArray.join(' '); + return sql; }; var mergeObjects = function() { @@ -437,12 +435,12 @@ var Adapter = function(settings) { for (var i in values) { var value = values[i]; - whereInArray = qb_escape(value); + whereInArray.push(qb_escape(value)); } prefix = (whereArray.length == 0 ? '' : type); - where_in = prefix + protect_identifiers(key) + not + " IN (" + whereInArray.join(', ') + ") "; + where_in = prefix + protectIdentifiers(key) + not + " IN (" + whereInArray.join(', ') + ") "; whereArray.push(where_in); @@ -565,11 +563,33 @@ var Adapter = function(settings) { } this.join = function(tableName, relation, direction) { - joinClause.push({ - table: tableName, - relation: relation, - direction: (typeof direction === 'string' ? trim(direction.toUpperCase()) : '') - }); + direction = (!direction || typeof direction !== 'string' ? '' : direction); + + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT INNER','LEFT OUTER','RIGHT INNER','RIGHT OUTER']; + + if (direction != '') { + direction = direction.toUpperCase(); + if (valid_directions.indexOf(direction) === -1) { + direction = ''; + } + else { + direction += ' '; + } + } + + trackAliases(tableName); + + var match; + if (match = relation.match(/([\w\.]+)([\W\s]+)(.+)/)) { + match[1] = protectIdentifiers(match[1]); + match[3] = protectIdentifiers(match[3]); + + relation = match[1] + match[2] + match[3]; + } + + join = direction + 'JOIN ' + protectIdentifiers(tableName, true) + ' ON ' + relation; + + joinArray.push(join); return that; }; From 62c175b5b643a0e2fe80b744129f74812f24df0f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 17 Jul 2014 17:12:21 -0400 Subject: [PATCH 010/200] Fixed bug with where_in statements. --- index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 21606c7..dd46fb0 100755 --- a/index.js +++ b/index.js @@ -422,14 +422,15 @@ var Adapter = function(settings) { }; this._where_in = function(key, values, not, type) { + key = key || null; values = values || []; type = type || 'AND '; not = (not ? ' NOT' : ''); - if(key === null || values.length === 0) return; + if(key === null || values.length === 0) return that; // Values must be an array... - if(Object.prototype.toString.call(value) === Object.prototype.toString.call([])) { + if(Object.prototype.toString.call(value) !== Object.prototype.toString.call([])) { values = [values]; } From 37d44c92c37467602e315e0e15b1d1ecbb560b31 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 17 Jul 2014 17:26:29 -0400 Subject: [PATCH 011/200] Added the ability to do call get_where(). --- index.js | 55 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index dd46fb0..376aa59 100755 --- a/index.js +++ b/index.js @@ -336,6 +336,20 @@ var Adapter = function(settings) { return str; } + var compileSelect = function() { + var sql = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) + + buildFromClause() + + buildJoinString() + + buildWhereClause() + + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') + + (havingClause !== '' ? ' HAVING ' + havingClause : '') + + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') + + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') + + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); + + return sql; + } + this.connectionSettings = function() { return connectionSettings; }; this.connection = function() { return connection; }; @@ -728,10 +742,10 @@ var Adapter = function(settings) { return that; }; - this.get = function(tableName, responseCallback) { - if (typeof tableName !== 'function') { - trackAliases(tableName); - this.from(tableName); + this.get = function(table, callback) { + if (typeof table !== 'function') { + trackAliases(table); + this.from(table); } else { if (fromArray.length == 0) { @@ -739,22 +753,31 @@ var Adapter = function(settings) { return that; } else { - responseCallback = tableName; + callback = table; } } - var combinedQueryString = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) - + buildFromClause() - + buildJoinString() - + buildWhereClause() - + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') - + (havingClause !== '' ? ' HAVING ' + havingClause : '') - + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') - + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') - + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); + var sql = compileSelect(); - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); + connection.query(sql, callback); + resetQuery(sql); + + return that; + }; + + this.get_where = function(table, where, callback) { + if (table !== '') { + this.from(table); + } + + if (where !== null) { + this.where(where); + } + + var sql = compileSelect(); + + connection.query(sql, callback); + resetQuery(sql); return that; }; From 6234cc5f585c98a3d28b4ac1b7e8e6423074f08d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 17 Jul 2014 17:51:52 -0400 Subject: [PATCH 012/200] Updated history file and fixed bug in how values were being escaped in normal where() calls. --- HISTORY.md | 3 ++- index.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 69867c4..4e45ec5 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -15,4 +15,5 @@ you spot any mistakes. * Restored ability to do `WHERE IN(...)` statements by passing an array as the second param to `where()` * Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) * Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) -* Identifiers will now be properly escaped in `JOIN` statements. \ No newline at end of file +* Identifiers will now be properly escaped in `JOIN` statements. +* Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) \ No newline at end of file diff --git a/index.js b/index.js index 376aa59..c52a24a 100755 --- a/index.js +++ b/index.js @@ -418,7 +418,7 @@ var Adapter = function(settings) { if (v !== null) { if (escape === true) { k = protectIdentifiers(k); - v = ' ' + this.escape(v); + v = ' ' + qb_escape(v); } if (!hasOperator(k)) { From d0a3e039a7038627db4e802017c7378e2afafcb7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 17 Jul 2014 18:46:20 -0400 Subject: [PATCH 013/200] Added select_min, select_max, select_avg, and select_sum methods. --- HISTORY.md | 5 ++- index.js | 112 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 4e45ec5..5532d29 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2014-07-15) +## v0.9.0 (2014-07-17) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -16,4 +16,5 @@ you spot any mistakes. * Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) * Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) * Identifiers will now be properly escaped in `JOIN` statements. -* Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) \ No newline at end of file +* Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) +* Added the ability to call `select_min()`, `select_max()`, `select_avg()`, and `select_sum()`. \ No newline at end of file diff --git a/index.js b/index.js index c52a24a..1d604ee 100755 --- a/index.js +++ b/index.js @@ -79,7 +79,7 @@ var Adapter = function(settings) { whereInArray = [], fromArray = [], joinArray = [], - selectClause = [], + selectArray = [], orderByClause = '', groupByClause = '', havingClause = '', @@ -95,7 +95,7 @@ var Adapter = function(settings) { whereInArray = []; fromArray = []; joinArray = []; - selectClause = []; + selectArray = []; orderByClause = ''; groupByClause = ''; havingClause = '', @@ -154,8 +154,8 @@ var Adapter = function(settings) { return str.replace(/[`]+/,'`'); } - var protectIdentifiers = function(item,escape) { - escape = (typeof escape === 'boolean' ? escape : true); + var protectIdentifiers = function(item,protect_identifiers) { + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { var escaped_array = {}; @@ -199,7 +199,7 @@ var Adapter = function(settings) { // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item if (aliasedTables.indexOf(parts[0]) !== -1) { - if (escape === true) { + if (protect_identifiers === true) { for (var key in parts) { var val = parts[key]; if (val !== '*') { @@ -218,7 +218,7 @@ var Adapter = function(settings) { return item + alias; } - if (escape === true) { + if (protect_identifiers === true) { item = escapeIdentifiers(item); } @@ -280,8 +280,9 @@ var Adapter = function(settings) { }; var buildJoinString = function() { - var sql = ' '; + var sql = ''; sql += joinArray.join(' '); + if(sql.length > 0) sql = ' ' + sql; return sql; }; @@ -337,19 +338,32 @@ var Adapter = function(settings) { } var compileSelect = function() { - var sql = 'SELECT ' + distinctClause + (selectClause.length === 0 ? '*' : selectClause.join(',')) - + buildFromClause() - + buildJoinString() - + buildWhereClause() - + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') - + (havingClause !== '' ? ' HAVING ' + havingClause : '') - + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') - + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') - + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); + var sql = 'SELECT ' + distinctClause; + if (selectArray.length === 0) { + sql += '*'; + } + + sql += selectArray.join(',') + + buildFromClause() + + buildJoinString() + + buildWhereClause() + + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') + + (havingClause !== '' ? ' HAVING ' + havingClause : '') + + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') + + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') + + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); return sql; } + var createAliasFromTable = function(item) { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + }; + this.connectionSettings = function() { return connectionSettings; }; this.connection = function() { return connection; }; @@ -608,23 +622,81 @@ var Adapter = function(settings) { return that; }; - this.select = function(selectSet) { - if (Object.prototype.toString.call(selectSet) === '[object Array]') { + this.select = function(select,escape) { + if (typeof escape !== 'boolean') escape = null; + + if (typeof select === 'string') { + select = select.split(','); + } + + for (var i in select) { + var val = select[i]; + + if(val !== '') { + selectArray.push(val); + } + } + + if (Object.prototype.toString.call(selectSet) === Object.prototype.toString.call([])) { for (var i = 0; i < selectSet.length; i++) { - selectClause.push(selectSet[i]); + selectArray.push(selectSet[i]); } } else { if (typeof selectSet === 'string') { var selectSetItems = selectSet.split(','); for (var i = 0; i < selectSetItems.length; i++) { - selectClause.push(trim(selectSetItems[i])); + selectArray.push(trim(selectSetItems[i])); } } } return that; }; + this.select_min = function(select,alias) { + return this._min_max_avg_sum(select,alias,'MIN'); + }; + + this.select_max = function(select,alias) { + return this._min_max_avg_sum(select,alias,'MAX'); + }; + + this.select_avg = function(select,alias) { + return this._min_max_avg_sum(select,alias,'AVG'); + }; + + this.select_sum = function(select,alias) { + return this._min_max_avg_sum(select,alias,'SUM'); + }; + + this._min_max_avg_sum = function(select,alias,type) { + select = select || ''; + alias = alias || ''; + type = type || 'MAX'; + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return that; + } + + type = type.toUpperCase(); + + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return that; + } + + if (alias == '') { + alias = createAliasFromTable(select.trim()); + } + + sql = type + '(' + protectIdentifiers(select.trim()) + ') AS ' + alias; + + selectArray.push(sql); + + return that; + } + this.distinct = function() { distinctClause = 'DISTINCT '; return that; From 5345a0f6a4a5fddc8449f978772fb9f0177634d2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 21 Jul 2014 15:08:56 -0400 Subject: [PATCH 014/200] Fixed various bugs in protectIdentifiers, significanly increased security and prevention of SQL injsection attacks, improved code formatting, improved SQL string formatting, and fixed select method. --- HISTORY.md | 3 +- index.js | 89 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 5532d29..fb1551c 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,4 +17,5 @@ you spot any mistakes. * Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) * Identifiers will now be properly escaped in `JOIN` statements. * Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) -* Added the ability to call `select_min()`, `select_max()`, `select_avg()`, and `select_sum()`. \ No newline at end of file +* Added the ability to call `select_min()`, `select_max()`, `select_avg()`, and `select_sum()`. +* Significanly improved security, helping to prevent SQL injection attacks. \ No newline at end of file diff --git a/index.js b/index.js index 1d604ee..78b7b08 100755 --- a/index.js +++ b/index.js @@ -169,14 +169,20 @@ var Adapter = function(settings) { } // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/, ' '); + item = item.replace(/\s+/g, ' '); // If the item has an alias declaration we remove it and set it aside. // Basically we remove everything to the right of the first space - if (item.indexOf(' ') !== -1) { - var alias_index = item.lastIndexOf(' '); - var alias = item.slice(alias_index); - item = item.slice(0,alias_index); + if (item.match(/\sAS\s/ig)) { + var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + var alias = (protect_identifiers ? item.substr(alias_index,4) + escapeIdentifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else if (item.indexOf(' ') !== -1) { + var alias_index = item.indexOf(' '); + + var alias = (protect_identifiers && ! hasOperator(item.substr(alias_index + 1)) ? ' ' + escapeIdentifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0,alias_index); } else { alias = ''; @@ -185,8 +191,8 @@ var Adapter = function(settings) { // This is basically a bug fix for queries that use MAX, MIN, etc. // If a parenthesis is found we know that we do not need to // escape the data or add a prefix. - if (item.indexOf('(') !== -1) { - return item + alias; + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + return item; } // Break the string apart if it contains periods, then insert the table prefix @@ -212,7 +218,7 @@ var Adapter = function(settings) { return item + alias; } - if (escape === true) { + if (protect_identifiers === true) { item = escapeIdentifiers(item); } @@ -319,8 +325,12 @@ var Adapter = function(settings) { }; var hasOperator = function (str) { - if(typeof str === 'string') - if(str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)); + if(typeof str === 'string') { + if(!str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)) { + return false; + } + } + return true; } var qb_escape = function(str) { @@ -343,7 +353,7 @@ var Adapter = function(settings) { sql += '*'; } - sql += selectArray.join(',') + sql += selectArray.join(', ') + buildFromClause() + buildJoinString() + buildWhereClause() @@ -443,7 +453,12 @@ var Adapter = function(settings) { k = protectIdentifiers(k); } - whereArray.push(prefix+k+v); + if (v) { + whereArray.push(prefix+k+v); + } + else { + whereArray.push(prefix+k); + } } return that; @@ -539,23 +554,25 @@ var Adapter = function(settings) { } return that; - } + }; - this.count = function(tableName, responseCallback) { - if (typeof tableName === 'string') { - var combinedQueryString = 'SELECT ' + distinctClause + 'COUNT(*) as count' + this.count = function(table, responseCallback) { + if (typeof table === 'string') { + trackAliases(table); + this.from(table); + } + var sql = this.query('SELECT COUNT(*) AS ' + protectIdentifiers('count') + buildFromClause() + buildJoinString() - + buildWhereClause(); + + buildWhereClause()); - connection.query(combinedQueryString, function(err, res) { - if (err) - responseCallback(err, null); - else - responseCallback(null, res[0]['count']); - }); - resetQuery(combinedQueryString); - } + connection.query(sql, function(err, res) { + if (err) + responseCallback(err, null); + else + responseCallback(null, res[0]['count']); + }); + resetQuery(sql); return that; }; @@ -594,7 +611,7 @@ var Adapter = function(settings) { this.join = function(tableName, relation, direction) { direction = (!direction || typeof direction !== 'string' ? '' : direction); - var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT INNER','LEFT OUTER','RIGHT INNER','RIGHT OUTER']; + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; if (direction != '') { direction = direction.toUpperCase(); @@ -623,31 +640,17 @@ var Adapter = function(settings) { }; this.select = function(select,escape) { - if (typeof escape !== 'boolean') escape = null; + if (typeof escape !== 'boolean') escape = true; if (typeof select === 'string') { select = select.split(','); } for (var i in select) { - var val = select[i]; + var val = select[i].trim(); if(val !== '') { - selectArray.push(val); - } - } - - if (Object.prototype.toString.call(selectSet) === Object.prototype.toString.call([])) { - for (var i = 0; i < selectSet.length; i++) { - selectArray.push(selectSet[i]); - } - } - else { - if (typeof selectSet === 'string') { - var selectSetItems = selectSet.split(','); - for (var i = 0; i < selectSetItems.length; i++) { - selectArray.push(trim(selectSetItems[i])); - } + selectArray.push(protectIdentifiers(val)); } } return that; From cfc6f043e2ffdc8595ec524ff047a880c083e691 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 25 Jul 2014 14:19:33 -0400 Subject: [PATCH 015/200] Added new features like or_having(), offset(), and set(). Modified update method to support optionally passing where clause as 3rd parameter. Restructured code to be test-able. Added new dependencies for testing: mocha and chai. Tests for select and from methods have been written. Tests for join have been started. Backwards compatibility has been retained. --- HISTORY.md | 10 +- Makefile | 5 + README.md | 7 + index.js | 982 +++++++------------------------------------ lib/query_builder.js | 978 ++++++++++++++++++++++++++++++++++++++++++ package.json | 9 +- test.js | 63 +++ 7 files changed, 1220 insertions(+), 834 deletions(-) create mode 100644 Makefile mode change 100644 => 100755 README.md create mode 100755 lib/query_builder.js mode change 100644 => 100755 package.json create mode 100755 test.js diff --git a/HISTORY.md b/HISTORY.md index fb1551c..773f949 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,4 +18,12 @@ you spot any mistakes. * Identifiers will now be properly escaped in `JOIN` statements. * Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) * Added the ability to call `select_min()`, `select_max()`, `select_avg()`, and `select_sum()`. -* Significanly improved security, helping to prevent SQL injection attacks. \ No newline at end of file +* Significanly improved security, helping to prevent SQL injection attacks. +* Added ability to do `OR HAVING` statements with `or_having()` method +* Added ability to add an offset directly without using the `limit()` method by using the `offset()` method. +* Added ability to set `SET` values for updates and inserts using the `set()` method. +* `UPDATE` statements now support `ORDER BY` clauses which can be added to the query using the `order_by()` method. +* The `update()` method's 3rd parameter can now either be the callback (as always) or a `WHERE` clause (ex. 'foo = "bar"' or {foo:'bar', id: 3}). If a where clause is provided, the callback is now the 4th parameter. This change is fully backwards-compatible with the previous version of this module. +* New package dependencies (for testing): chai and mocha. +* Tests have been written for better-ensuring future enhancements and fixes to not break functionality +* Library has been broken into 3 objects to allow for prpoer testing. This won't affect the API and is fully-backwards compatible with the previous version. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..574b885 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +TESTS = test/*.js +test: + mocha --timeout 5000 --reporter nyan $(TESTS) + +.PHONY: test diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 121fb32..bb4f7ff --- a/README.md +++ b/README.md @@ -40,6 +40,13 @@ Basic support of MySQL commands =============================== * SELECT + * DISTINCT + * FROM + * IN + * MIN + * MAX + * AVG + * SUM * UPDATE * INSERT (single-row and multi-row) * INSERT IGNORE diff --git a/index.js b/index.js index 78b7b08..6347931 100755 --- a/index.js +++ b/index.js @@ -26,888 +26,199 @@ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * -**/ - +**/ var Adapter = function(settings) { - var mysql = require('mysql'); - - var initializeConnectionSettings = function () { - if(settings.server) { - settings.host = settings.server; - } - if(settings.username) { - settings.user = settings.username; - } - - if (!settings.host) { - throw new Error('Unable to start ActiveRecord - no server given.'); - } - if (!settings.port) { - settings.port = 3306; - } - if (!settings.user) { - settings.user = ''; - } - if (!settings.password) { - settings.password = ''; - } - if (!settings.database) { - throw new Error('Unable to start ActiveRecord - no database given.'); - } - - return settings; - }; - - var connection; - var connectionSettings; - var pool; - - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } else { - connectionSettings = initializeConnectionSettings(); - connection = new mysql.createConnection(connectionSettings); - } - - if (settings.charset) { - connection.query('SET NAMES ' + settings.charset); - } - - var whereArray = [], - whereInArray = [], - fromArray = [], - joinArray = [], - selectArray = [], - orderByClause = '', - groupByClause = '', - havingClause = '', - limitClause = -1, - offsetClause = -1, - joinClause = [], - lastQuery = '', - distinctClause = '', - aliasedTables = [] - - var resetQuery = function(newLastQuery) { - whereArray = []; - whereInArray = []; - fromArray = []; - joinArray = []; - selectArray = []; - orderByClause = ''; - groupByClause = ''; - havingClause = '', - limitClause = -1; - offsetClause = -1; - joinClause = []; - distinctClause = ''; - lastQuery = (typeof newLastQuery === 'string' ? newLastQuery : ''); - aliasedTables = []; - }; - - var trackAliases = function(table) { - if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (var i in table) { - var t = table[i]; - trackAliases(t); - } - return; - } - - // Does the string contain a comma? If so, we need to separate - // the string into discreet statements - if (table.indexOf(',') !== -1) { - return trackAliases(table.split(',')); - } - - // if a table alias is used we can recognize it by a space - if (table.indexOf(' ') !== -1) { - // if the alias is written with the AS keyword, remove it - table = table.replace(/\s+AS\s+/gi, ' '); - - // Grab the alias - alias = table.slice(table.lastIndexOf(' ')).trim(); - - // Store the alias, if it doesn't already exist - if(aliasedTables.indexOf(table) == -1) { - aliasedTables.push(table); - } - } - } - - var escapeIdentifiers = function(item) { - if (!item || item === '*') { - return item; - } - - var str; - if (item.indexOf('.') !== -1) { - str = '`' + item.replace(/\./g,'`.`') + '`'; - } - else { - str = '`' + item + '`'; - } - - // remove duplicates if the user already included the escape - return str.replace(/[`]+/,'`'); - } - - var protectIdentifiers = function(item,protect_identifiers) { - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - var escaped_array = {}; - - for (k in item) { - var v = item[k]; - escaped_array[protectIdentifiers(k)] = protectIdentifiers(v); - } - - return escaped_array; - } - - // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/g, ' '); - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (item.match(/\sAS\s/ig)) { - var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - var alias = (protect_identifiers ? item.substr(alias_index,4) + escapeIdentifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else if (item.indexOf(' ') !== -1) { - var alias_index = item.indexOf(' '); - - var alias = (protect_identifiers && ! hasOperator(item.substr(alias_index + 1)) ? ' ' + escapeIdentifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else { - alias = ''; - } - - // This is basically a bug fix for queries that use MAX, MIN, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - return item; - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - parts = item.split('.'); - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (aliasedTables.indexOf(parts[0]) !== -1) { - if (protect_identifiers === true) { - for (var key in parts) { - var val = parts[key]; - if (val !== '*') { - parts[key] = escapeIdentifiers(val); - } - } - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = escapeIdentifiers(item); - } - - return item + alias; - } - if (protect_identifiers === true) { - item = escapeIdentifiers(item); - } - - return item + alias; - }; - - var buildWhereClause = function() { - var sql = ''; - if(whereArray.length > 0) { - sql += " WHERE "; - } - sql += whereArray.join(" "); - return sql; - } - - var buildFromClause = function() { - var sql = ''; - if(fromArray.length > 0) { - sql += " FROM "; - } - sql += '(' + fromArray.join(', ') + ')'; - return sql; - }; - - var buildDataString = function(dataSet, separator, clause) { - clause = clause || 'SET'; - separator = separator || ', '; - - var queryString = '', y = 1; - var useSeparator = true; - - var datasetSize = getObjectSize(dataSet); - - for (var key in dataSet) { - useSeparator = true; - - if (dataSet.hasOwnProperty(key)) { - if (dataSet[key] === null) { - queryString += protectIdentifiers(key) + "=NULL"; - } - else if (typeof dataSet[key] !== 'object') { - queryString += protectIdentifiers(key) + "=" + connection.escape(dataSet[key]); - } - else { - useSeparator = false; - datasetSize = datasetSize - 1; - } - - if (y < datasetSize && useSeparator) { - queryString += separator; - y++; - } - } - } - if (getObjectSize(dataSet) > 0) { - queryString = ' ' + clause + ' ' + queryString; - } - return queryString; - }; - - var buildJoinString = function() { - var sql = ''; - sql += joinArray.join(' '); - if(sql.length > 0) sql = ' ' + sql; - return sql; - }; - - var mergeObjects = function() { - for (var i = 1; i < arguments.length; i++) { - for (var key in arguments[i]) { - if (arguments[i].hasOwnProperty(key)) { - arguments[0][key] = arguments[i][key]; - } - } - } - return arguments[0]; - }; - - var getObjectSize = function(object) { - var size = 0; - for (var key in object) { - if (object.hasOwnProperty(key)) { - size++; - } - } - return size; - }; - - var trim = function (s) { - var l = 0, r = s.length - 1; - while (l < s.length && s[l] == ' ') { - l++; - } - while (r > l && s[r] == ' ') { - r-=1; - } - return s.substring(l, r + 1); - }; - - var hasOperator = function (str) { - if(typeof str === 'string') { - if(!str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)) { - return false; - } - } - return true; - } - - var qb_escape = function(str) { - if (typeof str === 'string') { - str = "'" + this.escape(str) + "'"; - } - else if (typeof str === 'boolean') { - str = (str === false ? 0 : 1); - } - else if (str === null) { - str = 'NULL'; - } - - return str; - } - - var compileSelect = function() { - var sql = 'SELECT ' + distinctClause; - if (selectArray.length === 0) { - sql += '*'; - } - - sql += selectArray.join(', ') - + buildFromClause() - + buildJoinString() - + buildWhereClause() - + (groupByClause !== '' ? ' GROUP BY ' + groupByClause : '') - + (havingClause !== '' ? ' HAVING ' + havingClause : '') - + (orderByClause !== '' ? ' ORDER BY ' + orderByClause : '') - + (limitClause !== -1 ? ' LIMIT ' + limitClause : '') - + (offsetClause !== -1 ? ' OFFSET ' + offsetClause : ''); - - return sql; - } - - var createAliasFromTable = function(item) { - if (item.indexOf('.') !== -1) { - return item.split('.').reverse()[0]; - } - - return item; - }; - - this.connectionSettings = function() { return connectionSettings; }; - this.connection = function() { return connection; }; - - this.where = function(key, value, isRaw) { - isRaw = (typeof isRaw === 'boolean' ? isRaw : false); - value = value || null; - - var escape = (isRaw ? false : true); - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - }; - - this.or_where = function(key, value, isRaw) { - isRaw = (typeof isRaw === 'boolean' ? isRaw : false); - value = value || null; - - var escape = (isRaw ? false : true); - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); - } - return this._where(key, value, 'OR ', escape); - }; - - this.where_in = function(key, values) { - return this._where_in(key,values,false,'AND '); - } - - this.or_where_in = function(key, values) { - return this._where_in(key,values,false,'OR '); - } - - this.where_not_in = function(key, values) { - return this._where_in(key,values,true,'AND '); - } - - this.or_where_not_in = function(key, values) { - return this._where_in(key,values,true,'OR '); - } - - this._where = function(key, value, type, escape) { - value = value || null; - type = type || 'AND '; - escape = (typeof escape === 'boolean' ? escape : true); - - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - key_array = {}; - key_array[key] = value; - key = key_array; - } - - for (var k in key) { - var v = key[k]; - - if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { - return this._where_in(k,v,false,type); - } - - var prefix = (whereArray.length == 0 ? '' : type); - - if (v === null && !hasOperator(k)) { - k += ' IS NULL'; - } - - if (v !== null) { - if (escape === true) { - k = protectIdentifiers(k); - v = ' ' + qb_escape(v); - } - - if (!hasOperator(k)) { - k += ' ='; - } - } - else { - k = protectIdentifiers(k); - } - - if (v) { - whereArray.push(prefix+k+v); - } - else { - whereArray.push(prefix+k); - } - } - - return that; - }; - - this._where_in = function(key, values, not, type) { - key = key || null; - values = values || []; - type = type || 'AND '; - not = (not ? ' NOT' : ''); - - if(key === null || values.length === 0) return that; - - // Values must be an array... - if(Object.prototype.toString.call(value) !== Object.prototype.toString.call([])) { - values = [values]; - } - - for (var i in values) { - var value = values[i]; - whereInArray.push(qb_escape(value)); - } - - prefix = (whereArray.length == 0 ? '' : type); - - where_in = prefix + protectIdentifiers(key) + not + " IN (" + whereInArray.join(', ') + ") "; - - whereArray.push(where_in); - - // reset the array for multiple calls - whereInArray = []; - return that; - }; - - this.like = function(field, match, side) { - match = match || ''; - side = side || 'both'; - - return this._like(field, match, 'AND ', side, ''); - }; - - this.not_like = function(field, match, side) { - match = match || ''; - side = side || 'both'; - - return this._like(field, match, 'AND ', side, ' NOT'); - }; - - this.or_like = function(field, match, side) { - match = match || ''; - side = side || 'both'; - - return this._like(field, match, 'OR ', side, ''); - }; - this.or_not_like = function(field, match, side) { - match = match || ''; - side = side || 'both'; + this.qb = require('./lib/query_builder.js').QueryBuilder(); + + // Non-Public QueryBuilder APIs + this._where = this.qb._where; + this._where_in = this.qb._where_in; + this._like = this.qb._like; + this._min_max_avg_sum = this.qb._min_max_avg_sum; + this._having = this.qb._having; + this._update = this.qb._update; + this.resetQuery = this.qb.resetQuery; + + // QueryBuilder Properties + this.whereArray = this.qb.whereArray; + this.whereInArray = this.qb.whereInArray; + this.fromArray = this.qb.fromArray; + this.joinArray = this.qb.joinArray; + this.selectArray = this.qb.selectArray; + this.setArray = this.qb.setArray; + this.orderByArray = this.qb.orderByArray; + this.orderByDir = this.qb.orderByDir; + this.groupByArray = this.qb.groupByArray; + this.havingArray = this.qb.havingArray; + this.limitTo = this.qb.limitTo; + this.offsetVal = this.qb.offsetVal; + this.joinClause = this.qb.joinClause; + this.lastQueryString = this.qb.lastQueryString; + this.distinctClause = this.qb.distinctClause; + this.aliasedTables = this.qb.aliasedTables; + + // QueryBuilder method mappings for backwards compatibility + this.where = this.qb.where; + this.or_where = this.qb.or_where; + this.where_in = this.qb.where_in; + this.or_where_in = this.qb.or_where_in; + this.where_not_in = this.qb.where_not_in; + this.or_where_not_in = this.qb.or_where_not_in; + this.like = this.qb.like; + this.not_like = this.qb.not_like; + this.or_like = this.qb.or_like; + this.or_not_like = this.qb.or_not_like; + this.from = this.qb.from; + this.join = this.qb.join; + this.select = this.qb.select; + this.select_min = this.qb.select_min; + this.select_max = this.qb.select_max; + this.select_avg = this.qb.select_avg; + this.select_sum = this.qb.select_sum; + this.distinct = this.qb.distinct; + this.group_by = this.qb.group_by; + this.having = this.qb.having; + this.or_having = this.qb.or_having; + this.order_by = this.qb.order_by; + this.limit = this.qb.limit; + this.offset = this.qb.offset; + this._last_query = this.qb._last_query; + this.last_query = this.qb._last_query; + + // QueryBuilder "Query Execution" methods: + this.count = function(table, callback) { + var sql = this.qb.count(table); - return this._like(field, match, 'OR ', side, ' NOT'); - }; - - this._like = function(field, match, type, side, not) { - match = match || ''; - type = type || 'AND '; - side = side || 'both'; - not = not || ''; - - if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - field_array = {}; - field_array[field] = match; - field = field_array; - } - - for(k in field) { - v = field[k]; - k = protectIdentifiers(k.trim()); - - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - - this._where(like_statement,null,type,false); - } - - return that; - }; - - this.count = function(table, responseCallback) { - if (typeof table === 'string') { - trackAliases(table); - this.from(table); - } - var sql = this.query('SELECT COUNT(*) AS ' + protectIdentifiers('count') - + buildFromClause() - + buildJoinString() - + buildWhereClause()); - connection.query(sql, function(err, res) { if (err) - responseCallback(err, null); + callback(err, null); else - responseCallback(null, res[0]['count']); + callback(null, res[0]['count']); }); - resetQuery(sql); return that; }; - this.from = function(from) { - if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { - from = [from]; - } - for (var i in from) { - var val = from[i]; - - if (val.indexOf(',') !== -1) { - var objects = val.split(','); - for (var j in objects) { - var v = objects[j].trim(); - - trackAliases(v); - - fromArray.push(protectIdentifiers(v, true)); - } - } - else { - val = val.trim(); - - // Extract any aliases that might exist. We use this information - // in the protectIdentifiers function to know whether to add a table prefix - trackAliases(val); - - fromArray.push(protectIdentifiers(val, true)); - } + this.get = function(table,callback) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; } - - return that; - } - - this.join = function(tableName, relation, direction) { - direction = (!direction || typeof direction !== 'string' ? '' : direction); - - var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase(); - if (valid_directions.indexOf(direction) === -1) { - direction = ''; - } - else { - direction += ' '; - } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); } - - trackAliases(tableName); - - var match; - if (match = relation.match(/([\w\.]+)([\W\s]+)(.+)/)) { - match[1] = protectIdentifiers(match[1]); - match[3] = protectIdentifiers(match[3]); - - relation = match[1] + match[2] + match[3]; - } - - join = direction + 'JOIN ' + protectIdentifiers(tableName, true) + ' ON ' + relation; - - joinArray.push(join); - return that; - }; - this.select = function(select,escape) { - if (typeof escape !== 'boolean') escape = true; - - if (typeof select === 'string') { - select = select.split(','); - } - - for (var i in select) { - var val = select[i].trim(); - - if(val !== '') { - selectArray.push(protectIdentifiers(val)); - } - } + var sql = this.qb.get(table,callback); + this.resetQuery(sql); + connection.query(sql, callback); return that; }; - this.select_min = function(select,alias) { - return this._min_max_avg_sum(select,alias,'MIN'); + this.get_where = function(table,where,callback) { + var sql = this.qb.get_where(table,where); + this.resetQuery(sql); + connection.query(sql, callback); + return that; }; - this.select_max = function(select,alias) { - return this._min_max_avg_sum(select,alias,'MAX'); + this.insert = function(table,set,callback,verb,suffix) { + var sql = this.qb.insert(table,set,callback,verb,suffix); + this.resetQuery(sql); + connection.query(sql); + return that; }; - this.select_avg = function(select,alias) { - return this._min_max_avg_sum(select,alias,'AVG'); + this.insert_ignore = function(table,set,callback) { + var sql = this.qb.insert_ignore(table,set,callback); + this.resetQuery(sql); + connection.query(sql, callback); + return that; }; - this.select_sum = function(select,alias) { - return this._min_max_avg_sum(select,alias,'SUM'); + this.insert_batch = function(table,set,callback) { + var sql = this.qb.insert_batch(table,set,callback); + this.resetQuery(sql); + connection.query(sql, callback); + return that; }; - this._min_max_avg_sum = function(select,alias,type) { - select = select || ''; - alias = alias || ''; - type = type || 'MAX'; - - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return that; + this.update = function(table,set,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; } - - type = type.toUpperCase(); - - if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return that; + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'update' call!"); } - if (alias == '') { - alias = createAliasFromTable(select.trim()); - } - - sql = type + '(' + protectIdentifiers(select.trim()) + ') AS ' + alias; - - selectArray.push(sql); - - return that; - } - - this.distinct = function() { - distinctClause = 'DISTINCT '; + var sql = this.qb.update(table,set,where,callback); + this.resetQuery(sql); + connection.query(sql, callback); return that; }; - - this.comma_separated_arguments = function(set) { - var clause = ''; - if (Object.prototype.toString.call(set) === '[object Array]') { - clause = set.join(', '); - } - else if (typeof set === 'string') { - clause = set; + + this.delete = function(table,callback) { + var sql = this.delete(table,callback); + + if (typeof table === 'function') { + callback = table; } - return clause; - }; - - this.group_by = function(set) { - groupByClause = this.comma_separated_arguments(set); - return that; - }; - - this.having = function(set) { - havingClause = this.comma_separated_arguments(set); + this.resetQuery(sql); + connection.query(sql, callback); return that; }; - this.order_by = function(set) { - orderByClause = this.comma_separated_arguments(set); - return that; - }; - - this.limit = function(newLimit, newOffset) { - if (typeof newLimit === 'number') { - limitClause = newLimit; + var initializeConnectionSettings = function () { + if(settings.server) { + settings.host = settings.server; } - if (typeof newOffset === 'number') { - offsetClause = newOffset; + if(settings.username) { + settings.user = settings.username; } - return that; - }; - this.ping = function() { - connection.ping(); - return that; - }; - - this.insert = function(tableName, dataSet, responseCallback, verb, querySuffix) { - if (typeof verb === 'undefined') { - var verb = 'INSERT'; - } - if (Object.prototype.toString.call(dataSet) !== '[object Array]') { - if (typeof querySuffix === 'undefined') { - var querySuffix = ''; - } - else if (typeof querySuffix !== 'string') { - var querySuffix = ''; - } - if (typeof tableName === 'string') { - - var combinedQueryString = verb + ' into ' + protectIdentifiers(tableName) - + buildDataString(dataSet, ', ', 'SET'); - - if (querySuffix != '') { - combinedQueryString = combinedQueryString + ' ' + querySuffix; - } - - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); - } + if (!settings.host) { + throw new Error('Unable to start ActiveRecord - no server given.'); } - else { - doBatchInsert(verb, tableName, dataSet, responseCallback); + if (!settings.port) { + settings.port = 3306; } - return that; - }; - - this.insert_ignore = function(tableName, dataSet, responseCallback, querySuffix) { - return this.insert(tableName, dataSet, responseCallback, 'INSERT IGNORE', querySuffix); - }; - - var doBatchInsert = function(verb, tableName, dataSet, responseCallback) { - if (Object.prototype.toString.call(dataSet) !== '[object Array]') { - throw new Error('Array of objects must be provided for batch insert!'); + if (!settings.user) { + settings.user = ''; } - - if (dataSet.length == 0) return false; - - var map = []; - var columns = []; - - for (var key in dataSet[0]) { - if (dataSet[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(protectIdentifiers(key)); - } - } + if (!settings.password) { + settings.password = ''; } - - for (var i = 0; i < dataSet.length; i++) { - (function(i) { - var row = []; - for (var key in dataSet[i]) { - if (dataSet[i].hasOwnProperty(key)) { - row.push(that.escape(dataSet[i][key])); - } - } - if (row.length != columns.length) { - throw new Error('Cannot use batch insert into ' + tableName + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); - } - map.push('(' + row.join(',') + ')'); - })(i); + if (!settings.database) { + throw new Error('Unable to start ActiveRecord - no database given.'); } - that.query(verb + ' INTO ' + protectIdentifiers(tableName) + ' (' + columns.join(', ') + ') VALUES' + map.join(','), responseCallback); - return that; + return settings; }; - this.get = function(table, callback) { - if (typeof table !== 'function') { - trackAliases(table); - this.from(table); - } - else { - if (fromArray.length == 0) { - throw new Error('You have not specified any tables to select from!'); - return that; - } - else { - callback = table; - } - } - - var sql = compileSelect(); - - connection.query(sql, callback); - resetQuery(sql); - - return that; - }; - - this.get_where = function(table, where, callback) { - if (table !== '') { - this.from(table); - } - - if (where !== null) { - this.where(where); - } - - var sql = compileSelect(); - - connection.query(sql, callback); - resetQuery(sql); - - return that; - }; - - this.update = function(tableName, newData, responseCallback) { - if (typeof tableName === 'string') { - var combinedQueryString = 'UPDATE ' + protectIdentifiers(tableName) - + buildDataString(newData, ', ', 'SET') - + buildWhereClause() - + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); - - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); - } - - return that; - }; - - this.escape = function(str) { - return connection.escape(str); - }; + var connection; + var connectionSettings; + var pool; + + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } else { + connectionSettings = initializeConnectionSettings(); + connection = new mysql.createConnection(connectionSettings); + } - this.delete = function(tableName, responseCallback) { - if (typeof tableName !== 'function') { - trackAliases(tableName); - this.from(tableName); - } - else { - if (fromArray.length == 0) { - throw new Error('You have not specified any tables to delete from!'); - return that; - } - else { - responseCallback = tableName; - } - } - - var combinedQueryString = 'DELETE' - + buildFromClause() - + buildWhereClause() - + (limitClause !== -1 ? ' LIMIT ' + limitClause : ''); - - connection.query(combinedQueryString, responseCallback); - resetQuery(combinedQueryString); - - return that; - }; + if (settings.charset) { + connection.query('SET NAMES ' + settings.charset); + } - this._last_query = function() { - return lastQuery; - }; + this.connectionSettings = function() { return connectionSettings; }; + this.connection = function() { return connection; }; this.query = function(sqlQueryString, responseCallback) { connection.query(sqlQueryString, responseCallback); - resetQuery(sqlQueryString); + this.resetQuery(sqlQueryString); return that; }; @@ -926,6 +237,15 @@ var Adapter = function(settings) { this.releaseConnection = function() { pool.releaseConnection(connection); }; + + this.ping = function() { + connection.ping(); + return that; + }; + + this.escape = function(str) { + return connection.escape(str); + }; var reconnectingTimeout = false; diff --git a/lib/query_builder.js b/lib/query_builder.js new file mode 100755 index 0000000..0d40b5a --- /dev/null +++ b/lib/query_builder.js @@ -0,0 +1,978 @@ +var QueryBuilder = function() { + + // ------------------------------ GENERIC FUNCTIONS ------------------------------// + var array_values = function(item) { + var keys = Object.keys(item); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = item[keys[i]]; + } + return values; + } + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + var trackAliases = function(qb,table) { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (var i in table) { + var t = table[i]; + trackAliases(qb,t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return trackAliases(qb,table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + alias = table.slice(table.lastIndexOf(' ')).trim(); + + // Store the alias, if it doesn't already exist + if(qb.aliasedTables.indexOf(table) == -1) { + qb.aliasedTables.push(table); + } + } + } + + var createAliasFromTable = function(item) { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + }; + + var escapeIdentifiers = function(item) { + if (!item || item === '*') { + return item; + } + + var str; + if (item.indexOf('.') !== -1) { + str = '`' + item.replace(/\./g,'`.`') + '`'; + } + else { + str = '`' + item + '`'; + } + + // remove duplicates if the user already included the escape + return str.replace(/[`]+/g,'`'); + } + + var protectIdentifiers = function(qb,item,protect_identifiers) { + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + var escaped_array = {}; + + for (k in item) { + var v = item[k]; + escaped_array[protectIdentifiers(qb,k)] = protectIdentifiers(qb,v); + } + + return escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + item = item.replace(/\s+/g, ' '); + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (item.match(/\sAS\s/ig)) { + var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + var alias = (protect_identifiers ? item.substr(alias_index,4) + escapeIdentifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else if (item.indexOf(' ') !== -1) { + var alias_index = item.indexOf(' '); + + var alias = (protect_identifiers && ! hasOperator(item.substr(alias_index + 1)) ? ' ' + escapeIdentifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else { + alias = ''; + } + + // This is basically a bug fix for queries that use MAX, MIN, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + return item; + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + parts = item.split('.'); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (qb.aliasedTables.indexOf(parts[0]) !== -1) { + if (protect_identifiers === true) { + for (var key in parts) { + var val = parts[key]; + if (val !== '*') { + parts[key] = escapeIdentifiers(val); + } + } + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = escapeIdentifiers(item); + } + + return item + alias; + } + if (protect_identifiers === true) { + item = escapeIdentifiers(item); + } + + return item + alias; + }; + + var hasOperator = function (str) { + if(typeof str === 'string') { + if(!str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)) { + return false; + } + } + return true; + } + + var qb_escape = function(str) { + if (typeof str === 'string') { + str = "'" + this.escape(str) + "'"; + } + else if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } + else if (str === null) { + str = 'NULL'; + } + + return str; + } + + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + var buildWhereClause = function(qb) { + var sql = ''; + if(qb.whereArray.length > 0) { + sql += " WHERE "; + } + sql += qb.whereArray.join(" "); + return sql; + } + + var buildFromClause = function(qb) { + var sql = ''; + if(qb.fromArray.length > 0) { + sql += " FROM "; + } else { + throw new Error("You have not provided any tables, views, or store procedures for this query!!"); + } + sql += '(' + qb.fromArray.join(', ') + ')'; + return sql; + }; + + var buildJoinString = function(qb) { + var sql = ''; + sql += qb.joinArray.join(' '); + if(sql.length > 0) sql = ' ' + sql; + return sql; + }; + + var buildGroupByClause = function(qb) { + if (qb.groupByArray.length <= 0) return ''; + + var sql = ' GROUP BY '; + sql += qb.groupByArray.join(', '); + return sql; + } + + var buildHavingClause = function(qb) { + if (qb.havingArray.length <= 0) return ''; + + var sql = ' HAVING '; + sql += qb.havingArray.join(' '); + return sql; + } + + var buildOrderByClause = function(qb) { + if (qb.orderByArray.length <= 0) return ''; + + var sql = ' ORDER BY '; + sql += qb.orderByArray.join(', '); + + if (qb.orderByDir !== false) { + sql += (qb.orderByDir == 'desc' ? ' DESC' : ' ASC'); + } + + return sql; + } + + var buildLimitClause = function(sql, limit, offset) { + if (!limit) return sql; + + var sql = ' '; + + if (offset == 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, '') + 'LIMIT ' + offset + limit; + } + + var compileSelect = function(qb) { + var sql = 'SELECT ' + qb.distinctClause; + if (qb.selectArray.length === 0) { + sql += '*'; + } else { + sql += qb.selectArray.join(', '); + } + + sql += buildFromClause(qb) + + buildJoinString(qb) + + buildWhereClause(qb) + + buildGroupByClause(qb) + + buildHavingClause(qb) + + buildOrderByClause(qb); + + sql = buildLimitClause(sql,qb.limitTo,qb.offsetVal); + return sql; + } + + + // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// + return { + whereArray: [], + whereInArray: [], + fromArray: [], + joinArray: [], + selectArray: [], + setArray: [], + orderByArray: [], + orderByDir: false, + groupByArray: [], + havingArray: [], + limitTo: false, + offsetVal: false, + joinClause: [], + lastQueryString: '', + distinctClause: '', + aliasedTables: [], + + resetQuery: function(newLastQuery) { + this.whereArray = []; + this.whereInArray = []; + this.fromArray = []; + this.joinArray = []; + this.selectArray = []; + this.setArray = []; + this.orderByArray = []; + this.orderByDir = false; + this.groupByArray = []; + this.havingArray = []; + this.limitTo = false; + this.offsetVal = false; + this.joinClause = []; + this.distinctClause = ''; + this.lastQueryString = (typeof newLastQuery === 'string' ? newLastQuery : ''); + this.aliasedTables = []; + }, + + where: function(key, value, isRaw) { + isRaw = (typeof isRaw === 'boolean' ? isRaw : false); + value = value || null; + + var escape = (isRaw ? false : true); + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + }, + + or_where: function(key, value, isRaw) { + isRaw = (typeof isRaw === 'boolean' ? isRaw : false); + value = value || null; + + var escape = (isRaw ? false : true); + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + }, + + where_in: function(key, values) { + return this._where_in(key,values,false,'AND '); + }, + + or_where_in: function(key, values) { + return this._where_in(key,values,false,'OR '); + }, + + where_not_in: function(key, values) { + return this._where_in(key,values,true,'AND '); + }, + + or_where_not_in: function(key, values) { + return this._where_in(key,values,true,'OR '); + }, + + _where: function(key, value, type, escape) { + value = value || null; + type = type || 'AND '; + escape = (typeof escape === 'boolean' ? escape : true); + + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + key_array = {}; + key_array[key] = value; + key = key_array; + } + + for (var k in key) { + var v = key[k]; + + if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { + return this._where_in(k,v,false,type); + } + + var prefix = (this.whereArray.length == 0 ? '' : type); + + if (v === null && !hasOperator(k)) { + k += ' IS NULL'; + } + + if (v !== null) { + if (escape === true) { + k = protectIdentifiers(this,k); + v = ' ' + qb_escape(v); + } + + if (!hasOperator(k)) { + k += ' ='; + } + } + else { + k = protectIdentifiers(this,k); + } + + if (v) { + this.whereArray.push(prefix+k+v); + } + else { + this.whereArray.push(prefix+k); + } + } + + return this; + }, + + _where_in: function(key, values, not, type) { + key = key || null; + values = values || []; + type = type || 'AND '; + not = (not ? ' NOT' : ''); + + if(key === null || values.length === 0) return this; + + // Values must be an array... + if(Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { + values = [values]; + } + + for (var i in values) { + this.whereInArray.push(qb_escape(values[i])); + } + + prefix = (this.whereArray.length == 0 ? '' : type); + where_in = prefix + protectIdentifiers(this,key) + not + " IN (" + this.whereInArray.join(', ') + ") "; + this.whereArray.push(where_in); + + // reset the array for multiple calls + this.whereInArray = []; + return this; + }, + + like: function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'AND ', side, ''); + }, + + not_like: function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'AND ', side, ' NOT'); + }, + + or_like: function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'OR ', side, ''); + }, + + or_not_like: function(field, match, side) { + match = match || ''; + side = side || 'both'; + + return this._like(field, match, 'OR ', side, ' NOT'); + }, + + _like: function(field, match, type, side, not) { + match = match || ''; + type = type || 'AND '; + side = side || 'both'; + not = not || ''; + + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + field_array = {}; + field_array[field] = match; + field = field_array; + } + + for(k in field) { + v = field[k]; + k = protectIdentifiers(this,k.trim()); + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + + this._where(like_statement,null,type,false); + } + + return this; + }, + + count: function(table, responseCallback) { + if (typeof table === 'string') { + trackAliases(this,table); + this.from(table); + } + var sql = 'SELECT COUNT(*) AS ' + protectIdentifiers(this,'count') + + buildFromClause(this) + + buildJoinString(this) + + buildWhereClause(this) + + return sql; + }, + + from: function(from) { + if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { + from = [from]; + } + for (var i in from) { + var val = from[i]; + + if (val.indexOf(',') !== -1) { + var objects = val.split(','); + for (var j in objects) { + var v = objects[j].trim(); + + trackAliases(this,v); + + this.fromArray.push(protectIdentifiers(this,v,true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protectIdentifiers function to know whether to add a table prefix + trackAliases(this,val); + + this.fromArray.push(protectIdentifiers(this,val,true)); + } + } + + return this; + }, + + join: function(table, relation, direction) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } + + relation = relation || ''; + direction = (!direction || typeof direction !== 'string' ? '' : direction); + + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + + if (direction != '') { + direction = direction.toUpperCase(); + if (valid_directions.indexOf(direction) === -1) { + direction = ''; + } + else { + if (typeof relation !== 'string') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } + direction += ' '; + } + } + + trackAliases(this,table); + + var match; + if (match = relation.match(/([\w\.]+)([\W\s]+)(.+)/)) { + match[1] = protectIdentifiers(this,match[1]); + match[3] = protectIdentifiers(this,match[3]); + + relation = match[1] + match[2] + match[3]; + } + + join = direction + 'JOIN ' + protectIdentifiers(this,table, true) + (relation !== '' ? ' ON ' + relation : ' ' + relation); + + this.joinArray.push(join); + return this; + }, + + select: function(select,escape) { + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); + } + } + else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); + } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + + if (typeof escape !== 'boolean') escape = true; + + if (typeof select === 'string') { + select = select.split(','); + } + + for (var i in select) { + var val = select[i].trim(); + + if(val !== '') { + this.selectArray.push(protectIdentifiers(this,val,escape)); + } + } + return this; + }, + + select_min: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MIN'); + }, + + select_max: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MAX'); + }, + + select_avg: function(select,alias) { + return this._min_max_avg_sum(select,alias,'AVG'); + }, + + select_sum: function(select,alias) { + return this._min_max_avg_sum(select,alias,'SUM'); + }, + + _min_max_avg_sum: function(select,alias,type) { + select = select || ''; + alias = alias || ''; + type = type || 'MAX'; + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } + + type = type.toUpperCase(); + + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } + + if (alias == '') { + alias = createAliasFromTable(select.trim()); + } + + sql = type + '(' + protectIdentifiers(this,select.trim()) + ') AS ' + alias; + + this.selectArray.push(sql); + + return this; + }, + + distinct: function() { + this.distinctClause = 'DISTINCT '; + return this; + }, + + group_by: function(by) { + if (typeof by === 'string') { + by = by.split(','); + } + + for (key in by) { + val = by[key].trim(); + + if (val !== '') { + this.groupByArray.push(protectIdentifiers(this,val)); + } + } + return this; + }, + + having: function(key, value, escape) { + return this._having(key, value, 'AND ', escape); + }, + + or_having: function(key,value,escape) { + return this._having(key, value, 'OR ', escape); + }, + + _having: function(key, value, type, escape) { + if (Object.prototype.toString.call(from) !== Object.prototype.toString.call({})) { + key_array = {}; + key_array[key] = value; + key = key_array; + } + + for (var k in key) { + var v = key[k]; + prefix = (this.havingArray.length == 0 ? '' : type); + + if (escape === true) { + k = protectIdentifiers(this,k); + } + + if (!hasOperator(k)) { + k += ' = '; + } + + if (v != '') { + v = ' ' + qb_escape(v); + } + + this.havingArray.push(prefix + k + k); + } + + return this; + }, + + order_by: function(orderby, direction) { + direction = direction || ''; + var rand_word = ' RAND()'; + + if (direction.toLowerCase() == 'random') { + orderby = ''; // Random results want or don't need a field name + direction = rand_word; + } + else if (direction.trim() != '') { + direction = (direction.trim().match(/^(ASC|DESC)$/i) ? ' ' + direction.toUpperCase() : ' ASC'); + } + + + if (orderby.indexOf(',') !== -1) { + temp = []; + var parts = orderby.split(','); + for (var i in parts) { + var part = parts[i].trim(); + + if (this.aliasedTables.indexOf(part) === -1) { + part = protectIdentifiers(this,part.trim()); + } + + temp.push(part); + } + + orderby = temp.join(', '); + } + else if (direction != rand_word) { + if (!orderby.match(/\s+(ASC|DESC)\s*/i)) { + orderby = protectIdentifiers(this,orderby); + } else { + var parts = orderby.match(/(.+)\s(ASC|DESC)/i); + if (parts.length === 3) { + orderby = protectIdentifiers(this,parts[1]) + ' ' + parts[2].toUpperCase(); + } + } + } + + orderby_statement = orderby + direction; + this.qb.orderByArray.push(orderby_statement); + + return this; + }, + + limit: function(limit, offset) { + if (!(typeof limit).match(/^(string|number)$/) || !limit.match(/^\d+$/)) { + return this; + } + + if (!(typeof offset).match(/^(string|number)$/) || !offset.match(/^\d+$/)) { + offset = 0; + } + + this.limitTo = parseInt(limit); + this.offsetVal = parseInt(offset); + + return this; + }, + + offset: function(offset) { + if ((typeof offset).match(/^(string|number)$/) && offset.match(/^\d+$/)) { + this.offsetVal = offset; + } + return this; + }, + + set: function(key, value, escape) { + escape = escape || true; + value = value || ''; + + if(Object.prototype.toString.call(value) !== Object.prototype.toString.call({})) { + key_array = {}; + key_array[key] = value; + key = key_array; + } + + for (var i in key) { + var v = key[i]; + + if (escape === false) { + this.setArray[protectIdentifiers(this,k)] = v; + } else { + this.setArray[protectIdentifiers(this,k,false)] = this.escape(v); + } + } + + return this; + }, + + insert: function(table, set, callback, ignore, suffix) { + table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : suffix); + + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + return this.insert_batch(table, set, callback); + } + + if (set !== null) { + this.set(set); + } + + if (table == '') { + if (this.fromArray.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.fromArray[0]; + } else { + this.fromArray = []; + this.from(table); + } + + var keys = Object.keys(this.setArray); + var values = array_values(this.setArray); + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + ' INTO ' + buildFromClause(this) + " (" + keys.join(', ') + ") VALUES (" + values.join(', ') + ")"; + }, + + insert_ignore: function(table, set, callback, suffix) { + return this.insert(table, set, callback, true, suffix); + }, + + insert_batch: function(table,set,callback) { + table = table || ''; + set = set || null; + + if (table == '') { + if (this.fromArray.length === 0) { + throw new Error("You have not set any tables to insert into."); + } + + table = this.fromArray[0]; + } else { + this.fromArray = []; + this.from(table); + } + + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error('Array of objects must be provided for batch insert!'); + } + + if (set.length == 0) return false; + + var map = []; + var columns = []; + + for (var key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(protectIdentifiers(this,key)); + } + } + } + + for (var i = 0; i < set.length; i++) { + (function(i) { + var row = []; + for (var key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(this.escape(set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error('Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(',') + ')'); + })(i); + } + + return 'INSERT INTO ' + buildFromClause(this) + ' (' + keys.join(', ') + ') VALUES' + values.join(','); + }, + + get: function(table, callback) { + if (typeof table !== 'function') { + trackAliases(this,table); + this.from(table); + } + else { + if (this.fromArray.length == 0) { + throw new Error('You have not specified any tables to select from!'); + } + } + return compileSelect(this); + }, + + get_where: function(table, where) { + where = where || null; + + if (typeof table !== 'string') { + throw new Error('You must specify a table as the first parameter when using the get_where method!'); + return this; + } else { + trackAliases(this,table); + this.from(table); + } + + if (where !== null) { + this.where(where); + } + + return compileSelect(this); + }, + + update: function(table, set, where, callback) { + set = set || null; + + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + if (set !== null) { + this.set(set); + } + + if (this.setArray.length == 0) { + throw new Error("You must set a some field value pairs to update using the set method or in object for in the second parameter of the update method!"); + } + + if (table == '') { + if (this.fromArray.length === 0) { + throw new Error("You have not set any tables to update!"); + } + + table = this.fromArray[0]; + } else { + this.fromArray = []; + this.from(table); + + table = this.fromArray[0]; + } + + if (where != null) { + this.where(where); + } + + var sql = this._update(table, this.setArray, this.whereArray, this.orderByArray, this.limitTo); + return sql; + }, + + _update: function(table,values,where,orderby,limit) { + orderby = orderby || []; + limit = limit || false; + + var valstr = []; + for (var i in values) { + valstr.push(key + ' = ' + values[i]); + } + + limit = (!limit ? '' : ' LIMIT ' + limit); + orderby = (orderby.length >= 1 ? ' ORDER BY ' + orderby.join(', ') : ''); + sql = 'UPDATE ' + table + " SET " + valstr.join(', '); + sql += (where != '' && where.length >= 1 ? ' WHERE ' + where.join(' ') : ''); + sql += orderby + limit; + return sql; + }, + + delete: function(table, callback) { + if (typeof table !== 'function') { + trackAliases(this,table); + this.from(table); + } + else { + if (fromArray.length == 0) { + throw new Error('You have not specified any tables to delete from!'); + return this; + } + } + + var sql = 'DELETE' + buildFromClause(this) + buildWhereClause(this); + return buildLimitClause(sql,limitTo,offsetVal); + }, + + _last_query: function() { + return this.lastQueryString; + } + } +}; + +module.exports = QueryBuilder; \ No newline at end of file diff --git a/package.json b/package.json old mode 100644 new mode 100755 index f80115b..1bca5e7 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.8.4", "author": "Martin Tajur ", "description": "MySQL ActiveRecord pattern implementation on top of the mysql module.", + "scripts": { + "test": "make test" + }, "homepage": "https://github.com/martintajur/node-mysql-activerecord", "repository": { "type": "git", @@ -14,10 +17,12 @@ "Daehyub Kim " ], "dependencies": { - "mysql": "~2.1" + "mysql": "~2.1", + "chai": "~1.4.2", + "mocha": "*" }, "main" : "./", "engines": { "node": "*" } -} +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100755 index 0000000..d98cb28 --- /dev/null +++ b/test.js @@ -0,0 +1,63 @@ +// ***************************************************************************** +// Setup MySQL DB +// ***************************************************************************** +var mysql_ar = require('index.js'); +var pool = new mysql_ar.Pool({ + server : 'localhost', + username : 'scripts', + password : '2B||!2B', + database : 'iti', + insecureAuth: true +}); +pool.getNewAdapter(function(db) { + db.query('SELECT 1', function(err,rows) { + if(err != undefined) { + logger.error("Could not connect to DB! " + err); + process.exit(1); + } + db.releaseConnection(); + }); +}); + +pool.getNewAdapter(function(db) { + //db.like('department_id','7','after').like('first_name', 'Joh', 'after').where('is_current_employee',true).get('_employees e', function(err, rows) { + //db.where('is_current_employee',true).where('first_name','Kyle').get('_employees e', function(err, rows) { + //db.select('first_name as fname').where({is_current_employee: true,first_name: 'Kyle'}).get('_employees', function(err, rows) { + /* db.from('_employees e') + .from('_departments d') + .where_in('department_id',[1,2,3,4]) + .or_where_in('department_id',[5,6,7,'foobar']) + .where_not_in('e.hrid',123) + .join('iti._acl_super_admins asa','asa.emp_id=e.id','left') + .get(function(err, rows) { + db.releaseConnection(); + if(err != undefined) { + console.log("ERROR: " + err); + console.log(db._last_query()); + process.exit(1); + } else { + //console.dir(rows); + console.log(db._last_query()); + } + }); */ + //db.select_sum('e.id','total_hrid').join('_departments d','d.id = e.department_id').get_where('_employees e',{'e.id':2380, 'e.is_current_employee': true}, function(err, rows) { + /*db.select_sum('e.id','total_hrid') + .join('_departments d','d.id = e.department_id','left') + .select(['first_name','last_name']) + .where('termination_date',null) + .get_where('_employees e',{ + 'e.id':2380, + 'e.is_current_employee': true + }, function(err, row) {*/ + db.order_by('id asc').get('_employees e', function(err, rows) { + db.releaseConnection(); + if(err != undefined) { + console.log("ERROR: " + err); + console.log(db._last_query()); + process.exit(1); + } else { + //console.dir(rows); + console.log(db._last_query()); + } + }); +}); \ No newline at end of file From da257542ab5d9f771ee7782e3504a316a0d5cb41 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 25 Jul 2014 14:23:05 -0400 Subject: [PATCH 016/200] Removed the test folder from gitignore. Removed uneccessary test.js file. --- .gitignore | 1 - test.js | 63 ---------------------- test/test-join.js | 43 +++++++++++++++ test/tests-from.js | 91 +++++++++++++++++++++++++++++++ test/tests-general.js | 10 ++++ test/tests-select.js | 122 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 266 insertions(+), 64 deletions(-) delete mode 100755 test.js create mode 100755 test/test-join.js create mode 100755 test/tests-from.js create mode 100755 test/tests-general.js create mode 100755 test/tests-select.js diff --git a/.gitignore b/.gitignore index 8eb594e..8bb42f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .DS_Store node_modules npm-debug* -test \ No newline at end of file diff --git a/test.js b/test.js deleted file mode 100755 index d98cb28..0000000 --- a/test.js +++ /dev/null @@ -1,63 +0,0 @@ -// ***************************************************************************** -// Setup MySQL DB -// ***************************************************************************** -var mysql_ar = require('index.js'); -var pool = new mysql_ar.Pool({ - server : 'localhost', - username : 'scripts', - password : '2B||!2B', - database : 'iti', - insecureAuth: true -}); -pool.getNewAdapter(function(db) { - db.query('SELECT 1', function(err,rows) { - if(err != undefined) { - logger.error("Could not connect to DB! " + err); - process.exit(1); - } - db.releaseConnection(); - }); -}); - -pool.getNewAdapter(function(db) { - //db.like('department_id','7','after').like('first_name', 'Joh', 'after').where('is_current_employee',true).get('_employees e', function(err, rows) { - //db.where('is_current_employee',true).where('first_name','Kyle').get('_employees e', function(err, rows) { - //db.select('first_name as fname').where({is_current_employee: true,first_name: 'Kyle'}).get('_employees', function(err, rows) { - /* db.from('_employees e') - .from('_departments d') - .where_in('department_id',[1,2,3,4]) - .or_where_in('department_id',[5,6,7,'foobar']) - .where_not_in('e.hrid',123) - .join('iti._acl_super_admins asa','asa.emp_id=e.id','left') - .get(function(err, rows) { - db.releaseConnection(); - if(err != undefined) { - console.log("ERROR: " + err); - console.log(db._last_query()); - process.exit(1); - } else { - //console.dir(rows); - console.log(db._last_query()); - } - }); */ - //db.select_sum('e.id','total_hrid').join('_departments d','d.id = e.department_id').get_where('_employees e',{'e.id':2380, 'e.is_current_employee': true}, function(err, rows) { - /*db.select_sum('e.id','total_hrid') - .join('_departments d','d.id = e.department_id','left') - .select(['first_name','last_name']) - .where('termination_date',null) - .get_where('_employees e',{ - 'e.id':2380, - 'e.is_current_employee': true - }, function(err, row) {*/ - db.order_by('id asc').get('_employees e', function(err, rows) { - db.releaseConnection(); - if(err != undefined) { - console.log("ERROR: " + err); - console.log(db._last_query()); - process.exit(1); - } else { - //console.dir(rows); - console.log(db._last_query()); - } - }); -}); \ No newline at end of file diff --git a/test/test-join.js b/test/test-join.js new file mode 100755 index 0000000..348ef31 --- /dev/null +++ b/test/test-join.js @@ -0,0 +1,43 @@ +var should = require('chai').should(); +var expect = require('chai').expect; + +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('join()', function() { + it('should exist', function() { + should.exist(qb.join); + }); + it('should be a function', function() { + qb.join.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('joinArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.joinArray.should.be.empty; + }); + it('should require a string to be passed as first parameter', function() { + var invalid_match = /must provide a table/; + expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); + expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); + expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); + expect(function() { qb.join(false); }, 'false provided').to.throw(Error, invalid_match); + expect(function() { qb.join({}); }, 'object provided').to.throw(Error, invalid_match); + expect(function() { qb.join([]); }, 'empty array provided').to.throw(Error, invalid_match); + expect(function() { qb.join(''); }, 'empty string provided').to.throw(Error, invalid_match); + expect(function() { qb.join(' '); }, 'string of spaces provided').to.throw(Error, invalid_match); + expect(function() { qb.join('blah');}, 'valid string provided').to.not.throw(Error); + expect(function() { qb.join('blah');}, 'valid string provided').to.not.throw(Error); + }) + it('should except single item and add it to join array as basic join and escape item', function() { + qb.resetQuery(); + qb.join('universe'); + qb.joinArray.should.eql(['JOIN `universe` ']); + }); + it('should except single item with alias and add it to join array as basic join and escape each part', function() { + qb.resetQuery(); + qb.join('universe u'); + qb.joinArray.should.eql(['JOIN `universe` `u` ']); + }); +}); \ No newline at end of file diff --git a/test/tests-from.js b/test/tests-from.js new file mode 100755 index 0000000..ee64b24 --- /dev/null +++ b/test/tests-from.js @@ -0,0 +1,91 @@ +var should = require('chai').should(); +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('from()', function() { + it('should exist', function() { + should.exist(qb.from); + }); + it('should be a function', function() { + qb.from.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('fromArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.fromArray.should.be.empty; + }); + it('should add an item to an array and escape it properly', function() { + qb.from('universe'); + qb.fromArray.should.eql(['`universe`']); + }); + it('should accept a comma-delimited string of items and trim and escape each properly', function() { + qb.resetQuery(); + qb.from('universe,galaxy , star_system, planet'); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should have an empty array after resetting', function() { + qb.resetQuery(); + qb.fromArray.should.be.empty; + }); + it('should be allowed to be called multiple times to add multiple items to the from array', function() { + qb.resetQuery(); + qb.from('universe').from('galaxy').from('star_system').from('planet'); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should accept an array of items and add them individually to the from array', function() { + qb.resetQuery(); + qb.from(['universe','galaxy','star_system','planet']); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should not double-escape an item', function() { + qb.resetQuery(); + qb.from('`do`'); + qb.fromArray.should.eql(['`do`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items', function() { + qb.resetQuery(); + qb.from(['`universe`','`galaxy`','`star_system`']); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', function() { + qb.resetQuery(); + qb.from(['`universe`','galaxy','`star_system`']); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should allow for aliases and it should escape them properly', function() { + qb.resetQuery(); + qb.from('universe u'); + qb.fromArray.should.eql(['`universe` `u`']); + }); + it('should allow for the word AS to be used to alias an item', function() { + qb.resetQuery(); + qb.from('universe as u'); + qb.fromArray.should.eql(['`universe` as `u`']); + }); + it('should allow for an array of item + aliases and it should escape them all properly', function() { + qb.resetQuery(); + qb.from(['universe u', 'galaxy g']); + qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', function() { + qb.resetQuery(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', function() { + qb.resetQuery(); + qb.from(['`universe` u', 'galaxy `g`']); + qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should add aliases to alias-tracking array', function() { + qb.resetQuery(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.aliasedTables.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', function() { + qb.resetQuery(); + qb.from(['universe u, galaxy g']); + qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + }); +}); \ No newline at end of file diff --git a/test/tests-general.js b/test/tests-general.js new file mode 100755 index 0000000..9562139 --- /dev/null +++ b/test/tests-general.js @@ -0,0 +1,10 @@ +var should = require('chai').should(); + +describe('QueryBuilder', function() { + var QueryBuilder = require('../lib/query_builder.js'); + + it('actually exists and can be initialized', function() { + var qb = new QueryBuilder(); + qb.should.be.instanceOf(Object); + }); +}); \ No newline at end of file diff --git a/test/tests-select.js b/test/tests-select.js new file mode 100755 index 0000000..75d88b8 --- /dev/null +++ b/test/tests-select.js @@ -0,0 +1,122 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('select()', function() { + it('should exist', function() { + should.exist(qb.select); + }); + it('should be a function', function() { + qb.select.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('selectArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.selectArray.should.be.empty; + }); + it('should require an array or string to be passed as first parameter', function() { + var invalid_match = /requires a string or array/; + var empty_str_match = /string is empty/; + var empty_arr_match = /array is empty/; + expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); + expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); + expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); + expect(function() { qb.select(false); }, 'false provided').to.throw(Error, invalid_match); + expect(function() { qb.select({}); }, 'object provided').to.throw(Error, invalid_match); + expect(function() { qb.select([]); }, 'empty array provided').to.throw(Error, empty_arr_match); + expect(function() { qb.select(''); }, 'empty string provided').to.throw(Error, empty_str_match); + expect(function() { qb.select(' '); }, 'string of spaces provided').to.throw(Error, empty_str_match); + expect(function() { qb.select('blah'); }, 'valid string provided').to.not.throw(Error); + }); + it('should add field to array and escape it properly', function() { + qb.resetQuery(); + qb.select('notes'); + qb.selectArray.should.eql(['`notes`']); + }); + it('should trim fields properly before placing them into the select array', function() { + qb.resetQuery(); + qb.select(' notes '); + qb.selectArray.should.eql(['`notes`']); + }); + it('should have an empty array after resetting', function() { + qb.resetQuery(); + qb.selectArray.should.be.empty; + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.select('foo',false); + qb.selectArray.should.eql(['foo']); + }); + it('should accept a comma-delimited string of field names and trim and escape each properly', function() { + qb.resetQuery(); + qb.select('do,re , mi, fa'); + qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); + }); + it('should be allowed to be called multiple times to add multiple fields to the select array', function() { + qb.resetQuery(); + qb.select('do').select('re').select('mi').select('fa'); + qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); + }); + it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', function() { + qb.resetQuery(); + qb.select('do').select('re',false).select('mi',false).select('fa'); + qb.selectArray.should.eql(['`do`','re','mi','`fa`']); + }); + it('should accept an array of fields and add them individually to the select array', function() { + qb.resetQuery(); + qb.select(['sol','la','ti','do']); + qb.selectArray.should.eql(['`sol`','`la`','`ti`','`do`']); + }); + it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', function() { + qb.resetQuery(); + qb.select(['sol','la','ti','do'],false); + qb.selectArray.should.eql(['sol','la','ti','do']); + }); + it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', function() { + qb.resetQuery(); + qb.select(['`sol`','la','ti','`do`'],false); + qb.selectArray.should.eql(['`sol`','la','ti','`do`']); + }); + it('should not double-escape a field', function() { + qb.resetQuery(); + qb.select('`do`'); + qb.selectArray.should.eql(['`do`']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields', function() { + qb.resetQuery(); + qb.select(['`do`','`re`','`mi`']); + qb.selectArray.should.eql(['`do`','`re`','`mi`']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', function() { + qb.resetQuery(); + qb.select(['`do`','re','`mi`']); + qb.selectArray.should.eql(['`do`','`re`','`mi`']); + }); + it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', function() { + qb.resetQuery(); + qb.select('foo as bar'); + qb.selectArray.should.eql(['`foo` as `bar`']); + }); + it('should not double-escape aliases', function() { + qb.resetQuery(); + qb.select(['foo as `bar`']); + qb.selectArray.should.eql(['`foo` as `bar`']); + }); + it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', function() { + qb.resetQuery(); + qb.select(['foo as bar','bar as foo']); + qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`']); + }); + it('should allow for field aliases with spaces in them', function() { + qb.resetQuery(); + qb.select('notes as The Notes'); + qb.selectArray.should.eql(['`notes` as `The Notes`']); + }); + it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', function() { + qb.resetQuery(); + qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); + qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); + }); +}); \ No newline at end of file From 1af92f434e88f5c9547e6d0fc1d578688b1fa228 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Jul 2014 12:10:53 -0400 Subject: [PATCH 017/200] More bug fixes and tests. --- lib/query_builder.js | 28 ++++++++++----------- test/test-join.js | 58 +++++++++++++++++++++++++++++++++++++++++--- test/tests-from.js | 21 +++++++++++++++- test/tests-select.js | 18 ++++++++++++++ 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/lib/query_builder.js b/lib/query_builder.js index 0d40b5a..2e5dc99 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -33,11 +33,11 @@ var QueryBuilder = function() { table = table.replace(/\s+AS\s+/gi, ' '); // Grab the alias - alias = table.slice(table.lastIndexOf(' ')).trim(); + alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist - if(qb.aliasedTables.indexOf(table) == -1) { - qb.aliasedTables.push(table); + if(qb.aliasedTables.indexOf(alias) == -1) { + qb.aliasedTables.push(alias); } } } @@ -113,11 +113,12 @@ var QueryBuilder = function() { // with an alias. While we're at it, we will escape the components if (item.indexOf('.') !== -1) { parts = item.split('.'); - + var first_seg = parts[0].trim().replace(/`/g,''); + // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item - if (qb.aliasedTables.indexOf(parts[0]) !== -1) { + if (qb.aliasedTables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { for (var key in parts) { var val = parts[key]; @@ -522,22 +523,21 @@ var QueryBuilder = function() { throw new Error("You must provide a table, view, or stored procedure to join to!"); } - relation = relation || ''; - direction = (!direction || typeof direction !== 'string' ? '' : direction); + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; if (direction != '') { - direction = direction.toUpperCase(); + direction = direction.toUpperCase().trim(); if (valid_directions.indexOf(direction) === -1) { - direction = ''; + throw new Error("Invalid join direction provided as third parameter."); } - else { - if (typeof relation !== 'string') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - direction += ' '; + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); } + + direction += ' '; } trackAliases(this,table); diff --git a/test/test-join.js b/test/test-join.js index 348ef31..fadaac3 100755 --- a/test/test-join.js +++ b/test/test-join.js @@ -27,9 +27,9 @@ describe('join()', function() { expect(function() { qb.join([]); }, 'empty array provided').to.throw(Error, invalid_match); expect(function() { qb.join(''); }, 'empty string provided').to.throw(Error, invalid_match); expect(function() { qb.join(' '); }, 'string of spaces provided').to.throw(Error, invalid_match); - expect(function() { qb.join('blah');}, 'valid string provided').to.not.throw(Error); - expect(function() { qb.join('blah');}, 'valid string provided').to.not.throw(Error); - }) + expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); + }); it('should except single item and add it to join array as basic join and escape item', function() { qb.resetQuery(); qb.join('universe'); @@ -40,4 +40,56 @@ describe('join()', function() { qb.join('universe u'); qb.joinArray.should.eql(['JOIN `universe` `u` ']); }); + it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', function() { + var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; + var invalid_direction = /Invalid join direction provided as third parameter/; + + expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe','','left'); }, 'empty string 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',' ','left'); }, 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',5,'left'); }, 'integer 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',5.6,'left'); }, 'float 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',[],'left'); }, 'array 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe',{},'left'); }, 'object 2nd param').to.throw(Error,invalid_2nd_param); + expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(function() { qb.join('universe','foo = bar'); }, 'no 3rd param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar','left'); }, '3 valid params').to.not.throw(Error); + }); + it('should allow valid join direction to be passed in third parameter', function() { + // NOTE: A lot of this functionality was already tested when testing second param + var invalid_direction = /Invalid join direction provided as third parameter/; + + expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',''); }, 'empty third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',' '); }, 'just spaces').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',null); }, 'null third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',false); }, 'false third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',5); }, 'integer third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',5.5); }, 'float third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',{}); }, 'object third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',[]); }, 'array third param').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar','left '); }, 'trailing space').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',' left '); }, 'leading and trailing space').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar',' left'); }, 'leading space').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar','left'); }, 'lowercase direction').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar','LEFT'); }, 'uppercase direction').to.not.throw(Error); + expect(function() { qb.join('universe','foo = bar','LEFT OUTER'); }, 'two word direction').to.not.throw(Error); + }); + it('should except a valid second parameter as a join condition and escape it properly', function() { + qb.resetQuery(); + qb.join('universe u','u.type_id = ut.id'); + qb.joinArray.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); + }); + it('should escape compound objects properly', function() { + qb.resetQuery(); + qb.join('universe.galaxy.star_system s','s.type_id = st.id'); + qb.joinArray.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + }); + it('should add aliases to alias-tracking array', function() { + qb.resetQuery(); + qb.join('universe.galaxy.star_system s'); + qb.aliasedTables.should.eql(['s']); + }); }); \ No newline at end of file diff --git a/test/tests-from.js b/test/tests-from.js index ee64b24..3fa57d9 100755 --- a/test/tests-from.js +++ b/test/tests-from.js @@ -81,11 +81,30 @@ describe('from()', function() { it('should add aliases to alias-tracking array', function() { qb.resetQuery(); qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.aliasedTables.should.eql(['`universe` `u`','`galaxy` `g`']); + qb.aliasedTables.should.eql(['u','g']); }); it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', function() { qb.resetQuery(); qb.from(['universe u, galaxy g']); qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); }); + it('should allow for namespacing in field name (host.db.table)', function() { + qb.resetQuery(); + qb.from('star_system.planet'); + qb.fromArray.should.eql(['`star_system`.`planet`']); + + qb.resetQuery(); + qb.from('galaxy.star_system.planet'); + qb.fromArray.should.eql(['`galaxy`.`star_system`.`planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', function() { + qb.resetQuery(); + qb.from('universe.galaxy.star_system planet'); + qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', function() { + qb.resetQuery(); + qb.from('universe.galaxy.star_system as planet'); + qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); + }) }); \ No newline at end of file diff --git a/test/tests-select.js b/test/tests-select.js index 75d88b8..a36e3ec 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -119,4 +119,22 @@ describe('select()', function() { qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); }); + it('should allow for namespacing in field name (host.db.table.field)', function() { + qb.resetQuery(); + qb.select('star_system.planet'); + qb.selectArray.should.eql(['`star_system`.`planet`']); + + qb.resetQuery(); + qb.select('galaxy.star_system.planet'); + qb.selectArray.should.eql(['`galaxy`.`star_system`.`planet`']); + + qb.resetQuery(); + qb.select('universe.galaxy.star_system.planet'); + qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', function() { + qb.resetQuery(); + qb.select('universe.galaxy.star_system.planet as planet'); + qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); + }); }); \ No newline at end of file From 90a80f44eb4b2bca3993220c866045b8f0527c6a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Jul 2014 17:21:21 -0400 Subject: [PATCH 018/200] Fixed a major bug in how complex join conditions are escaped while also fixing a few bugs in the escapeIdentifiers function. --- lib/query_builder.js | 69 ++++++++++++++++++++++++++++++++++++-------- test/test-join.js | 15 ++++++++++ 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/lib/query_builder.js b/lib/query_builder.js index 2e5dc99..6acaa95 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -55,12 +55,22 @@ var QueryBuilder = function() { return item; } + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + for (var i in item) { + item[i] = escapeIdentifiers(item[i]); + } + return item; + } + else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + return item; + } + var str; - if (item.indexOf('.') !== -1) { - str = '`' + item.replace(/\./g,'`.`') + '`'; + if (item.indexOf('.' + '*') !== -1) { + str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); } else { - str = '`' + item + '`'; + str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); } // remove duplicates if the user already included the escape @@ -146,8 +156,9 @@ var QueryBuilder = function() { }; var hasOperator = function (str) { - if(typeof str === 'string') { - if(!str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/gi)) { + if(typeof str === 'string' && str.length > 0) { + var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); + if(match === null) { return false; } } @@ -518,13 +529,14 @@ var QueryBuilder = function() { return this; }, - join: function(table, relation, direction) { + join: function(table, relation, direction, escape) { if (typeof table !== 'string' || table.trim().length == 0) { throw new Error("You must provide a table, view, or stored procedure to join to!"); } relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; @@ -542,15 +554,48 @@ var QueryBuilder = function() { trackAliases(this,table); - var match; - if (match = relation.match(/([\w\.]+)([\W\s]+)(.+)/)) { - match[1] = protectIdentifiers(this,match[1]); - match[3] = protectIdentifiers(this,match[3]); + // Split multiple conditions + var regex = /\sAND\s|\sOR\s/ig; + var m = relation.match(regex); + var matches = []; + var k, temp, temp_match; + if (escape === true && m) { + while(k = regex.exec(relation)) { + matches.push(k); + } - relation = match[1] + match[2] + match[3]; + var new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? protectIdentifiers(this,temp_match[1],escape) + temp_match[2] + protectIdentifiers(this,temp_match[3],escape) : temp); + new_relation += matches[j][0]; + } + + relation = ' ON ' + new_relation; + } + + // Split apart the condition and protect the identifiers + else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + protectIdentifiers(this,match[1],true) + match[2] + protectIdentifiers(this,match[3],true); + } + else if (!hasOperator(relation)) { + console.log("Has operator: " + relation); + relation = ' USING (' + (escape ? escapeIdentifiers(relation) : relation) + ')'; + } + else { + relation = ' '; + } + + // Do we want to escape the table name? + if (escape === true) { + table = protectIdentifiers(this,table,true); } - join = direction + 'JOIN ' + protectIdentifiers(this,table, true) + (relation !== '' ? ' ON ' + relation : ' ' + relation); + join = direction + 'JOIN ' + table + relation; this.joinArray.push(join); return this; diff --git a/test/test-join.js b/test/test-join.js index fadaac3..27460ae 100755 --- a/test/test-join.js +++ b/test/test-join.js @@ -92,4 +92,19 @@ describe('join()', function() { qb.join('universe.galaxy.star_system s'); qb.aliasedTables.should.eql(['s']); }); + it('should properly place join direction into join clause', function() { + qb.resetQuery(); + qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); + qb.joinArray.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + }); + it('should be chainable to allow for multiple join clauses', function() { + qb.resetQuery(); + qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); + qb.joinArray.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); + }); + it('should escape complex join conditions', function() { + qb.resetQuery(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); + qb.joinArray.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); + }); }); \ No newline at end of file From 07d700980b56c2d8ca252ceb7d430f7141100d75 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Jul 2014 17:33:09 -0400 Subject: [PATCH 019/200] Fixed bug causing the alias to not show up when supplying functions like count directly with a select() call and choosing not to escape. --- lib/query_builder.js | 2 +- test/{test-join.js => tests-join.js} | 0 test/tests-select.js | 5 +++++ test/tests-where.js | 13 +++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) rename test/{test-join.js => tests-join.js} (100%) create mode 100755 test/tests-where.js diff --git a/lib/query_builder.js b/lib/query_builder.js index 6acaa95..9fed495 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -115,7 +115,7 @@ var QueryBuilder = function() { // If a parenthesis is found we know that we do not need to // escape the data or add a prefix. if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - return item; + return item + alias; } // Break the string apart if it contains periods, then insert the table prefix diff --git a/test/test-join.js b/test/tests-join.js similarity index 100% rename from test/test-join.js rename to test/tests-join.js diff --git a/test/tests-select.js b/test/tests-select.js index a36e3ec..6521fac 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -137,4 +137,9 @@ describe('select()', function() { qb.select('universe.galaxy.star_system.planet as planet'); qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); }); + it('should allow for functions in select statement without escaping them if second param is false', function() { + qb.resetQuery(); + qb.select('count(*) as count', false); + qb.selectArray.should.eql(['count(*) as count']); + }); }); \ No newline at end of file diff --git a/test/tests-where.js b/test/tests-where.js new file mode 100755 index 0000000..d6143c9 --- /dev/null +++ b/test/tests-where.js @@ -0,0 +1,13 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('where()', function() { + it('should exist', function() { + should.exist(qb.where); + }); + it('should be a function', function() { + qb.where.should.be.a('function'); + }); +}); \ No newline at end of file From da31ad2d8111b244045995ad1a3eb2f56b46443e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Jul 2014 17:33:45 -0400 Subject: [PATCH 020/200] Fixed bug causing the alias to not show up when supplying functions like count directly with a select() call and choosing not to escape. --- test/tests-select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/tests-select.js b/test/tests-select.js index 6521fac..a0e0f0e 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -141,5 +141,9 @@ describe('select()', function() { qb.resetQuery(); qb.select('count(*) as count', false); qb.selectArray.should.eql(['count(*) as count']); + + qb.resetQuery(); + qb.select('count(*) as count'); + qb.selectArray.should.eql(['count(*) as `count`']); }); }); \ No newline at end of file From 2aaa0b38dd08e966857620abdcdc1e0a92fe1de1 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 4 Aug 2014 13:41:11 -0400 Subject: [PATCH 021/200] Latest tests and testing out a test documentation with a test.md file generated from mocha. --- HISTORY.md | 3 +- Makefile | 2 +- lib/query_builder.js | 112 +++++-- test/tests-select.js | 27 +- test/tests-where.js | 102 ++++++ tests.md | 732 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 941 insertions(+), 37 deletions(-) create mode 100644 tests.md diff --git a/HISTORY.md b/HISTORY.md index 773f949..2a067a5 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -26,4 +26,5 @@ you spot any mistakes. * The `update()` method's 3rd parameter can now either be the callback (as always) or a `WHERE` clause (ex. 'foo = "bar"' or {foo:'bar', id: 3}). If a where clause is provided, the callback is now the 4th parameter. This change is fully backwards-compatible with the previous version of this module. * New package dependencies (for testing): chai and mocha. * Tests have been written for better-ensuring future enhancements and fixes to not break functionality -* Library has been broken into 3 objects to allow for prpoer testing. This won't affect the API and is fully-backwards compatible with the previous version. \ No newline at end of file +* Library has been broken into 3 objects to allow for prpoer testing. This won't affect the API and is fully-backwards compatible with the previous version. +* Officially announcing that how third parameter of the `where()` method works is deprecated... starting with v1.0.0, third param will be `(bool) escape` and not `(bool) isRaw`. So, all calls to this method using the 3rd parameter will, in future, have to be changed by converting `true` to `false` and vice versa. This is so that we have a consistent API throughout. \ No newline at end of file diff --git a/Makefile b/Makefile index 574b885..b18e3a3 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/*.js test: - mocha --timeout 5000 --reporter nyan $(TESTS) + mocha --timeout 5000 --reporter markdown $(TESTS) .PHONY: test diff --git a/lib/query_builder.js b/lib/query_builder.js index 9fed495..d053f53 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -33,7 +33,7 @@ var QueryBuilder = function() { table = table.replace(/\s+AS\s+/gi, ' '); // Grab the alias - alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); + var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist if(qb.aliasedTables.indexOf(alias) == -1) { @@ -78,6 +78,8 @@ var QueryBuilder = function() { } var protectIdentifiers = function(qb,item,protect_identifiers) { + if (item === '') return item; + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { @@ -94,6 +96,24 @@ var QueryBuilder = function() { // Convert tabs or multiple spaces into single spaces item = item.replace(/\s+/g, ' '); + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + var has_alias = item.lastIndexOf(')'); + var alias; + if (has_alias >= 0) { + alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); + alias = escapeIdentifiers(alias); + alias = ' AS ' + alias; + item = item.substr(0,has_alias + 1); + } else { + alias = ''; + } + + return item + alias; + } + // If the item has an alias declaration we remove it and set it aside. // Basically we remove everything to the right of the first space if (item.match(/\sAS\s/ig)) { @@ -108,21 +128,14 @@ var QueryBuilder = function() { item = item.substr(0,alias_index); } else { - alias = ''; - } - - // This is basically a bug fix for queries that use MAX, MIN, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - return item + alias; + var alias = ''; } // Break the string apart if it contains periods, then insert the table prefix // in the correct location, assuming the period doesn't indicate that we're dealing // with an alias. While we're at it, we will escape the components if (item.indexOf('.') !== -1) { - parts = item.split('.'); + var parts = item.split('.'); var first_seg = parts[0].trim().replace(/`/g,''); // Does the first segment of the exploded item match @@ -165,9 +178,16 @@ var QueryBuilder = function() { return true; } - var qb_escape = function(str) { + var qb_escape = function(qb,str) { + if(qb.hasOwnProperty('escape')) { + var do_escape = qb.escape; + } else { + var SqlString = require('../node_modules/mysql/lib/protocol/SqlString.js'); + var do_escape = SqlString.escape; + } + if (typeof str === 'string') { - str = "'" + this.escape(str) + "'"; + str = do_escape(str); } else if (typeof str === 'boolean') { str = (str === false ? 0 : 1); @@ -309,11 +329,14 @@ var QueryBuilder = function() { this.aliasedTables = []; }, - where: function(key, value, isRaw) { - isRaw = (typeof isRaw === 'boolean' ? isRaw : false); - value = value || null; + where: function(key, value, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + value = (typeof value === 'undefined' ? null : value); + escape = (typeof escape === 'boolean' ? escape : true); - var escape = (isRaw ? false : true); if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { return this._where_in(key, value, false, 'AND '); } @@ -348,12 +371,12 @@ var QueryBuilder = function() { }, _where: function(key, value, type, escape) { - value = value || null; + value = (typeof value === 'undefined' ? null : value); type = type || 'AND '; escape = (typeof escape === 'boolean' ? escape : true); if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - key_array = {}; + var key_array = {}; key_array[key] = value; key = key_array; } @@ -362,7 +385,7 @@ var QueryBuilder = function() { var v = key[k]; if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { - return this._where_in(k,v,false,type); + return this._where_in(k,v,false,type,escape); } var prefix = (this.whereArray.length == 0 ? '' : type); @@ -373,8 +396,12 @@ var QueryBuilder = function() { if (v !== null) { if (escape === true) { - k = protectIdentifiers(this,k); - v = ' ' + qb_escape(v); + k = protectIdentifiers(this,k,escape); + v = ' ' + qb_escape(this,v); + } + + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + qb_escape(this,v); } if (!hasOperator(k)) { @@ -382,7 +409,7 @@ var QueryBuilder = function() { } } else { - k = protectIdentifiers(this,k); + k = protectIdentifiers(this,k,escape); } if (v) { @@ -396,11 +423,12 @@ var QueryBuilder = function() { return this; }, - _where_in: function(key, values, not, type) { + _where_in: function(key, values, not, type, escape) { key = key || null; values = values || []; type = type || 'AND '; not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); if(key === null || values.length === 0) return this; @@ -410,11 +438,11 @@ var QueryBuilder = function() { } for (var i in values) { - this.whereInArray.push(qb_escape(values[i])); + this.whereInArray.push(qb_escape(this,values[i])); } prefix = (this.whereArray.length == 0 ? '' : type); - where_in = prefix + protectIdentifiers(this,key) + not + " IN (" + this.whereInArray.join(', ') + ") "; + where_in = prefix + protectIdentifiers(this,key,escape) + not + " IN (" + this.whereInArray.join(', ') + ")"; this.whereArray.push(where_in); // reset the array for multiple calls @@ -558,7 +586,7 @@ var QueryBuilder = function() { var regex = /\sAND\s|\sOR\s/ig; var m = relation.match(regex); var matches = []; - var k, temp, temp_match; + var k, temp, temp_match, match; if (escape === true && m) { while(k = regex.exec(relation)) { matches.push(k); @@ -595,13 +623,14 @@ var QueryBuilder = function() { table = protectIdentifiers(this,table,true); } - join = direction + 'JOIN ' + table + relation; + var join = direction + 'JOIN ' + table + relation; this.joinArray.push(join); return this; }, select: function(select,escape) { + // First param must be a non-empty string or object if (typeof select === 'string') { select = select.trim(); if (select.length == 0) { @@ -619,8 +648,29 @@ var QueryBuilder = function() { if (typeof escape !== 'boolean') escape = true; + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) if (typeof select === 'string') { - select = select.split(','); + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + var m, open_paren_index, inner_parenthesis; + var reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; + } + } + } + } + else { + select = [select]; + } + } } for (var i in select) { @@ -707,7 +757,7 @@ var QueryBuilder = function() { _having: function(key, value, type, escape) { if (Object.prototype.toString.call(from) !== Object.prototype.toString.call({})) { - key_array = {}; + var key_array = {}; key_array[key] = value; key = key_array; } @@ -725,7 +775,7 @@ var QueryBuilder = function() { } if (v != '') { - v = ' ' + qb_escape(v); + v = ' ' + qb_escape(this,v); } this.havingArray.push(prefix + k + k); @@ -806,7 +856,7 @@ var QueryBuilder = function() { value = value || ''; if(Object.prototype.toString.call(value) !== Object.prototype.toString.call({})) { - key_array = {}; + var key_array = {}; key_array[key] = value; key = key_array; } diff --git a/test/tests-select.js b/test/tests-select.js index a0e0f0e..0992950 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -137,13 +137,32 @@ describe('select()', function() { qb.select('universe.galaxy.star_system.planet as planet'); qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); }); - it('should allow for functions in select statement without escaping them if second param is false', function() { + it('should not allow subqueries or functions with commas in them without the second parameter being false', function() { + qb.resetQuery(); + expect(function() { + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); + }).to.throw(Error); + + expect(function() { + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); + }).to.not.throw(Error); + }); + it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', function() { qb.resetQuery(); qb.select('count(*) as count', false); - qb.selectArray.should.eql(['count(*) as count']); + qb.selectArray.should.eql(['count(*) AS `count`']); + + qb.resetQuery(); + qb.select('count(*) as count, m.*, MIN(id) as min', false); + qb.selectArray.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); + + qb.resetQuery(); + qb.select('(select count(p.*) as count from planets p) as num_planets', false); + qb.selectArray.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); qb.resetQuery(); - qb.select('count(*) as count'); - qb.selectArray.should.eql(['count(*) as `count`']); + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); + qb.selectArray.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); + }); }); \ No newline at end of file diff --git a/test/tests-where.js b/test/tests-where.js index d6143c9..e263502 100755 --- a/test/tests-where.js +++ b/test/tests-where.js @@ -10,4 +10,106 @@ describe('where()', function() { it('should be a function', function() { qb.where.should.be.a('function'); }); + it('should accept a field name in the form of a string as the first parameter', function() { + qb.where('planet'); + qb.whereArray.should.eql(['`planet` IS NULL']); + }); + it('should assume second param is NULL if not provided', function() { + qb.resetQuery(); + qb.where('planet'); + qb.whereArray.should.eql(['`planet` IS NULL']); + }); + it('should accept NULL as second parameter and assume IS NULL', function() { + qb.resetQuery(); + qb.where('planet',null); + qb.whereArray.should.eql(['`planet` IS NULL']); + }); + it('should accept boolean values and will transform them properly', function() { + qb.resetQuery(); + qb.where('planet',true); + qb.whereArray.should.eql(['`planet` = 1']); + + qb.resetQuery(); + qb.where('planet',false); + qb.whereArray.should.eql(['`planet` = 0']); + }); + it('should accept integer and float values', function() { + qb.resetQuery(); + qb.where('planet',5); + qb.whereArray.should.eql(['`planet` = 5']); + + qb.resetQuery(); + qb.where('planet',123.456); + qb.whereArray.should.eql(['`planet` = 123.456']); + }); + it('should accept string values', function() { + qb.resetQuery(); + qb.where('planet','Earth'); + qb.whereArray.should.eql(["`planet` = 'Earth'"]); + + qb.resetQuery(); + qb.where('galaxy','Milky Way'); + qb.whereArray.should.eql(["`galaxy` = 'Milky Way'"]); + }); + it('should accept arrays of values and assume a WHERE IN clause', function() { + qb.resetQuery(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); + }); + it('should concatenate multiple where clauses with AND by default', function() { + qb.resetQuery(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.where('galaxy','Milky Way'); + qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); + qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + }); + it('should accept an object of key/value pairs (field: value)', function() { + qb.resetQuery(); + qb.where({planet:'Earth',star_system:'Solar'}); + qb.whereArray.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); + }); + it('should accept an object of key/value pairs (field: value) where values can be arrays', function() { + qb.resetQuery(); + qb.where({star_system:'Solar',planet:['Earth','Mars']}); + qb.whereArray.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); + }); + it('should accept an operators in the first parameter', function() { + qb.resetQuery(); + qb.where('position >',3); + qb.whereArray.should.eql(["`position` > 3"]); + + qb.resetQuery(); + qb.where('position <',3); + qb.whereArray.should.eql(["`position` < 3"]); + + qb.resetQuery(); + qb.where('position >=',3); + qb.whereArray.should.eql(["`position` >= 3"]); + + qb.resetQuery(); + qb.where('position <=',3); + qb.whereArray.should.eql(["`position` <= 3"]); + + qb.resetQuery(); + qb.where('position <>',3); + qb.whereArray.should.eql(["`position` <> 3"]); + + qb.resetQuery(); + qb.where('position !=',3); + qb.whereArray.should.eql(["`position` != 3"]); + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.where({star_system:'Solar',planet:['Earth','Mars']},false); + qb.whereArray.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); + }); + it("should even escape complex/compound where clauses when provided as a string in the first and only parameter", function() { + qb.resetQuery(); + qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Mikly Way','Andromeda'))"); + qb.whereArray.should.eql("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Mikly Way','Andromeda'))"); + }); }); \ No newline at end of file diff --git a/tests.md b/tests.md new file mode 100644 index 0000000..6b8e0a2 --- /dev/null +++ b/tests.md @@ -0,0 +1,732 @@ +mocha --timeout 5000 --reporter markdown test/*.js +# TOC + - [from()](#from) + - [QueryBuilder](#querybuilder) + - [join()](#join) + - [select()](#select) + - [where()](#where) + + + +# from() +should exist. + +```js +should.exist(qb.from); +``` + +should be a function. + +```js +qb.from.should.be.a('function'); +``` + +should have an array to put fields into. + +```js +qb.should.have.property('fromArray'); +``` + +should have an empty array to put fields into at the beginning. + +```js +qb.fromArray.should.be.empty; +``` + +should add an item to an array and escape it properly. + +```js +qb.from('universe'); +qb.fromArray.should.eql(['`universe`']); +``` + +should accept a comma-delimited string of items and trim and escape each properly. + +```js +qb.resetQuery(); +qb.from('universe,galaxy , star_system, planet'); +qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); +``` + +should have an empty array after resetting. + +```js +qb.resetQuery(); +qb.fromArray.should.be.empty; +``` + +should be allowed to be called multiple times to add multiple items to the from array. + +```js +qb.resetQuery(); +qb.from('universe').from('galaxy').from('star_system').from('planet'); +qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); +``` + +should accept an array of items and add them individually to the from array. + +```js +qb.resetQuery(); +qb.from(['universe','galaxy','star_system','planet']); +qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); +``` + +should not double-escape an item. + +```js +qb.resetQuery(); + qb.from('`do`'); + qb.fromArray.should.eql(['`do`']); +``` + +should not double-escape items when provided with an array of pre-escaped items. + +```js +qb.resetQuery(); + qb.from(['`universe`','`galaxy`','`star_system`']); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); +``` + +should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items. + +```js +qb.resetQuery(); + qb.from(['`universe`','galaxy','`star_system`']); + qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); +``` + +should allow for aliases and it should escape them properly. + +```js +qb.resetQuery(); +qb.from('universe u'); +qb.fromArray.should.eql(['`universe` `u`']); +``` + +should allow for the word AS to be used to alias an item. + +```js +qb.resetQuery(); +qb.from('universe as u'); +qb.fromArray.should.eql(['`universe` as `u`']); +``` + +should allow for an array of item + aliases and it should escape them all properly. + +```js +qb.resetQuery(); +qb.from(['universe u', 'galaxy g']); +qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); +``` + +should allow for an array of item + aliases that are pre-escaped and it should not double-escape them. + +```js +qb.resetQuery(); +qb.from(['`universe` `u`', '`galaxy` `g`']); +qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); +``` + +should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items. + +```js +qb.resetQuery(); +qb.from(['`universe` u', 'galaxy `g`']); +qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); +``` + +should add aliases to alias-tracking array. + +```js +qb.resetQuery(); +qb.from(['`universe` `u`', '`galaxy` `g`']); +qb.aliasedTables.should.eql(['u','g']); +``` + +should allow for an comma-delimited list of item + aliases and it should escape them all properly. + +```js +qb.resetQuery(); +qb.from(['universe u, galaxy g']); +qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); +``` + +should allow for namespacing in field name (host.db.table). + +```js +qb.resetQuery(); +qb.from('star_system.planet'); +qb.fromArray.should.eql(['`star_system`.`planet`']); + +qb.resetQuery(); +qb.from('galaxy.star_system.planet'); +qb.fromArray.should.eql(['`galaxy`.`star_system`.`planet`']); +``` + +should allow for namespacing in field name (host.db.table.column) + alias. + +```js +qb.resetQuery(); +qb.from('universe.galaxy.star_system planet'); +qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); +``` + +should allow for namespacing in field name (host.db.table.column) + alias (declare with AS). + +```js +qb.resetQuery(); +qb.from('universe.galaxy.star_system as planet'); +qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); +``` + + +# QueryBuilder +actually exists and can be initialized. + +```js +var qb = new QueryBuilder(); +qb.should.be.instanceOf(Object); +``` + + +# join() +should exist. + +```js +should.exist(qb.join); +``` + +should be a function. + +```js +qb.join.should.be.a('function'); +``` + +should have an array to put fields into. + +```js +qb.should.have.property('joinArray'); +``` + +should have an empty array to put fields into at the beginning. + +```js +qb.joinArray.should.be.empty; +``` + +should require a string to be passed as first parameter. + +```js +var invalid_match = /must provide a table/; +expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); +expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); +expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); +expect(function() { qb.join(false); }, 'false provided').to.throw(Error, invalid_match); +expect(function() { qb.join({}); }, 'object provided').to.throw(Error, invalid_match); +expect(function() { qb.join([]); }, 'empty array provided').to.throw(Error, invalid_match); +expect(function() { qb.join(''); }, 'empty string provided').to.throw(Error, invalid_match); +expect(function() { qb.join(' '); }, 'string of spaces provided').to.throw(Error, invalid_match); +expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); +expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); +``` + +should except single item and add it to join array as basic join and escape item. + +```js +qb.resetQuery(); +qb.join('universe'); +qb.joinArray.should.eql(['JOIN `universe` ']); +``` + +should except single item with alias and add it to join array as basic join and escape each part. + +```js +qb.resetQuery(); +qb.join('universe u'); +qb.joinArray.should.eql(['JOIN `universe` `u` ']); +``` + +should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided. + +```js +var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; +var invalid_direction = /Invalid join direction provided as third parameter/; + +expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe','','left'); }, 'empty string 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',' ','left'); }, 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',5,'left'); }, 'integer 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',5.6,'left'); }, 'float 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',[],'left'); }, 'array 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe',{},'left'); }, 'object 2nd param').to.throw(Error,invalid_2nd_param); +expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); +expect(function() { qb.join('universe','foo = bar'); }, 'no 3rd param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar','left'); }, '3 valid params').to.not.throw(Error); +``` + +should allow valid join direction to be passed in third parameter. + +```js +// NOTE: A lot of this functionality was already tested when testing second param +var invalid_direction = /Invalid join direction provided as third parameter/; + +expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); +expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',''); }, 'empty third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',' '); }, 'just spaces').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',null); }, 'null third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',false); }, 'false third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',5); }, 'integer third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',5.5); }, 'float third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',{}); }, 'object third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',[]); }, 'array third param').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar','left '); }, 'trailing space').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',' left '); }, 'leading and trailing space').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar',' left'); }, 'leading space').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar','left'); }, 'lowercase direction').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar','LEFT'); }, 'uppercase direction').to.not.throw(Error); +expect(function() { qb.join('universe','foo = bar','LEFT OUTER'); }, 'two word direction').to.not.throw(Error); +``` + +should except a valid second parameter as a join condition and escape it properly. + +```js +qb.resetQuery(); +qb.join('universe u','u.type_id = ut.id'); +qb.joinArray.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); +``` + +should escape compound objects properly. + +```js +qb.resetQuery(); +qb.join('universe.galaxy.star_system s','s.type_id = st.id'); +qb.joinArray.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); +``` + +should add aliases to alias-tracking array. + +```js +qb.resetQuery(); +qb.join('universe.galaxy.star_system s'); +qb.aliasedTables.should.eql(['s']); +``` + +should properly place join direction into join clause. + +```js +qb.resetQuery(); +qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); +qb.joinArray.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); +``` + +should be chainable to allow for multiple join clauses. + +```js +qb.resetQuery(); +qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); +qb.joinArray.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); +``` + +should escape complex join conditions. + +```js +qb.resetQuery(); +qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); +qb.joinArray.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); +``` + + +# select() +should exist. + +```js +should.exist(qb.select); +``` + +should be a function. + +```js +qb.select.should.be.a('function'); +``` + +should have an array to put fields into. + +```js +qb.should.have.property('selectArray'); +``` + +should have an empty array to put fields into at the beginning. + +```js +qb.selectArray.should.be.empty; +``` + +should require an array or string to be passed as first parameter. + +```js +var invalid_match = /requires a string or array/; +var empty_str_match = /string is empty/; +var empty_arr_match = /array is empty/; +expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); +expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); +expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); +expect(function() { qb.select(false); }, 'false provided').to.throw(Error, invalid_match); +expect(function() { qb.select({}); }, 'object provided').to.throw(Error, invalid_match); +expect(function() { qb.select([]); }, 'empty array provided').to.throw(Error, empty_arr_match); +expect(function() { qb.select(''); }, 'empty string provided').to.throw(Error, empty_str_match); +expect(function() { qb.select(' '); }, 'string of spaces provided').to.throw(Error, empty_str_match); +expect(function() { qb.select('blah'); }, 'valid string provided').to.not.throw(Error); +``` + +should add field to array and escape it properly. + +```js +qb.resetQuery(); +qb.select('notes'); +qb.selectArray.should.eql(['`notes`']); +``` + +should trim fields properly before placing them into the select array. + +```js +qb.resetQuery(); +qb.select(' notes '); +qb.selectArray.should.eql(['`notes`']); +``` + +should have an empty array after resetting. + +```js +qb.resetQuery(); +qb.selectArray.should.be.empty; +``` + +should not escape fields if asked not to. + +```js +qb.resetQuery(); +qb.select('foo',false); +qb.selectArray.should.eql(['foo']); +``` + +should accept a comma-delimited string of field names and trim and escape each properly. + +```js +qb.resetQuery(); +qb.select('do,re , mi, fa'); +qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); +``` + +should be allowed to be called multiple times to add multiple fields to the select array. + +```js +qb.resetQuery(); +qb.select('do').select('re').select('mi').select('fa'); +qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); +``` + +should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array. + +```js +qb.resetQuery(); +qb.select('do').select('re',false).select('mi',false).select('fa'); +qb.selectArray.should.eql(['`do`','re','mi','`fa`']); +``` + +should accept an array of fields and add them individually to the select array. + +```js +qb.resetQuery(); +qb.select(['sol','la','ti','do']); +qb.selectArray.should.eql(['`sol`','`la`','`ti`','`do`']); +``` + +should accept an array of fields and add them individually to the select array without escaping, if asked not to. + +```js +qb.resetQuery(); +qb.select(['sol','la','ti','do'],false); +qb.selectArray.should.eql(['sol','la','ti','do']); +``` + +should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to. + +```js +qb.resetQuery(); +qb.select(['`sol`','la','ti','`do`'],false); +qb.selectArray.should.eql(['`sol`','la','ti','`do`']); +``` + +should not double-escape a field. + +```js +qb.resetQuery(); + qb.select('`do`'); + qb.selectArray.should.eql(['`do`']); +``` + +should not double-escape fields when provided with an array of pre-escaped fields. + +```js +qb.resetQuery(); + qb.select(['`do`','`re`','`mi`']); + qb.selectArray.should.eql(['`do`','`re`','`mi`']); +``` + +should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields. + +```js +qb.resetQuery(); + qb.select(['`do`','re','`mi`']); + qb.selectArray.should.eql(['`do`','`re`','`mi`']); +``` + +should allow for field aliases to be provided and those fields and aliases should be properly escaped. + +```js +qb.resetQuery(); +qb.select('foo as bar'); +qb.selectArray.should.eql(['`foo` as `bar`']); +``` + +should not double-escape aliases. + +```js +qb.resetQuery(); +qb.select(['foo as `bar`']); +qb.selectArray.should.eql(['`foo` as `bar`']); +``` + +should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped. + +```js +qb.resetQuery(); +qb.select(['foo as bar','bar as foo']); +qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`']); +``` + +should allow for field aliases with spaces in them. + +```js +qb.resetQuery(); +qb.select('notes as The Notes'); +qb.selectArray.should.eql(['`notes` as `The Notes`']); +``` + +should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped. + +```js +qb.resetQuery(); +qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); +qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); +``` + +should allow for namespacing in field name (host.db.table.field). + +```js +qb.resetQuery(); +qb.select('star_system.planet'); +qb.selectArray.should.eql(['`star_system`.`planet`']); + +qb.resetQuery(); +qb.select('galaxy.star_system.planet'); +qb.selectArray.should.eql(['`galaxy`.`star_system`.`planet`']); + +qb.resetQuery(); +qb.select('universe.galaxy.star_system.planet'); +qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); +``` + +should allow for namespacing in field name (host.db.table.column) + alias. + +```js +qb.resetQuery(); +qb.select('universe.galaxy.star_system.planet as planet'); +qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); +``` + +should not allow subqueries or functions with commas in them without the second parameter being false. + +```js +qb.resetQuery(); +expect(function() { + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); +}).to.throw(Error); + +expect(function() { + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); +}).to.not.throw(Error); +``` + +should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped). + +```js +qb.resetQuery(); +qb.select('count(*) as count', false); +qb.selectArray.should.eql(['count(*) AS `count`']); + +qb.resetQuery(); +qb.select('count(*) as count, m.*, MIN(id) as min', false); +qb.selectArray.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); + +qb.resetQuery(); +qb.select('(select count(p.*) as count from planets p) as num_planets', false); +qb.selectArray.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); + +qb.resetQuery(); +qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); +qb.selectArray.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); +``` + + +# where() +should exist. + +```js +should.exist(qb.where); +``` + +should be a function. + +```js +qb.where.should.be.a('function'); +``` + +should accept a field name in the form of a string as the first parameter. + +```js +qb.where('planet'); +qb.whereArray.should.eql(['`planet` IS NULL']); +``` + +should assume second param is NULL if not provided. + +```js +qb.resetQuery(); +qb.where('planet'); +qb.whereArray.should.eql(['`planet` IS NULL']); +``` + +should accept NULL as second parameter and assume IS NULL. + +```js +qb.resetQuery(); +qb.where('planet',null); +qb.whereArray.should.eql(['`planet` IS NULL']); +``` + +should accept boolean values and will transform them properly. + +```js +qb.resetQuery(); +qb.where('planet',true); +qb.whereArray.should.eql(['`planet` = 1']); + +qb.resetQuery(); +qb.where('planet',false); +qb.whereArray.should.eql(['`planet` = 0']); +``` + +should accept integer and float values. + +```js +qb.resetQuery(); +qb.where('planet',5); +qb.whereArray.should.eql(['`planet` = 5']); + +qb.resetQuery(); +qb.where('planet',123.456); +qb.whereArray.should.eql(['`planet` = 123.456']); +``` + +should accept string values. + +```js +qb.resetQuery(); +qb.where('planet','Earth'); +qb.whereArray.should.eql(["`planet` = 'Earth'"]); + +qb.resetQuery(); +qb.where('galaxy','Milky Way'); +qb.whereArray.should.eql(["`galaxy` = 'Milky Way'"]); +``` + +should accept arrays of values and assume a WHERE IN clause. + +```js +qb.resetQuery(); +qb.where('planet',['Mercury','Venus','Earth','Mars']); +qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); +``` + +should concatenate multiple where clauses with AND by default. + +```js +qb.resetQuery(); +qb.where('planet',['Mercury','Venus','Earth','Mars']); +qb.where('galaxy','Milky Way'); +qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); +``` + +should be chainable. + +```js +qb.resetQuery(); +qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); +qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); +``` + +should accept an object of key/value pairs (field: value). + +```js +qb.resetQuery(); +qb.where({planet:'Earth',star_system:'Solar'}); +qb.whereArray.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); +``` + +should accept an object of key/value pairs (field: value) where values can be arrays. + +```js +qb.resetQuery(); +qb.where({star_system:'Solar',planet:['Earth','Mars']}); +qb.whereArray.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); +``` + +should accept an operators in the first parameter. + +```js +qb.resetQuery(); +qb.where('position >',3); +qb.whereArray.should.eql(["`position` > 3"]); + +qb.resetQuery(); +qb.where('position <',3); +qb.whereArray.should.eql(["`position` < 3"]); + +qb.resetQuery(); +qb.where('position >=',3); +qb.whereArray.should.eql(["`position` >= 3"]); + +qb.resetQuery(); +qb.where('position <=',3); +qb.whereArray.should.eql(["`position` <= 3"]); + +qb.resetQuery(); +qb.where('position <>',3); +qb.whereArray.should.eql(["`position` <> 3"]); + +qb.resetQuery(); +qb.where('position !=',3); +qb.whereArray.should.eql(["`position` != 3"]); +``` + +should not escape fields if asked not to. + +```js +qb.resetQuery(); +qb.where({star_system:'Solar',planet:['Earth','Mars']},false); +qb.whereArray.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); +``` + From d46d09f201c3553a1a6fc59fbbe40414797fb380 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 6 Aug 2014 10:48:39 -0400 Subject: [PATCH 022/200] Tests have been written for select, from, join, where, and orderby. --- 1 | 5 ++ Makefile | 2 +- lib/query_builder.js | 171 +++++++++++++++++++++++++++--------------- test/tests-orderby.js | 107 ++++++++++++++++++++++++++ test/tests-where.js | 11 ++- 5 files changed, 231 insertions(+), 65 deletions(-) create mode 100644 1 create mode 100755 test/tests-orderby.js diff --git a/1 b/1 new file mode 100644 index 0000000..6a0dc75 --- /dev/null +++ b/1 @@ -0,0 +1,5 @@ +TESTS = test/*.js +test: + mocha --timeout 5000 --check-leaks --reporter nyan $(TESTS) + +.PHONY: test diff --git a/Makefile b/Makefile index b18e3a3..6a0dc75 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/*.js test: - mocha --timeout 5000 --reporter markdown $(TESTS) + mocha --timeout 5000 --check-leaks --reporter nyan $(TESTS) .PHONY: test diff --git a/lib/query_builder.js b/lib/query_builder.js index d053f53..c335d92 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -9,7 +9,7 @@ var QueryBuilder = function() { values[i] = item[keys[i]]; } return values; - } + }; // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // var trackAliases = function(qb,table) { @@ -40,7 +40,7 @@ var QueryBuilder = function() { qb.aliasedTables.push(alias); } } - } + }; var createAliasFromTable = function(item) { if (item.indexOf('.') !== -1) { @@ -75,7 +75,7 @@ var QueryBuilder = function() { // remove duplicates if the user already included the escape return str.replace(/[`]+/g,'`'); - } + }; var protectIdentifiers = function(qb,item,protect_identifiers) { if (item === '') return item; @@ -105,7 +105,8 @@ var QueryBuilder = function() { if (has_alias >= 0) { alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); alias = escapeIdentifiers(alias); - alias = ' AS ' + alias; + if (alias != '') + alias = ' AS ' + alias; item = item.substr(0,has_alias + 1); } else { alias = ''; @@ -176,7 +177,7 @@ var QueryBuilder = function() { } } return true; - } + }; var qb_escape = function(qb,str) { if(qb.hasOwnProperty('escape')) { @@ -197,7 +198,7 @@ var QueryBuilder = function() { } return str; - } + }; // ---------------------------- SQL BUILD TOOLS ----------------------------// @@ -208,7 +209,7 @@ var QueryBuilder = function() { } sql += qb.whereArray.join(" "); return sql; - } + }; var buildFromClause = function(qb) { var sql = ''; @@ -234,7 +235,7 @@ var QueryBuilder = function() { var sql = ' GROUP BY '; sql += qb.groupByArray.join(', '); return sql; - } + }; var buildHavingClause = function(qb) { if (qb.havingArray.length <= 0) return ''; @@ -242,7 +243,7 @@ var QueryBuilder = function() { var sql = ' HAVING '; sql += qb.havingArray.join(' '); return sql; - } + }; var buildOrderByClause = function(qb) { if (qb.orderByArray.length <= 0) return ''; @@ -255,7 +256,7 @@ var QueryBuilder = function() { } return sql; - } + }; var buildLimitClause = function(sql, limit, offset) { if (!limit) return sql; @@ -269,7 +270,7 @@ var QueryBuilder = function() { offset += ', '; } return sql.replace(/\s+$/, '') + 'LIMIT ' + offset + limit; - } + }; var compileSelect = function(qb) { var sql = 'SELECT ' + qb.distinctClause; @@ -288,7 +289,43 @@ var QueryBuilder = function() { sql = buildLimitClause(sql,qb.limitTo,qb.offsetVal); return sql; - } + }; + + var compileDelete = function(qb) { + if (typeof table !== 'function') { + trackAliases(this,table); + this.from(table); + } + else { + if (fromArray.length == 0) { + throw new Error('You have not specified any tables to delete from!'); + return this; + } + } + + var sql = 'DELETE' + buildFromClause(this) + buildWhereClause(this); + return buildLimitClause(sql,limitTo,offsetVal); + }; + + var compileUpdate = function(qb) { + var valstr = []; + for (var i in qb.setArray) { + valstr.push(key + ' = ' + qb.setArray[i]); + } + + if (qb.fromArray.length === 1) { + var table = qb.fromArray.toString(); + } else { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + var limit = (!qb.limitTo ? '' : ' LIMIT ' + qb.limitTo); + sql = 'UPDATE ' + table + " SET " + valstr.join(', '); + sql += buildWhereClause(qb); + sql += buildOrderByClause(qb); + sql += buildLimitClause(qb); + return sql; + }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// @@ -441,8 +478,8 @@ var QueryBuilder = function() { this.whereInArray.push(qb_escape(this,values[i])); } - prefix = (this.whereArray.length == 0 ? '' : type); - where_in = prefix + protectIdentifiers(this,key,escape) + not + " IN (" + this.whereInArray.join(', ') + ")"; + var prefix = (this.whereArray.length == 0 ? '' : type); + var where_in = prefix + protectIdentifiers(this,key,escape) + not + " IN (" + this.whereInArray.join(', ') + ")"; this.whereArray.push(where_in); // reset the array for multiple calls @@ -785,47 +822,49 @@ var QueryBuilder = function() { }, order_by: function(orderby, direction) { - direction = direction || ''; - var rand_word = ' RAND()'; + var m; + var rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - if (direction.toLowerCase() == 'random') { - orderby = ''; // Random results want or don't need a field name - direction = rand_word; - } - else if (direction.trim() != '') { - direction = (direction.trim().match(/^(ASC|DESC)$/i) ? ' ' + direction.toUpperCase() : ' ASC'); + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()') && Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + this.orderByArray.push(rand_word); + return this; } - - - if (orderby.indexOf(',') !== -1) { - temp = []; - var parts = orderby.split(','); - for (var i in parts) { - var part = parts[i].trim(); - - if (this.aliasedTables.indexOf(part) === -1) { - part = protectIdentifiers(this,part.trim()); - } - - temp.push(part); + + // normalize orderby to be an array of items + if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + if (typeof orderby === 'string') { + orderby = orderby.split(','); + } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { + this.orderByArray.push(rand_word); + return this; + } + else { + throw new Error("No field provided to order by!"); } - - orderby = temp.join(', '); } - else if (direction != rand_word) { - if (!orderby.match(/\s+(ASC|DESC)\s*/i)) { - orderby = protectIdentifiers(this,orderby); + + for (var i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); + + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.orderByArray.push(rand_word); + return this; + } + orderby[i] = {field: protectIdentifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; } else { - var parts = orderby.match(/(.+)\s(ASC|DESC)/i); - if (parts.length === 3) { - orderby = protectIdentifiers(this,parts[1]) + ' ' + parts[2].toUpperCase(); + if (direction.match(/^(ASC|DESC)$/i) || direction === '') { + orderby[i] = {field: protectIdentifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + } else { + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); } } + + this.orderByArray.push(orderby[i].field + ' ' + orderby[i].direction); } - orderby_statement = orderby + direction; - this.qb.orderByArray.push(orderby_statement); - return this; }, @@ -1027,28 +1066,30 @@ var QueryBuilder = function() { this.where(where); } - var sql = this._update(table, this.setArray, this.whereArray, this.orderByArray, this.limitTo); + var sql = this._update(this); return sql; }, - _update: function(table,values,where,orderby,limit) { - orderby = orderby || []; - limit = limit || false; - - var valstr = []; - for (var i in values) { - valstr.push(key + ' = ' + values[i]); + delete: function(table, callback) { + return compiledDelete(this); + }, + + get_compiled_select: function(table) { + if (typeof table !== 'undefined') { + trackAliases(this,table); + this.from(table); + } + else { + if (fromArray.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; + } } - limit = (!limit ? '' : ' LIMIT ' + limit); - orderby = (orderby.length >= 1 ? ' ORDER BY ' + orderby.join(', ') : ''); - sql = 'UPDATE ' + table + " SET " + valstr.join(', '); - sql += (where != '' && where.length >= 1 ? ' WHERE ' + where.join(' ') : ''); - sql += orderby + limit; - return sql; + return compileSelect(this); }, - delete: function(table, callback) { + get_compiled_delete: function(table) { if (typeof table !== 'function') { trackAliases(this,table); this.from(table); @@ -1064,6 +1105,14 @@ var QueryBuilder = function() { return buildLimitClause(sql,limitTo,offsetVal); }, + get_compiled_update: function(table) { + return compileSelect(this); + }, + + get_compiled_insert: function(table) { + throw new Error("This function has not yet been omplemented!"); + }, + _last_query: function() { return this.lastQueryString; } diff --git a/test/tests-orderby.js b/test/tests-orderby.js new file mode 100755 index 0000000..c6a040d --- /dev/null +++ b/test/tests-orderby.js @@ -0,0 +1,107 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('order_by()', function() { + it('should exist', function() { + should.exist(qb.order_by); + }); + it('should be a function', function() { + qb.order_by.should.be.a('function'); + }); + it('should accept a field and direction separated by a space as first parameter and escape the field', function() { + qb.order_by('planet_position asc'); + qb.orderByArray.should.eql(['`planet_position` ASC']); + }); + it('should accept a field and direction as seperate parameters and escape the field', function() { + qb.resetQuery(); + qb.order_by('planet_position', 'asc'); + qb.orderByArray.should.eql(['`planet_position` ASC']); + }); + it('should add additional order_by calls to teh order by array', function() { + qb.resetQuery(); + qb.order_by('planet_position', 'asc'); + qb.order_by('planet_size', 'desc'); + qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); + qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + }); + it('should assume ASC when no direction is provided', function() { + qb.resetQuery(); + qb.order_by('planet_position'); + qb.orderByArray.should.eql(['`planet_position` ASC']); + }); + it('should only accept valid ordering directions (ASC, DESC, random)', function() { + qb.resetQuery(); + expect(function() { qb.order_by('planet_position'); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position','ASC'); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position','DESC'); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position','random'); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',null); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',undefined); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',false); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',3); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',true); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',[]); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',{}); }).to.not.throw(Error); + expect(function() { qb.order_by('planet_position',''); }).to.not.throw(Error); + + // Only an invalid string will throw an error + expect(function() { qb.order_by('planet_position','FAKE'); }).to.throw(Error); + }); + it('should accept a comma-separated list of fields to order by with a single direction at the end', function() { + qb.resetQuery(); + qb.order_by('planet_position, planet_size asc'); + qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + }); + it('should accept a comma-separated list of field + direction pairs', function() { + qb.resetQuery(); + qb.order_by('planet_position desc, planet_size asc'); + qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + }); + it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', function() { + qb.resetQuery(); + qb.order_by('planet_position', 'random'); + qb.orderByArray.should.eql(['RAND()']); + + qb.resetQuery(); + qb.order_by('planet_size', 'RAND'); + qb.orderByArray.should.eql(['RAND()']); + + qb.resetQuery(); + qb.order_by('planet_position, planet_size', 'rand'); + qb.orderByArray.should.eql(['RAND()']); + + qb.resetQuery(); + qb.order_by(null, 'RAND()'); + qb.orderByArray.should.eql(['RAND()']); + + qb.resetQuery(); + qb.order_by('', 'rand'); + qb.orderByArray.should.eql(['RAND()']); + }); + it('should accept an array of field + direction pairs', function() { + qb.resetQuery(); + qb.order_by(['planet_position DESC', 'planet_size ASC']); + qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + }); + it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', function() { + qb.resetQuery(); + qb.order_by(['planet_position', 'planet_size'], 'desc'); + qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + + qb.resetQuery(); + qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); + qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + }); + it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', function() { + qb.resetQuery(); + qb.order_by(['planet_position', 'planet_size']); + qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + + }); +}); \ No newline at end of file diff --git a/test/tests-where.js b/test/tests-where.js index e263502..b0a538e 100755 --- a/test/tests-where.js +++ b/test/tests-where.js @@ -107,9 +107,14 @@ describe('where()', function() { qb.where({star_system:'Solar',planet:['Earth','Mars']},false); qb.whereArray.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); - it("should even escape complex/compound where clauses when provided as a string in the first and only parameter", function() { + it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", function() { qb.resetQuery(); - qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Mikly Way','Andromeda'))"); - qb.whereArray.should.eql("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Mikly Way','Andromeda'))"); + qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.whereArray.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + }); + it("should escape (quote) functions and subqueries as strings when provided as second parameter", function() { + qb.resetQuery(); + qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.whereArray.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); }); }); \ No newline at end of file From 235c6045bc43aa57d96344bd0ae1d7ca6ee55f90 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 7 Aug 2014 10:15:17 -0400 Subject: [PATCH 023/200] Latest tests... including a fix to order_by(). --- 1 | 5 ---- lib/query_builder.js | 64 ++++++++++++++++++++++++++++++++++--------- test/test-limit.js | 42 ++++++++++++++++++++++++++++ test/tests-orderby.js | 19 +++++++++++++ 4 files changed, 112 insertions(+), 18 deletions(-) delete mode 100644 1 create mode 100755 test/test-limit.js diff --git a/1 b/1 deleted file mode 100644 index 6a0dc75..0000000 --- a/1 +++ /dev/null @@ -1,5 +0,0 @@ -TESTS = test/*.js -test: - mocha --timeout 5000 --check-leaks --reporter nyan $(TESTS) - -.PHONY: test diff --git a/lib/query_builder.js b/lib/query_builder.js index c335d92..d206204 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -10,6 +10,44 @@ var QueryBuilder = function() { } return values; }; + + var prepareForLimitAndOffset = function(item,type) { + if ((typeof item).match(/^(string|number)$/) === null) { + throw new Error("Only integers or integers in the form of a string are allowed"); + } + + if (typeof item === 'string') { + item = item.trim(); + if (item.match(/^\d+$/) === null) { + throw new Error("The string you provided to " + type + " by contains more than integer values--this isn't allowed."); + } + // Force to an integer + item = parseInt(item); + } + + // Make sure the number is a good one + if (typeof item === 'number') { + // Check for NaN and Infinity + if (item !== +item || item === Infinity) { + throw new Error("You have not provided a valid number to " + type + " by!"); + } + + // Make sure it's positive + if (item < 0) { + throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSETT' : 'LIMIT') + "ing SQL results!"); + } + + // Only allow integers + if (item % 1 !== 0) { + throw new Error("You cannot " + type + " a SQL result set with a float!"); + } + } + else { + throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); + } + + return item; + } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // var trackAliases = function(qb,table) { @@ -827,7 +865,7 @@ var QueryBuilder = function() { direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()') && Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { this.orderByArray.push(rand_word); return this; } @@ -835,6 +873,10 @@ var QueryBuilder = function() { // normalize orderby to be an array of items if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } orderby = orderby.split(','); } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { this.orderByArray.push(rand_word); @@ -845,6 +887,10 @@ var QueryBuilder = function() { } } + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } + for (var i in orderby) { orderby[i] = orderby[i].replace(/\s+/g, ' '); @@ -869,24 +915,16 @@ var QueryBuilder = function() { }, limit: function(limit, offset) { - if (!(typeof limit).match(/^(string|number)$/) || !limit.match(/^\d+$/)) { - return this; - } + this.limitTo = prepareForLimitAndOffset(limit,'limit'); - if (!(typeof offset).match(/^(string|number)$/) || !offset.match(/^\d+$/)) { - offset = 0; + if (offset !== undefined) { + this.offsetVal = prepareForLimitAndOffset(offset,'offset'); } - - this.limitTo = parseInt(limit); - this.offsetVal = parseInt(offset); - return this; }, offset: function(offset) { - if ((typeof offset).match(/^(string|number)$/) && offset.match(/^\d+$/)) { - this.offsetVal = offset; - } + this.offsetVal = prepareForLimitAndOffset(offset,'offset'); return this; }, diff --git a/test/test-limit.js b/test/test-limit.js new file mode 100755 index 0000000..4eb62f7 --- /dev/null +++ b/test/test-limit.js @@ -0,0 +1,42 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('limit()', function() { + it('should exist', function() { + should.exist(qb.limit); + }); + it('should be a function', function() { + qb.limit.should.be.a('function'); + }); + it('should require an integer (or integer in string form) in first parameter', function() { + qb.resetQuery(); + expect(function() { qb.limit(5); }, 'integer provided').to.not.throw(Error); + expect(function() { qb.limit('5'); }, '5 in string form provided').to.not.throw(Error); + expect(function() { qb.limit(5.7); }, 'float provided').to.throw(Error); + expect(function() { qb.limit('5.7');}, 'float provided').to.throw(Error); + expect(function() { qb.limit(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.limit(null); }, 'null provided').to.throw(Error); + expect(function() { qb.limit(true); }, 'true provided').to.throw(Error); + expect(function() { qb.limit(false);}, 'false provided').to.throw(Error); + expect(function() { qb.limit(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.limit({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.limit([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.limit([5]); }, 'array with integer in it provided').to.throw(Error); + }); + it('should allow an integer (or integer in string form) in second parameter', function() { + qb.resetQuery(); + expect(function() { qb.limit(10,5); }, 'integer provided').to.not.throw(Error); + expect(function() { qb.limit(10,'5'); }, '5 in string form provided').to.not.throw(Error); + expect(function() { qb.limit(10,5.7); }, 'float provided').to.throw(Error); + expect(function() { qb.limit(10,'5.7');}, 'float provided').to.throw(Error); + expect(function() { qb.limit(10,null); }, 'null provided').to.throw(Error); + expect(function() { qb.limit(10,true); }, 'true provided').to.throw(Error); + expect(function() { qb.limit(10,false);}, 'false provided').to.throw(Error); + expect(function() { qb.limit(10,''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.limit(10,{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.limit(10,[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.limit(10,[5]); }, 'array with integer in it provided').to.throw(Error); + }); +}); \ No newline at end of file diff --git a/test/tests-orderby.js b/test/tests-orderby.js index c6a040d..babbe04 100755 --- a/test/tests-orderby.js +++ b/test/tests-orderby.js @@ -10,7 +10,26 @@ describe('order_by()', function() { it('should be a function', function() { qb.order_by.should.be.a('function'); }); + it('should require non-empty string or array as first param unless random is provided as second parameter', function() { + qb.order_by('planet_position asc'); + expect(function() { qb.order_by(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.order_by(null); }, 'null provided').to.throw(Error); + expect(function() { qb.order_by(false); }, 'false provided').to.throw(Error); + expect(function() { qb.order_by(true); }, 'true provided').to.throw(Error); + expect(function() { qb.order_by({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.order_by(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.order_by(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.order_by([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.order_by(''); }, 'empty string provided').to.throw(Error); + // If random + expect(function() { qb.order_by('','rand'); }, 'empty string and random direction provided').to.not.throw(Error); + expect(function() { qb.order_by(undefined,'rand'); }, 'undefined and random direction provided').to.not.throw(Error); + expect(function() { qb.order_by(null,'rand'); }, 'null and random direction provided').to.not.throw(Error); + expect(function() { qb.order_by(false,'rand'); }, 'false and random direction provided').to.not.throw(Error); + expect(function() { qb.order_by([],'rand'); }, 'empty array and random direction provided').to.not.throw(Error); + }); it('should accept a field and direction separated by a space as first parameter and escape the field', function() { + qb.resetQuery(); qb.order_by('planet_position asc'); qb.orderByArray.should.eql(['`planet_position` ASC']); }); From e4f5d95dfcde499027282b67d071b8839fd369b0 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 20 Aug 2014 18:05:38 -0400 Subject: [PATCH 024/200] Added a bunch of new tests and fixed a lot of code. --- lib/query_builder.js | 282 ++++++++++++++++++++++++++++++++--------- test/test-distinct.js | 18 +++ test/test-limit.js | 55 +++++++- test/test-offset.js | 60 +++++++++ test/tests-groupby.js | 52 ++++++++ test/tests-having.js | 109 ++++++++++++++++ test/tests-like.js | 189 +++++++++++++++++++++++++++ test/tests-orderby.js | 7 +- test/tests-select.js | 19 ++- test/tests-set.js | 96 ++++++++++++++ test/tests-where.js | 49 +++++++ test/tests-where_in.js | 177 ++++++++++++++++++++++++++ 12 files changed, 1043 insertions(+), 70 deletions(-) create mode 100755 test/test-distinct.js create mode 100755 test/test-offset.js create mode 100755 test/tests-groupby.js create mode 100755 test/tests-having.js create mode 100755 test/tests-like.js create mode 100755 test/tests-set.js create mode 100755 test/tests-where_in.js diff --git a/lib/query_builder.js b/lib/query_builder.js index d206204..c0118f4 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -48,7 +48,41 @@ var QueryBuilder = function() { return item; } - + + var extractHavingParts = function(key,key_array) { + var m; + key = key.trim().replace(/\s+/g,' '); + var str_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + var sstr_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + var num_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + var bool_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s((true|false)+)$/; + + if (m = str_condition.exec(key)) { + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = sstr_condition.exec(key)) { + //console.log("Key has sstring value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = num_condition.exec(key)) { + //console.log("Key has numeric value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = bool_condition.exec(key)) { + //console.log("Key has boolean value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else { + throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); + } + + return key_array; + } + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // var trackAliases = function(qb,table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { @@ -225,7 +259,7 @@ var QueryBuilder = function() { var do_escape = SqlString.escape; } - if (typeof str === 'string') { + if (typeof str === 'string' && !str.match(/^\d+$/)) { str = do_escape(str); } else if (typeof str === 'boolean') { @@ -429,33 +463,28 @@ var QueryBuilder = function() { return this._where(key, value, 'OR ', escape); }, - where_in: function(key, values) { - return this._where_in(key,values,false,'AND '); - }, - - or_where_in: function(key, values) { - return this._where_in(key,values,false,'OR '); - }, - - where_not_in: function(key, values) { - return this._where_in(key,values,true,'AND '); - }, - - or_where_not_in: function(key, values) { - return this._where_in(key,values,true,'OR '); - }, - _where: function(key, value, type, escape) { value = (typeof value === 'undefined' ? null : value); type = type || 'AND '; escape = (typeof escape === 'boolean' ? escape : true); if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + if (key.length == 0) { + throw new Error("where(): Invalid field name provided."); + } + } var key_array = {}; key_array[key] = value; key = key_array; } + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } + for (var k in key) { var v = key[k]; @@ -498,18 +527,41 @@ var QueryBuilder = function() { return this; }, + where_in: function(key, values, escape) { + return this._where_in(key,values,false,'AND ', escape); + }, + + or_where_in: function(key, values, escape) { + return this._where_in(key,values,false,'OR ', escape); + }, + + where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'AND ', escape); + }, + + or_where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'OR ', escape); + }, + _where_in: function(key, values, not, type, escape) { - key = key || null; + key = key || ''; values = values || []; type = type || 'AND '; not = (not ? ' NOT' : ''); escape = (typeof escape === 'boolean' ? escape : true); - - if(key === null || values.length === 0) return this; + + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } // Values must be an array... - if(Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { - values = [values]; + if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); + } } for (var i in values) { @@ -526,61 +578,76 @@ var QueryBuilder = function() { }, like: function(field, match, side) { - match = match || ''; - side = side || 'both'; - return this._like(field, match, 'AND ', side, ''); }, not_like: function(field, match, side) { - match = match || ''; - side = side || 'both'; - return this._like(field, match, 'AND ', side, ' NOT'); }, or_like: function(field, match, side) { - match = match || ''; - side = side || 'both'; - return this._like(field, match, 'OR ', side, ''); }, or_not_like: function(field, match, side) { - match = match || ''; - side = side || 'both'; - return this._like(field, match, 'OR ', side, ' NOT'); }, _like: function(field, match, type, side, not) { - match = match || ''; + match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); type = type || 'AND '; - side = side || 'both'; + side = (typeof side === 'undefined' ? 'both' : side); not = not || ''; - + + if (typeof field === 'string' && field.length == 0) { + throw new Error("The field provided in your like call is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("The object provided in your like call is empty."); + } + else if ((typeof field).match(/^(string|object)$/) === null) { + throw new Error("You have provided an invalid value as the first parameter in your like() call. Only valid strings and objects are allowed."); + } + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - field_array = {}; + if (match === null) { + throw new Error("like(): Since your first parameters is a string, your second param must a valid number, boolean, or string."); + } + + var field_array = {}; field_array[field] = match; field = field_array; } for(k in field) { - v = field[k]; - k = protectIdentifiers(this,k.trim()); - + var like_statement; + var v = field[k]; + var k = protectIdentifiers(this,k.trim()); + + // Make sure value is only string, number, or boolean + if ((typeof v).match(/^(string|number|boolean)$/) === null) { + throw new Error("You have provided an invalid value as the second parameter in your like() call. Only valid strings, numbers, and booleans are allowed."); + } + // if number, no Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("You have provided an invalid number value as the second parameter in your like() call. Only valid strings, numbers, and booleans are allowed."); + } + if (side === 'none') { like_statement = k + not + ' LIKE ' + "'" + v + "'"; } - else if (side === 'before') { + else if (side === 'before' || side === 'left') { like_statement = k + not + ' LIKE ' + "'%" + v + "'"; } - else if (side === 'after') { + else if (side === 'after' || side === 'right') { like_statement = k + not + ' LIKE ' + "'" + v + "%'"; } - else { + else if (side === 'both') { like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; } + else { + throw new Error("like(): Invalid direction provided!"); + } this._where(like_statement,null,type,false); } @@ -795,25 +862,48 @@ var QueryBuilder = function() { alias = createAliasFromTable(select.trim()); } - sql = type + '(' + protectIdentifiers(this,select.trim()) + ') AS ' + alias; + var sql = type + '(' + protectIdentifiers(this,select.trim()) + ') AS ' + alias; this.selectArray.push(sql); return this; }, - distinct: function() { - this.distinctClause = 'DISTINCT '; + distinct: function(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + + if (do_distinct) { + this.distinctClause = 'DISTINCT '; + } + else { + this.distinctClause = ''; + } return this; }, group_by: function(by) { if (typeof by === 'string') { + by = by.trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } by = by.split(','); } + + if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } + + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } - for (key in by) { - val = by[key].trim(); + for (var key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); + } + + var val = by[key].trim(); if (val !== '') { this.groupByArray.push(protectIdentifiers(this,val)); @@ -823,37 +913,80 @@ var QueryBuilder = function() { }, having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'AND ', escape); }, - or_having: function(key,value,escape) { + or_having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'OR ', escape); }, _having: function(key, value, type, escape) { - if (Object.prototype.toString.call(from) !== Object.prototype.toString.call({})) { - var key_array = {}; + type = type || 'AND '; + + var m; + var key_array = {}; + var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); + + if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... + throw new Error("The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } key_array[key] = value; key = key_array; } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = extractHavingParts(key,key_array); + } + } + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (var i in key) { + if (typeof key[i] !== 'string') { + throw new Error("You've provided an unparseable format to the having() method.."); + } + else { + key_array = extractHavingParts(key[i],key_array); + } + } + key = key_array; + } + } + } + else { + throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); + } + for (var k in key) { var v = key[k]; - prefix = (this.havingArray.length == 0 ? '' : type); + var prefix = (this.havingArray.length == 0 ? '' : type); if (escape === true) { k = protectIdentifiers(this,k); } - if (!hasOperator(k)) { - k += ' = '; + if (v === null) { + k += ' IS'; + } + else if (!hasOperator(k)) { + k += ' ='; } if (v != '') { v = ' ' + qb_escape(this,v); } - this.havingArray.push(prefix + k + k); + this.havingArray.push(prefix + k + v); } return this; @@ -929,22 +1062,45 @@ var QueryBuilder = function() { }, set: function(key, value, escape) { - escape = escape || true; - value = value || ''; + escape = (typeof escape === 'boolean' ? escape : true); - if(Object.prototype.toString.call(value) !== Object.prototype.toString.call({})) { + if (typeof key === 'string') { + key = key.trim(); + if (key.length == 0) { + throw new Error("set(): Invalid field name provided!"); + } + var key_array = {}; key_array[key] = value; key = key_array; } + else if(Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length == 0) { + throw new Error("set(): The object you provided is empty."); + } + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); + } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } for (var i in key) { var v = key[i]; + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if(typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + if (escape === false) { - this.setArray[protectIdentifiers(this,k)] = v; + this.setArray[protectIdentifiers(this,i,escape)] = v; } else { - this.setArray[protectIdentifiers(this,k,false)] = this.escape(v); + this.setArray[protectIdentifiers(this,i,escape)] = qb_escape(this,v); } } @@ -1144,7 +1300,7 @@ var QueryBuilder = function() { }, get_compiled_update: function(table) { - return compileSelect(this); + return compiledUpdate(this); }, get_compiled_insert: function(table) { diff --git a/test/test-distinct.js b/test/test-distinct.js new file mode 100755 index 0000000..8576ddb --- /dev/null +++ b/test/test-distinct.js @@ -0,0 +1,18 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('distinct()', function() { + it('should exist', function() { + should.exist(qb.distinct); + }); + it('should be a function', function() { + qb.distinct.should.be.a('function'); + }); + it('should override the default distinctClause with the "DISTINCT " keyword', function() { + qb.resetQuery(); + qb.distinct(); + qb.distinctClause.should.eql('DISTINCT '); + }); +}); \ No newline at end of file diff --git a/test/test-limit.js b/test/test-limit.js index 4eb62f7..173880e 100755 --- a/test/test-limit.js +++ b/test/test-limit.js @@ -10,12 +10,20 @@ describe('limit()', function() { it('should be a function', function() { qb.limit.should.be.a('function'); }); + it('should have an array to put fields into', function() { + qb.should.have.property('limitTo'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.limitTo.should.be.false; + }); it('should require an integer (or integer in string form) in first parameter', function() { qb.resetQuery(); expect(function() { qb.limit(5); }, 'integer provided').to.not.throw(Error); expect(function() { qb.limit('5'); }, '5 in string form provided').to.not.throw(Error); expect(function() { qb.limit(5.7); }, 'float provided').to.throw(Error); expect(function() { qb.limit('5.7');}, 'float provided').to.throw(Error); + expect(function() { qb.limit('abc');}, 'alpha provided').to.throw(Error); + expect(function() { qb.limit('abc7');}, 'alpha numerics provided').to.throw(Error); expect(function() { qb.limit(); }, 'nothing provided').to.throw(Error); expect(function() { qb.limit(null); }, 'null provided').to.throw(Error); expect(function() { qb.limit(true); }, 'true provided').to.throw(Error); @@ -25,18 +33,55 @@ describe('limit()', function() { expect(function() { qb.limit([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.limit([5]); }, 'array with integer in it provided').to.throw(Error); }); - it('should allow an integer (or integer in string form) in second parameter', function() { + it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', function() { qb.resetQuery(); expect(function() { qb.limit(10,5); }, 'integer provided').to.not.throw(Error); expect(function() { qb.limit(10,'5'); }, '5 in string form provided').to.not.throw(Error); expect(function() { qb.limit(10,5.7); }, 'float provided').to.throw(Error); - expect(function() { qb.limit(10,'5.7');}, 'float provided').to.throw(Error); - expect(function() { qb.limit(10,null); }, 'null provided').to.throw(Error); - expect(function() { qb.limit(10,true); }, 'true provided').to.throw(Error); - expect(function() { qb.limit(10,false);}, 'false provided').to.throw(Error); + expect(function() { qb.limit(10,'5.7'); }, 'float provided').to.throw(Error); + expect(function() { qb.limit(10,'abc'); }, 'alpha provided').to.throw(Error); + expect(function() { qb.limit(10,'abc7');}, 'alphanumerics provided').to.throw(Error); + expect(function() { qb.limit(10,null); }, 'null provided').to.throw(Error); + expect(function() { qb.limit(10,true); }, 'true provided').to.throw(Error); + expect(function() { qb.limit(10,false); }, 'false provided').to.throw(Error); expect(function() { qb.limit(10,''); }, 'empty string provided').to.throw(Error); expect(function() { qb.limit(10,{}); }, 'empty object provided').to.throw(Error); expect(function() { qb.limit(10,[]); }, 'empty array provided').to.throw(Error); expect(function() { qb.limit(10,[5]); }, 'array with integer in it provided').to.throw(Error); }); + it('should override the default limitTo value when a limit is provided', function() { + qb.resetQuery(); + qb.limit(10); + qb.limitTo.should.eql(10); + }); + it('should override the default limitTo and offsetVal values when a limit and an offset are provided', function() { + qb.resetQuery(); + qb.limit(10,20); + qb.limitTo.should.eql(10); + qb.offsetVal.should.eql(20); + }); + it('should trim string values that are provided', function() { + qb.resetQuery(); + qb.limit('10 '); + qb.limitTo.should.eql(10); + }); + it('should trim string values that are provided', function() { + qb.resetQuery(); + qb.limit(' 10 ',' 12'); + qb.limitTo.should.eql(10); + qb.offsetVal.should.eql(12); + }); + it('should override values set by any previous calls to itself', function() { + qb.resetQuery(); + qb.limit(10); + qb.limitTo.should.eql(10); + qb.limit(20); + qb.limitTo.should.eql(20); + }); + it('should be chainable whereby the last call to the method will contain the value(s) used', function() { + qb.resetQuery(); + qb.limit(10,5).limit(20).limit(100,30); + qb.limitTo.should.eql(100); + qb.offsetVal.should.eql(30); + }); }); \ No newline at end of file diff --git a/test/test-offset.js b/test/test-offset.js new file mode 100755 index 0000000..a4bfdfe --- /dev/null +++ b/test/test-offset.js @@ -0,0 +1,60 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('offset()', function() { + it('should exist', function() { + should.exist(qb.offset); + }); + it('should be a function', function() { + qb.offset.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('offsetVal'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.offsetVal.should.be.false; + }); + it('should require an integer (or integer in string form) in first parameter', function() { + qb.resetQuery(); + expect(function() { qb.offset(5); }, 'integer provided').to.not.throw(Error); + expect(function() { qb.offset('5'); }, '5 in string form provided').to.not.throw(Error); + expect(function() { qb.offset(5.7); }, 'float provided').to.throw(Error); + expect(function() { qb.offset('5.7'); }, 'float provided').to.throw(Error); + expect(function() { qb.offset('abc') }, 'alpha provided').to.throw(Error); + expect(function() { qb.offset('abc7'); }, 'alpha numerics provided').to.throw(Error); + expect(function() { qb.offset(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.offset(null); }, 'null provided').to.throw(Error); + expect(function() { qb.offset(true); }, 'true provided').to.throw(Error); + expect(function() { qb.offset(false); }, 'false provided').to.throw(Error); + expect(function() { qb.offset(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.offset({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.offset([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.offset([5]); }, 'array with integer in it provided').to.throw(Error); + }); + it('should override the default offsetVal value when a offset is provided', function() { + qb.resetQuery(); + qb.offset(10); + qb.offsetVal.should.eql(10); + }); + it('should trim string values that are provided', function() { + qb.resetQuery(); + qb.offset('10 '); + qb.offset(' 10'); + qb.offset(' 10 '); + qb.offsetVal.should.eql(10); + }); + it('should override values set by any previous calls to itself', function() { + qb.resetQuery(); + qb.offset(10); + qb.offsetVal.should.eql(10); + qb.offset(20); + qb.offsetVal.should.eql(20); + }); + it('should be chainable whereby the last call to the method will contain the value used', function() { + qb.resetQuery(); + qb.offset(10).offset(20).offset(100); + qb.offsetVal.should.eql(100); + }); +}); \ No newline at end of file diff --git a/test/tests-groupby.js b/test/tests-groupby.js new file mode 100755 index 0000000..730e26f --- /dev/null +++ b/test/tests-groupby.js @@ -0,0 +1,52 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('group_by()', function() { + it('should exist', function() { + should.exist(qb.group_by); + }); + it('should be a function', function() { + qb.group_by.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('groupByArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.groupByArray.should.be.empty; + }); + it('should accept a single field in string form', function() { + qb.resetQuery(); + qb.group_by('planet_type'); + qb.groupByArray.should.eql(['`planet_type`']); + }); + it('should accept a multiple fields delimited by commas', function() { + qb.resetQuery(); + qb.group_by('planet_type, planet_position'); + qb.groupByArray.should.eql(['`planet_type`','`planet_position`']); + }); + it('should accept an array of fields', function() { + qb.resetQuery(); + qb.group_by(['planet_type', 'planet_position']); + qb.groupByArray.should.eql(['`planet_type`','`planet_position`']); + }); + it('should not accept anything but a string or an array of strings', function() { + qb.resetQuery(); + expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); + expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); + expect(function() { qb.group_by(true); }, 'true provided').to.throw(Error); + expect(function() { qb.group_by({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.group_by(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.group_by(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); + + // valid string + expect(function() { qb.group_by('planet_type'); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.group_by(['planet_type']); }, 'array of string(s) provided').to.not.throw(Error); + + }); +}); \ No newline at end of file diff --git a/test/tests-having.js b/test/tests-having.js new file mode 100755 index 0000000..8dd1c4c --- /dev/null +++ b/test/tests-having.js @@ -0,0 +1,109 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('having()', function() { + it('should exist', function() { + should.exist(qb.having); + }); + it('should be a function', function() { + qb.having.should.be.a('function'); + }); + it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', function() { + qb.resetQuery(); + qb.having('planet_class > "M"'); + qb.havingArray.should.eql(["`planet_class` > 'M'"]); + + qb.resetQuery(); + qb.having('planet_class < "M"'); + qb.havingArray.should.eql(["`planet_class` < 'M'"]); + + qb.resetQuery(); + qb.having('planet_class <> "M"'); + qb.havingArray.should.eql(["`planet_class` <> 'M'"]); + + qb.resetQuery(); + qb.having('planet_class >= "M"'); + qb.havingArray.should.eql(["`planet_class` >= 'M'"]); + + qb.resetQuery(); + qb.having('planet_class <= "M"'); + qb.havingArray.should.eql(["`planet_class` <= 'M'"]); + + qb.resetQuery(); + qb.having('planet_class = "M"'); + qb.havingArray.should.eql(["`planet_class` = 'M'"]); + + qb.resetQuery(); + qb.having('planet_class != "M"'); + qb.havingArray.should.eql(["`planet_class` != 'M'"]); + }); + it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', function() { + qb.resetQuery(); + expect(function() { qb.having('planet_class = "M", sentient_life = 1'); }, 'two conditions provided').to.throw(Error); + }); + it('should accept an array of conditions and prepend AND to each condition following the first one', function() { + qb.resetQuery(); + qb.having(["planet_class = 'M'", 'sentient_life = 1']); + qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); + }); + it('should accept an object of conditions and prepend AND to each condition following the first one', function() { + qb.resetQuery(); + var object = {planet_class: 'M', sentient_life: 1}; + object['planet_order <='] = 3; + qb.having(object); + qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + }); + it('should not accept anything but a non-empty array, object, or string', function() { + qb.resetQuery(); + expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); + expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); + expect(function() { qb.group_by(true); }, 'true provided').to.throw(Error); + expect(function() { qb.group_by({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.group_by(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.group_by(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); + + // valid string + expect(function() { qb.group_by('planet_type = "M"'); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.group_by(['planet_type = "M"']); }, 'array of string(s) provided').to.not.throw(Error); + }); + it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', function() { + qb.resetQuery(); + qb.having('planet_class','M'); + qb.havingArray.should.eql(["`planet_class` = 'M'"]); + }); + it('should not escape conditions if asked not to', function() { + qb.resetQuery(); + qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); + qb.havingArray.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); + qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + }); +}); + +describe('or_having()', function() { + it('should exist', function() { + should.exist(qb.or_having); + }); + it('should be a function', function() { + qb.or_having.should.be.a('function'); + }); + it('should accept an array of conditions and prepend OR to each condition following the first one', function() { + qb.resetQuery(); + qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); + qb.havingArray.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); + }); + it('should be chainable with normal having', function() { + qb.resetQuery(); + qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); + qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); + }); +}); \ No newline at end of file diff --git a/test/tests-like.js b/test/tests-like.js new file mode 100755 index 0000000..0200a0f --- /dev/null +++ b/test/tests-like.js @@ -0,0 +1,189 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('like()', function() { + it('should exist', function() { + should.exist(qb.like); + }); + it('should be a function', function() { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', function() { + expect(function() { qb.like(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.like(null); }, 'null provided').to.throw(Error); + expect(function() { qb.like(false); }, 'false provided').to.throw(Error); + expect(function() { qb.like(true); }, 'true provided').to.throw(Error); + expect(function() { qb.like({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.like(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.like(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.like([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like(''); }, 'empty string provided').to.throw(Error); + + expect(function() { qb.like('planet_name','ear','after'); }, 'valid string').to.not.throw(Error); + expect(function() { qb.like({planet_name: 'ear'}); }, 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', function() { + expect(function() { qb.like('planet_name'); }, 'no second param provided').to.throw(Error); + expect(function() { qb.like('planet_name','ear'); }, 'valid second param provided').to.not.throw(Error); + expect(function() { qb.like({planet_name: 'ear'}); }, 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', function() { + expect(function() { qb.like('planet_name',null); }, 'null provided').to.throw(Error); + expect(function() { qb.like('planet_name',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.like('planet_name',[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like('planet_name',NaN); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like('planet_name',Infinity);}, 'empty array provided').to.throw(Error); + + expect(function() { qb.like('planet_name',false); }, 'false provided').to.not.throw(Error); + expect(function() { qb.like('planet_name',true); }, 'true provided').to.not.throw(Error); + expect(function() { qb.like('planet_name',3); }, 'integer provided').to.not.throw(Error); + expect(function() { qb.like('planet_name',3.5); }, 'float provided').to.not.throw(Error); + expect(function() { qb.like('planet_name',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.like('planet_name','ear'); }, 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', function() { + expect(function() { qb.like('galaxy_name','milk',null); }, 'null provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',NaN); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',Infinity); }, 'empty array provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',false); }, 'false provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',true); }, 'true provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk',''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.like('galaxy_name','milk','foo'); }, 'non-empty string provided').to.throw(Error); + + expect(function() { qb.like('galaxy_name','milk'); }, 'no third param provided').to.not.throw(Error); + expect(function() { qb.like('galaxy_name','milk','right'); }, 'right as third param').to.not.throw(Error); + expect(function() { qb.like('galaxy_name','milk','left'); }, 'left as third param').to.not.throw(Error); + expect(function() { qb.like('galaxy_name','milk','both'); }, 'both as third param').to.not.throw(Error); + expect(function() { qb.like('galaxy_name','milk','before'); }, 'before as third param').to.not.throw(Error); + expect(function() { qb.like('galaxy_name','milk','after'); }, 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky', 'after'); + qb.whereArray.should.eql(["`galaxy_name` LIKE 'milky%'"]); + + qb.resetQuery(); + qb.like('galaxy_name', 'milky', 'right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky', 'before'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky'"]); + + qb.resetQuery(); + qb.like('galaxy_name', 'milky', 'left'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'"]); + + qb.resetQuery(); + qb.like('galaxy_name', 'milky', 'both'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); +}); + +describe('or_like()', function() { + it('should exist', function() { + should.exist(qb.or_like); + }); + it('should be a function', function() { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', function() { + qb.resetQuery(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + }); +}); + +describe('not_like()', function() { + it('should exist', function() { + should.exist(qb.not_like); + }); + it('should be a function', function() { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', function() { + qb.resetQuery(); + qb.not_like('galaxy_name', 'milky'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', function() { + qb.resetQuery(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); +}); + +describe('or_not_like()', function() { + it('should exist', function() { + should.exist(qb.or_not_like); + }); + it('should be a function', function() { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', function() { + qb.resetQuery(); + qb.or_not_like('galaxy_name', 'milky'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', function() { + qb.resetQuery(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', function() { + qb.resetQuery(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); +}); \ No newline at end of file diff --git a/test/tests-orderby.js b/test/tests-orderby.js index babbe04..b3e6937 100755 --- a/test/tests-orderby.js +++ b/test/tests-orderby.js @@ -10,8 +10,13 @@ describe('order_by()', function() { it('should be a function', function() { qb.order_by.should.be.a('function'); }); + it('should have an array to put fields into', function() { + qb.should.have.property('orderByArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.orderByArray.should.be.empty; + }); it('should require non-empty string or array as first param unless random is provided as second parameter', function() { - qb.order_by('planet_position asc'); expect(function() { qb.order_by(); }, 'nothing provided').to.throw(Error); expect(function() { qb.order_by(null); }, 'null provided').to.throw(Error); expect(function() { qb.order_by(false); }, 'false provided').to.throw(Error); diff --git a/test/tests-select.js b/test/tests-select.js index 0992950..8421e42 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -165,4 +165,21 @@ describe('select()', function() { qb.selectArray.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); }); -}); \ No newline at end of file +}); +var prefixes = ['min','max','avg','sum']; +for (var i in prefixes) { + var type = prefixes[i]; + describe('select_' + type+'()', function() { + it('should exist', function() { + should.exist(qb['select_' + type]); + }); + it('should be a function', function() { + qb['select_' + type].should.be.a('function'); + }); + it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', function() { + qb.resetQuery(); + qb['select_' + type]('s.star_systems'); + qb.selectArray.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); + }); + }); +} \ No newline at end of file diff --git a/test/tests-set.js b/test/tests-set.js new file mode 100755 index 0000000..f682389 --- /dev/null +++ b/test/tests-set.js @@ -0,0 +1,96 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('set()', function() { + it('should exist', function() { + should.exist(qb.set); + }); + it('should be a function', function() { + qb.set.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('setArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.setArray.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object as first param', function() { + qb.resetQuery(); + expect(function() { qb.set(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.set(null); }, 'null provided').to.throw(Error); + expect(function() { qb.set(false); }, 'false provided').to.throw(Error); + expect(function() { qb.set(true); }, 'true provided').to.throw(Error); + expect(function() { qb.set({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.set(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.set(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.set(NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.set(Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.set([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.set([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.set(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.set(' '); }, 'string full of spaces provided').to.throw(Error); + expect(function() { qb.set(/foobar/); }, 'regex provided').to.throw(Error); + + expect(function() { qb.set('planet_position',3); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); + }); + it('should not accept anything but a string, number, null, or boolean as second param', function() { + qb.resetQuery(); + expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); + expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.set('planet_position',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.set('planet_position',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.set('planet_position',[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.set('planet_position',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.set('planet_position',/foobar/); }, 'regex provided').to.throw(Error); + + expect(function() { qb.set('planet_position',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',3); }, 'Integer provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',3.5); }, 'float provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',false); }, 'false provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',true); }, 'true provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',' '); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.set('planet_position','Three'); }, 'valid provided').to.not.throw(Error); + }); + it('should add first param (key) and second param (value) to hash and escape them properly', function() { + qb.resetQuery(); + qb.set('galaxy_name','Milky Way'); + qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'"}); + }); + it('should merge passed object into setArray and escape items properly', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way'}); + qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'"}); + + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); + qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'", "`galaxy_class`": "'C'"}); + }); + it('should not escape items if asked not to', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.setArray.should.eql({galaxy_name: 'Milky Way'}); + }); + it('should append more items to setArray as set() is called', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}, null, false); + qb.set('galaxy_size','D'); + qb.setArray.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); + qb.setArray.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + }); + it('should overwrite values of keys that have been set already', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D'); + qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_size`": "'D'"}); + }); +}); \ No newline at end of file diff --git a/test/tests-where.js b/test/tests-where.js index b0a538e..34d8749 100755 --- a/test/tests-where.js +++ b/test/tests-where.js @@ -10,7 +10,31 @@ describe('where()', function() { it('should be a function', function() { qb.where.should.be.a('function'); }); + it('should have an array to put fields into', function() { + qb.should.have.property('whereArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.whereArray.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object', function() { + qb.resetQuery(); + expect(function() { qb.where(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.where(null); }, 'null provided').to.throw(Error); + expect(function() { qb.where(false); }, 'false provided').to.throw(Error); + expect(function() { qb.where(true); }, 'true provided').to.throw(Error); + expect(function() { qb.where({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.where(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.where(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.where([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.where([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.where(''); }, 'empty string provided').to.throw(Error); + + expect(function() { qb.where('planet_position',3); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.where({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); + + }); it('should accept a field name in the form of a string as the first parameter', function() { + qb.resetQuery(); qb.where('planet'); qb.whereArray.should.eql(['`planet` IS NULL']); }); @@ -117,4 +141,29 @@ describe('where()', function() { qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); qb.whereArray.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); }); +}); + +describe('or_where()', function() { + it('should exist', function() { + should.exist(qb.or_where); + }); + it('should be a function', function() { + qb.or_where.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', function() { + qb.resetQuery(); + qb.or_where('planet','Mars'); + qb.or_where('planet','Venus'); + qb.whereArray.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.or_where('planet','Mars').or_where('planet','Venus'); + qb.whereArray.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + }); + it('should be chainable with normal where', function() { + qb.resetQuery(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); + qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); + }); }); \ No newline at end of file diff --git a/test/tests-where_in.js b/test/tests-where_in.js new file mode 100755 index 0000000..72e3369 --- /dev/null +++ b/test/tests-where_in.js @@ -0,0 +1,177 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('where_in()', function() { + it('should exist', function() { + should.exist(qb.where_in); + }); + it('should be a function', function() { + qb.where_in.should.be.a('function'); + }); + it('should have an array to put fields into', function() { + qb.should.have.property('whereInArray'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.whereInArray.should.be.empty; + }); + it('should not accept anything but a non-empty string as first parameter', function() { + qb.resetQuery(); + expect(function() { qb.where_in(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.where_in(null); }, 'null provided').to.throw(Error); + expect(function() { qb.where_in(false); }, 'false provided').to.throw(Error); + expect(function() { qb.where_in(true); }, 'true provided').to.throw(Error); + expect(function() { qb.where_in({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.where_in({foo:'bar'}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.where_in(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.where_in(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.where_in(NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.where_in(Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.where_in([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.where_in([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.where_in(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.where_in(/foobar/); }, 'regex provided').to.throw(Error); + + expect(function() { qb.where_in('planet_position',[1,2,3]); }, 'valid string provided').to.not.throw(Error); + }); + it('should not accept anything but a non-empty array of values as second parameter', function() { + qb.resetQuery(); + expect(function() { qb.where_in('planet'); }, 'nothing provided').to.throw(Error); + expect(function() { qb.where_in('planet',null); }, 'null provided').to.throw(Error); + expect(function() { qb.where_in('planet',false); }, 'false provided').to.throw(Error); + expect(function() { qb.where_in('planet',true); }, 'true provided').to.throw(Error); + expect(function() { qb.where_in('planet',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.where_in('planet',{foo:'bar'}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.where_in('planet',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.where_in('planet',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.where_in('planet',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.where_in('planet',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.where_in('planet',[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.where_in('planet',''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.where_in('planet',/foobar/); }, 'regex provided').to.throw(Error); + + expect(function() { qb.where_in('planet',['Mars','Earth','Venus','Mercury']); }, 'non-empty array provided').to.not.throw(Error); + }); + it('should require both a field name an array of values as first and second parameters, respectively', function() { + qb.resetQuery(); + qb.where_in('planet_position',[1,2,3]); + qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)']); + }); + it('should concatenate multiple WHERE IN clauses with AND ', function() { + qb.resetQuery(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']); + qb.where_in('galaxy_id',[123,456,789,0110]); + qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); + qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.where_in('planet_position',[1,2,3],false); + qb.whereArray.should.eql(['planet_position IN (1, 2, 3)']); + }); +}); + +describe('where_not_in()', function() { + it('should exist', function() { + should.exist(qb.where_not_in); + }); + it('should be a function', function() { + qb.where_not_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', function() { + qb.resetQuery(); + qb.where_not_in('planet_position',[1,2,3]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "AND"', function() { + qb.resetQuery(); + qb.where_not_in('planet_position',[1,2,3]); + qb.where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', function() { + qb.resetQuery(); + qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.where_not_in('planet_position',[1,2,3],false); + qb.whereArray.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); +}); + +describe('or_where_in()', function() { + it('should exist', function() { + should.exist(qb.or_where_in); + }); + it('should be a function', function() { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', function() { + qb.resetQuery(); + qb.or_where_in('planet_position',[1,2,3]); + qb.or_where_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', function() { + qb.resetQuery(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.or_where_in('planet_position',[1,2,3],false); + qb.whereArray.should.eql(['planet_position IN (1, 2, 3)']); + }); +}); + +describe('or_where_not_in()', function() { + it('should exist', function() { + should.exist(qb.or_where_in); + }); + it('should be a function', function() { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', function() { + qb.resetQuery(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "OR"', function() { + qb.resetQuery(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.or_where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable', function() { + qb.resetQuery(); + qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', function() { + qb.resetQuery(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); + qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', function() { + qb.resetQuery(); + qb.or_where_not_in('planet_position',[1,2,3],false); + qb.whereArray.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); +}); \ No newline at end of file From 041355fa52d97ad7774303af2323cd800ca703e0 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 17 Sep 2014 10:34:06 -0400 Subject: [PATCH 025/200] Making sure this is up to date on github. --- index.js | 42 ++++++++++++++++++++++-- lib/query_builder.js | 48 +++++++++++++++------------- test/tests-delete.js | 76 ++++++++++++++++++++++++++++++++++++++++++++ test/tests-set.js | 9 +++++- 4 files changed, 149 insertions(+), 26 deletions(-) create mode 100755 test/tests-delete.js diff --git a/index.js b/index.js index 6347931..bbdafe7 100755 --- a/index.js +++ b/index.js @@ -84,6 +84,11 @@ var Adapter = function(settings) { this.order_by = this.qb.order_by; this.limit = this.qb.limit; this.offset = this.qb.offset; + this.set = this.qb.set; + this.get_compiled_select = this.qb.get_compiled_select; + this.get_compiled_insert = this.qb.get_compiled_insert; + this.get_compiled_update = this.qb.get_compiled_update; + this.get_compiled_delete = this.qb.get_compiled_delete; this._last_query = this.qb._last_query; this.last_query = this.qb._last_query; @@ -159,16 +164,47 @@ var Adapter = function(settings) { return that; }; - this.delete = function(table,callback) { - var sql = this.delete(table,callback); + this.update_batch = function() { - if (typeof table === 'function') { + return that; + }; + + this.delete = function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); } + + var sql = this.delete(table, where); + this.resetQuery(sql); connection.query(sql, callback); return that; }; + + this.empty_table - function(table, callback) { + var sql = this.qb.empty_table(table,callback); + this.resetQuery(sql); + connection.query(sql); + return that; + }); + + this.truncate - function(table, callback) { + var sql = this.qb.truncate(table,callback); + this.resetQuery(sql); + connection.query(sql); + return that; + }); var initializeConnectionSettings = function () { if(settings.server) { diff --git a/lib/query_builder.js b/lib/query_builder.js index c0118f4..e306b6d 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -364,19 +364,13 @@ var QueryBuilder = function() { }; var compileDelete = function(qb) { - if (typeof table !== 'function') { - trackAliases(this,table); - this.from(table); - } - else { - if (fromArray.length == 0) { - throw new Error('You have not specified any tables to delete from!'); - return this; - } + if (qb.fromArray.length == 0) { + throw new Error('You have not specified any tables to delete from!'); + return this; } - var sql = 'DELETE' + buildFromClause(this) + buildWhereClause(this); - return buildLimitClause(sql,limitTo,offsetVal); + var sql = 'DELETE' + buildFromClause(qb) + buildWhereClause(qb); + return buildLimitClause(sql,qb.limitTo,qb.offsetVal); }; var compileUpdate = function(qb) { @@ -1264,8 +1258,21 @@ var QueryBuilder = function() { return sql; }, - delete: function(table, callback) { - return compiledDelete(this); + delete: function(table, where) { + if ((typeof table == 'string' && table.trim().length > 0) || Object.prototype.toString.call(table) === Object.prototype.toString.call([])) { + this.from(table); + } + + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + } + else { + this.where(where); + } + } + + return compileDelete(this); }, get_compiled_select: function(table) { @@ -1288,19 +1295,16 @@ var QueryBuilder = function() { trackAliases(this,table); this.from(table); } - else { - if (fromArray.length == 0) { - throw new Error('You have not specified any tables to delete from!'); - return this; - } - } - var sql = 'DELETE' + buildFromClause(this) + buildWhereClause(this); - return buildLimitClause(sql,limitTo,offsetVal); + return compileDelete(this); }, get_compiled_update: function(table) { - return compiledUpdate(this); + if (typeof table !== 'function') { + trackAliases(this,table); + this.from(table); + } + return compileUpdate(this); }, get_compiled_insert: function(table) { diff --git a/test/tests-delete.js b/test/tests-delete.js new file mode 100755 index 0000000..b704a31 --- /dev/null +++ b/test/tests-delete.js @@ -0,0 +1,76 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +describe('delete()', function() { + it('should exist', function() { + should.exist(qb.delete); + }); + it('should be a function', function() { + qb.delete.should.be.a('function'); + }); + it('should add a table to fromArray when a table is supplied', function() { + qb.resetQuery(); + qb.delete('galaxies'); + qb.fromArray.should.eql(['`galaxies`']); + }); + it('should add a set of tables to fromArray when an array of tables is supplied', function() { + qb.resetQuery(); + qb.delete(['galaxies','star_systems','planets']); + qb.fromArray.should.eql(['`galaxies`','`star_systems`','`planets`']); + }); + it('should add where conditions to whereArray when conditions are supplied', function() { + qb.resetQuery(); + qb.delete('planets', {continents: 7, star_system: 'Solar'}); + qb.whereArray.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); + }); + it('should accept an array of tables and an object of where conditions and put each in their respective arrays', function() { + qb.resetQuery(); + qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); + qb.fromArray.should.eql(['`galaxies`','`star_systems`','`planets`']); + qb.whereArray.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); + }); + it('should return a string', function() { + qb.resetQuery(); + var sql = qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', function() { + qb.resetQuery(); + var sql = qb.delete('galaxies'); + sql.should.eql('DELETE FROM (`galaxies`)'); + }); + it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', function() { + qb.resetQuery(); + var sql = qb.delete('galaxies', {class: 'M'}); + sql.should.eql("DELETE FROM (`galaxies`) WHERE `class` = 'M'"); + }); + it('should build a properly-escaped delete statement that deletes all records in a set of tables if an array of tables is given', function() { + qb.resetQuery(); + var sql = qb.delete(['galaxies','star_systems','planets']); + sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); + }); + it('should build a properly-escaped delete statement that deletes all records in a set of tables that match the passed conditions', function() { + qb.resetQuery(); + var sql = qb.delete(['galaxies','star_systems','planets'], {class: 'M'}); + sql.should.eql("DELETE FROM (`galaxies`, `star_systems`, `planets`) WHERE `class` = 'M'"); + }); + it('should use tables added previously via the from() method', function() { + qb.resetQuery(); + qb.from('galaxies'); + var sql = qb.delete(); + sql.should.eql('DELETE FROM (`galaxies`)'); + + qb.resetQuery(); + var sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); + }); + it('should accept where conditions added previously via the where() method', function() { + qb.resetQuery(); + var sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); + sql.should.eql("DELETE FROM (`galaxies`) WHERE `created` >= 4600000000 AND `class` = 'M'"); + }); +}); \ No newline at end of file diff --git a/test/tests-set.js b/test/tests-set.js index f682389..9d584eb 100755 --- a/test/tests-set.js +++ b/test/tests-set.js @@ -91,6 +91,13 @@ describe('set()', function() { qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D'); - qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_size`": "'D'"}); + qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'D'"}); + }); + it('should NOT overwrite values of keys that are the same but have different escape flags', function() { + qb.resetQuery(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D', false); + qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'C'", galaxy_class: 'D'}); }); }); \ No newline at end of file From e219a1bbad20436ca4ebede2037b7a46f7594c9b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 24 Sep 2014 14:04:19 -0400 Subject: [PATCH 026/200] Normalized the naming of methods and functions in the query builder and tests. --- lib/query_builder.js | 447 ++++++++++++++++++++++------------------- test/test-distinct.js | 6 +- test/test-limit.js | 44 ++-- test/test-offset.js | 26 +-- test/tests-delete.js | 50 +++-- test/tests-from.js | 78 +++---- test/tests-groupby.js | 18 +- test/tests-having.js | 63 +++--- test/tests-insert.js | 123 ++++++++++++ test/tests-join.js | 36 ++-- test/tests-like.js | 76 +++---- test/tests-orderby.js | 70 +++---- test/tests-select.js | 114 +++++------ test/tests-set.js | 44 ++-- test/tests-where.js | 110 +++++----- test/tests-where_in.js | 80 ++++---- 16 files changed, 778 insertions(+), 607 deletions(-) create mode 100755 test/tests-insert.js diff --git a/lib/query_builder.js b/lib/query_builder.js index e306b6d..62dc0c2 100755 --- a/lib/query_builder.js +++ b/lib/query_builder.js @@ -11,7 +11,10 @@ var QueryBuilder = function() { return values; }; - var prepareForLimitAndOffset = function(item,type) { + var prepare_for_limit_and_offset = function(item,type) { + type = type || 'limit'; + type = type.toLowerCase(); + if ((typeof item).match(/^(string|number)$/) === null) { throw new Error("Only integers or integers in the form of a string are allowed"); } @@ -19,7 +22,7 @@ var QueryBuilder = function() { if (typeof item === 'string') { item = item.trim(); if (item.match(/^\d+$/) === null) { - throw new Error("The string you provided to " + type + " by contains more than integer values--this isn't allowed."); + throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); } // Force to an integer item = parseInt(item); @@ -34,12 +37,12 @@ var QueryBuilder = function() { // Make sure it's positive if (item < 0) { - throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSETT' : 'LIMIT') + "ing SQL results!"); + throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); } // Only allow integers if (item % 1 !== 0) { - throw new Error("You cannot " + type + " a SQL result set with a float!"); + throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); } } else { @@ -49,7 +52,7 @@ var QueryBuilder = function() { return item; } - var extractHavingParts = function(key,key_array) { + var extract_having_parts = function(key,key_array) { var m; key = key.trim().replace(/\s+/g,' '); var str_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting @@ -84,11 +87,11 @@ var QueryBuilder = function() { } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - var trackAliases = function(qb,table) { + var track_aliases = function(qb,table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (var i in table) { var t = table[i]; - trackAliases(qb,t); + track_aliases(qb,t); } return; } @@ -96,7 +99,7 @@ var QueryBuilder = function() { // Does the string contain a comma? If so, we need to separate // the string into discreet statements if (table.indexOf(',') !== -1) { - return trackAliases(qb,table.split(',')); + return track_aliases(qb,table.split(',')); } // if a table alias is used we can recognize it by a space @@ -108,13 +111,13 @@ var QueryBuilder = function() { var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist - if(qb.aliasedTables.indexOf(alias) == -1) { - qb.aliasedTables.push(alias); + if(qb.aliased_tables.indexOf(alias) == -1) { + qb.aliased_tables.push(alias); } } }; - var createAliasFromTable = function(item) { + var create_aliases_from_table = function(item) { if (item.indexOf('.') !== -1) { return item.split('.').reverse()[0]; } @@ -122,14 +125,14 @@ var QueryBuilder = function() { return item; }; - var escapeIdentifiers = function(item) { + var escape_identifiers = function(item) { if (!item || item === '*') { return item; } if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (var i in item) { - item[i] = escapeIdentifiers(item[i]); + item[i] = escape_identifiers(item[i]); } return item; } @@ -149,7 +152,7 @@ var QueryBuilder = function() { return str.replace(/[`]+/g,'`'); }; - var protectIdentifiers = function(qb,item,protect_identifiers) { + var protect_identifiers = function(qb,item,protect_identifiers) { if (item === '') return item; protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); @@ -159,7 +162,7 @@ var QueryBuilder = function() { for (k in item) { var v = item[k]; - escaped_array[protectIdentifiers(qb,k)] = protectIdentifiers(qb,v); + escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); } return escaped_array; @@ -176,7 +179,7 @@ var QueryBuilder = function() { var alias; if (has_alias >= 0) { alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = escapeIdentifiers(alias); + alias = escape_identifiers(alias); if (alias != '') alias = ' AS ' + alias; item = item.substr(0,has_alias + 1); @@ -191,13 +194,13 @@ var QueryBuilder = function() { // Basically we remove everything to the right of the first space if (item.match(/\sAS\s/ig)) { var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - var alias = (protect_identifiers ? item.substr(alias_index,4) + escapeIdentifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); item = item.substr(0,alias_index); } else if (item.indexOf(' ') !== -1) { var alias_index = item.indexOf(' '); - var alias = (protect_identifiers && ! hasOperator(item.substr(alias_index + 1)) ? ' ' + escapeIdentifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); item = item.substr(0,alias_index); } else { @@ -214,12 +217,12 @@ var QueryBuilder = function() { // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item - if (qb.aliasedTables.indexOf(first_seg) !== -1) { + if (qb.aliased_tables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { for (var key in parts) { var val = parts[key]; if (val !== '*') { - parts[key] = escapeIdentifiers(val); + parts[key] = escape_identifiers(val); } } @@ -229,19 +232,19 @@ var QueryBuilder = function() { } if (protect_identifiers === true) { - item = escapeIdentifiers(item); + item = escape_identifiers(item); } return item + alias; } if (protect_identifiers === true) { - item = escapeIdentifiers(item); + item = escape_identifiers(item); } return item + alias; }; - var hasOperator = function (str) { + var has_operator = function (str) { if(typeof str === 'string' && str.length > 0) { var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); if(match === null) { @@ -274,162 +277,162 @@ var QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// - var buildWhereClause = function(qb) { + var build_where_clause = function(qb) { var sql = ''; - if(qb.whereArray.length > 0) { + if(qb.where_array.length > 0) { sql += " WHERE "; } - sql += qb.whereArray.join(" "); + sql += qb.where_array.join(" "); return sql; }; - var buildFromClause = function(qb) { + var build_from_clause = function(qb) { var sql = ''; - if(qb.fromArray.length > 0) { + if(qb.from_array.length > 0) { sql += " FROM "; } else { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); } - sql += '(' + qb.fromArray.join(', ') + ')'; + sql += '(' + qb.from_array.join(', ') + ')'; return sql; }; - var buildJoinString = function(qb) { + var build_join_string = function(qb) { var sql = ''; - sql += qb.joinArray.join(' '); + sql += qb.join_array.join(' '); if(sql.length > 0) sql = ' ' + sql; return sql; }; - var buildGroupByClause = function(qb) { - if (qb.groupByArray.length <= 0) return ''; + var build_group_by_clause = function(qb) { + if (qb.group_by_array.length <= 0) return ''; var sql = ' GROUP BY '; - sql += qb.groupByArray.join(', '); + sql += qb.group_by_array.join(', '); return sql; }; - var buildHavingClause = function(qb) { - if (qb.havingArray.length <= 0) return ''; + var build_having_clause = function(qb) { + if (qb.having_array.length <= 0) return ''; var sql = ' HAVING '; - sql += qb.havingArray.join(' '); + sql += qb.having_array.join(' '); return sql; }; - var buildOrderByClause = function(qb) { - if (qb.orderByArray.length <= 0) return ''; + var build_order_by_clause = function(qb) { + if (qb.order_by_array.length <= 0) return ''; var sql = ' ORDER BY '; - sql += qb.orderByArray.join(', '); + sql += qb.order_by_array.join(', '); - if (qb.orderByDir !== false) { - sql += (qb.orderByDir == 'desc' ? ' DESC' : ' ASC'); + if (qb.order_by_dir !== false) { + sql += (qb.order_by_dir == 'desc' ? ' DESC' : ' ASC'); } return sql; }; - var buildLimitClause = function(sql, limit, offset) { + var build_limit_clause = function(sql, limit, offset) { if (!limit) return sql; - var sql = ' '; + sql += ' '; - if (offset == 0) { + if (typeof offset !== 'number' || offset === 0) { offset = ''; } else { offset += ', '; } - return sql.replace(/\s+$/, '') + 'LIMIT ' + offset + limit; + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - var compileSelect = function(qb) { - var sql = 'SELECT ' + qb.distinctClause; - if (qb.selectArray.length === 0) { + var compile_select = function(qb) { + var sql = 'SELECT ' + qb.distinct_clause; + if (qb.select_array.length === 0) { sql += '*'; } else { - sql += qb.selectArray.join(', '); + sql += qb.select_array.join(', '); } - sql += buildFromClause(qb) - + buildJoinString(qb) - + buildWhereClause(qb) - + buildGroupByClause(qb) - + buildHavingClause(qb) - + buildOrderByClause(qb); + sql += build_from_clause(qb) + + build_join_string(qb) + + build_where_clause(qb) + + build_group_by_clause(qb) + + build_having_clause(qb) + + build_order_by_clause(qb); - sql = buildLimitClause(sql,qb.limitTo,qb.offsetVal); + sql = build_limit_clause(sql,qb.limit_to,qb.offset_val); return sql; }; - var compileDelete = function(qb) { - if (qb.fromArray.length == 0) { + var compile_delete = function(qb) { + if (qb.from_array.length == 0) { throw new Error('You have not specified any tables to delete from!'); return this; } - var sql = 'DELETE' + buildFromClause(qb) + buildWhereClause(qb); - return buildLimitClause(sql,qb.limitTo,qb.offsetVal); + var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); + return build_limit_clause(sql,qb.limit_to,qb.offset_val); }; - var compileUpdate = function(qb) { + var compile_update = function(qb) { var valstr = []; - for (var i in qb.setArray) { - valstr.push(key + ' = ' + qb.setArray[i]); + for (var i in qb.set_array) { + valstr.push(key + ' = ' + qb.set_array[i]); } - if (qb.fromArray.length === 1) { - var table = qb.fromArray.toString(); + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); } else { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; } - var limit = (!qb.limitTo ? '' : ' LIMIT ' + qb.limitTo); + var limit = (!qb.limit_to ? '' : ' LIMIT ' + qb.limit_to); sql = 'UPDATE ' + table + " SET " + valstr.join(', '); - sql += buildWhereClause(qb); - sql += buildOrderByClause(qb); - sql += buildLimitClause(qb); + sql += build_where_clause(qb); + sql += build_order_by_clause(qb); + sql += build_limit_clause(qb); return sql; }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// return { - whereArray: [], - whereInArray: [], - fromArray: [], - joinArray: [], - selectArray: [], - setArray: [], - orderByArray: [], - orderByDir: false, - groupByArray: [], - havingArray: [], - limitTo: false, - offsetVal: false, - joinClause: [], - lastQueryString: '', - distinctClause: '', - aliasedTables: [], - - resetQuery: function(newLastQuery) { - this.whereArray = []; - this.whereInArray = []; - this.fromArray = []; - this.joinArray = []; - this.selectArray = []; - this.setArray = []; - this.orderByArray = []; - this.orderByDir = false; - this.groupByArray = []; - this.havingArray = []; - this.limitTo = false; - this.offsetVal = false; - this.joinClause = []; - this.distinctClause = ''; - this.lastQueryString = (typeof newLastQuery === 'string' ? newLastQuery : ''); - this.aliasedTables = []; + where_array: [], + where_in_array: [], + from_array: [], + join_array: [], + select_array: [], + set_array: [], + order_by_array: [], + order_by_dir: false, + group_by_array: [], + having_array: [], + limit_to: false, + offset_val: false, + join_clause: [], + last_query_string: '', + distinct_clause: '', + aliased_tables: [], + + reset_query: function(new_last_query) { + this.where_array = []; + this.where_in_array = []; + this.from_array = []; + this.join_array = []; + this.select_array = []; + this.set_array = []; + this.order_by_array = []; + this.order_by_dir = false; + this.group_by_array = []; + this.having_array = []; + this.limit_to = false; + this.offset_val = false; + this.join_clause = []; + this.distinct_clause = ''; + this.last_query_string = (typeof new_last_query === 'string' ? new_last_query : ''); + this.aliased_tables = []; }, where: function(key, value, escape) { @@ -486,15 +489,15 @@ var QueryBuilder = function() { return this._where_in(k,v,false,type,escape); } - var prefix = (this.whereArray.length == 0 ? '' : type); + var prefix = (this.where_array.length == 0 ? '' : type); - if (v === null && !hasOperator(k)) { + if (v === null && !has_operator(k)) { k += ' IS NULL'; } if (v !== null) { if (escape === true) { - k = protectIdentifiers(this,k,escape); + k = protect_identifiers(this,k,escape); v = ' ' + qb_escape(this,v); } @@ -502,19 +505,19 @@ var QueryBuilder = function() { v = ' ' + qb_escape(this,v); } - if (!hasOperator(k)) { + if (!has_operator(k)) { k += ' ='; } } else { - k = protectIdentifiers(this,k,escape); + k = protect_identifiers(this,k,escape); } if (v) { - this.whereArray.push(prefix+k+v); + this.where_array.push(prefix+k+v); } else { - this.whereArray.push(prefix+k); + this.where_array.push(prefix+k); } } @@ -559,15 +562,15 @@ var QueryBuilder = function() { } for (var i in values) { - this.whereInArray.push(qb_escape(this,values[i])); + this.where_in_array.push(qb_escape(this,values[i])); } - var prefix = (this.whereArray.length == 0 ? '' : type); - var where_in = prefix + protectIdentifiers(this,key,escape) + not + " IN (" + this.whereInArray.join(', ') + ")"; - this.whereArray.push(where_in); + var prefix = (this.where_array.length == 0 ? '' : type); + var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); // reset the array for multiple calls - this.whereInArray = []; + this.where_in_array = []; return this; }, @@ -616,7 +619,7 @@ var QueryBuilder = function() { for(k in field) { var like_statement; var v = field[k]; - var k = protectIdentifiers(this,k.trim()); + var k = protect_identifiers(this,k.trim()); // Make sure value is only string, number, or boolean if ((typeof v).match(/^(string|number|boolean)$/) === null) { @@ -649,15 +652,15 @@ var QueryBuilder = function() { return this; }, - count: function(table, responseCallback) { + count: function(table) { if (typeof table === 'string') { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } - var sql = 'SELECT COUNT(*) AS ' + protectIdentifiers(this,'count') - + buildFromClause(this) - + buildJoinString(this) - + buildWhereClause(this) + var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'count') + + build_from_clause(this) + + build_join_string(this) + + build_where_clause(this) return sql; }, @@ -674,19 +677,19 @@ var QueryBuilder = function() { for (var j in objects) { var v = objects[j].trim(); - trackAliases(this,v); + track_aliases(this,v); - this.fromArray.push(protectIdentifiers(this,v,true)); + this.from_array.push(protect_identifiers(this,v,true)); } } else { val = val.trim(); // Extract any aliases that might exist. We use this information - // in the protectIdentifiers function to know whether to add a table prefix - trackAliases(this,val); + // in the protect_identifiers function to know whether to add a table prefix + track_aliases(this,val); - this.fromArray.push(protectIdentifiers(this,val,true)); + this.from_array.push(protect_identifiers(this,val,true)); } } @@ -716,7 +719,7 @@ var QueryBuilder = function() { direction += ' '; } - trackAliases(this,table); + track_aliases(this,table); // Split multiple conditions var regex = /\sAND\s|\sOR\s/ig; @@ -734,7 +737,7 @@ var QueryBuilder = function() { for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { temp = relation.substr(s, matches[j].index - s); temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? protectIdentifiers(this,temp_match[1],escape) + temp_match[2] + protectIdentifiers(this,temp_match[3],escape) : temp); + new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); new_relation += matches[j][0]; } @@ -744,11 +747,11 @@ var QueryBuilder = function() { // Split apart the condition and protect the identifiers else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + protectIdentifiers(this,match[1],true) + match[2] + protectIdentifiers(this,match[3],true); + relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); } - else if (!hasOperator(relation)) { + else if (!has_operator(relation)) { console.log("Has operator: " + relation); - relation = ' USING (' + (escape ? escapeIdentifiers(relation) : relation) + ')'; + relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; } else { relation = ' '; @@ -756,12 +759,12 @@ var QueryBuilder = function() { // Do we want to escape the table name? if (escape === true) { - table = protectIdentifiers(this,table,true); + table = protect_identifiers(this,table,true); } var join = direction + 'JOIN ' + table + relation; - this.joinArray.push(join); + this.join_array.push(join); return this; }, @@ -813,7 +816,7 @@ var QueryBuilder = function() { var val = select[i].trim(); if(val !== '') { - this.selectArray.push(protectIdentifiers(this,val,escape)); + this.select_array.push(protect_identifiers(this,val,escape)); } } return this; @@ -853,12 +856,12 @@ var QueryBuilder = function() { } if (alias == '') { - alias = createAliasFromTable(select.trim()); + alias = create_aliases_from_table(select.trim()); } - var sql = type + '(' + protectIdentifiers(this,select.trim()) + ') AS ' + alias; + var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; - this.selectArray.push(sql); + this.select_array.push(sql); return this; }, @@ -867,10 +870,10 @@ var QueryBuilder = function() { do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); if (do_distinct) { - this.distinctClause = 'DISTINCT '; + this.distinct_clause = 'DISTINCT '; } else { - this.distinctClause = ''; + this.distinct_clause = ''; } return this; }, @@ -900,7 +903,7 @@ var QueryBuilder = function() { var val = by[key].trim(); if (val !== '') { - this.groupByArray.push(protectIdentifiers(this,val)); + this.group_by_array.push(protect_identifiers(this,val)); } } return this; @@ -939,7 +942,7 @@ var QueryBuilder = function() { key = key_array; } else { - key = extractHavingParts(key,key_array); + key = extract_having_parts(key,key_array); } } else if (key_is_array === true) { @@ -949,7 +952,7 @@ var QueryBuilder = function() { throw new Error("You've provided an unparseable format to the having() method.."); } else { - key_array = extractHavingParts(key[i],key_array); + key_array = extract_having_parts(key[i],key_array); } } key = key_array; @@ -963,16 +966,16 @@ var QueryBuilder = function() { for (var k in key) { var v = key[k]; - var prefix = (this.havingArray.length == 0 ? '' : type); + var prefix = (this.having_array.length == 0 ? '' : type); if (escape === true) { - k = protectIdentifiers(this,k); + k = protect_identifiers(this,k); } if (v === null) { k += ' IS'; } - else if (!hasOperator(k)) { + else if (!has_operator(k)) { k += ' ='; } @@ -980,7 +983,7 @@ var QueryBuilder = function() { v = ' ' + qb_escape(this,v); } - this.havingArray.push(prefix + k + v); + this.having_array.push(prefix + k + v); } return this; @@ -993,7 +996,7 @@ var QueryBuilder = function() { // Don't need to do anything below if the direction provided is random if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { - this.orderByArray.push(rand_word); + this.order_by_array.push(rand_word); return this; } @@ -1006,7 +1009,7 @@ var QueryBuilder = function() { } orderby = orderby.split(','); } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { - this.orderByArray.push(rand_word); + this.order_by_array.push(rand_word); return this; } else { @@ -1023,35 +1026,36 @@ var QueryBuilder = function() { if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { if (m[2].trim() === 'RAND()') { - this.orderByArray.push(rand_word); + this.order_by_array.push(rand_word); return this; } - orderby[i] = {field: protectIdentifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; } else { if (direction.match(/^(ASC|DESC)$/i) || direction === '') { - orderby[i] = {field: protectIdentifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; } else { throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); } } - this.orderByArray.push(orderby[i].field + ' ' + orderby[i].direction); + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); } return this; }, limit: function(limit, offset) { - this.limitTo = prepareForLimitAndOffset(limit,'limit'); + this.limit_to = prepare_for_limit_and_offset(limit,'limit'); if (offset !== undefined) { - this.offsetVal = prepareForLimitAndOffset(offset,'offset'); + return this.offset(offset); } + return this; }, offset: function(offset) { - this.offsetVal = prepareForLimitAndOffset(offset,'offset'); + this.offset_val = prepare_for_limit_and_offset(offset,'offset'); return this; }, @@ -1092,109 +1096,140 @@ var QueryBuilder = function() { } if (escape === false) { - this.setArray[protectIdentifiers(this,i,escape)] = v; + this.set_array[protect_identifiers(this,i,escape)] = v; } else { - this.setArray[protectIdentifiers(this,i,escape)] = qb_escape(this,v); + this.set_array[protect_identifiers(this,i,escape)] = qb_escape(this,v); } } return this; }, - insert: function(table, set, callback, ignore, suffix) { + insert: function(table, set, ignore, suffix) { table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : suffix); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - return this.insert_batch(table, set, callback); + return this.insert_batch(table, set, ignore, suffix); } if (set !== null) { - this.set(set); + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } + } + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && (!table.match(/[a-zA-Z]/) || !table.match(/^[a-zA-Z0-9\$_]+/))) { + throw new Error("insert(): Invalid table name provided!"); } if (table == '') { - if (this.fromArray.length == 0) { + if (this.from_array.length == 0) { throw new Error('No tables set to insert into!'); } - table = this.fromArray[0]; + table = this.from_array[0]; } else { - this.fromArray = []; + this.from_array = []; this.from(table); } - var keys = Object.keys(this.setArray); - var values = array_values(this.setArray); + var keys = Object.keys(this.set_array); + var values = array_values(this.set_array); var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + ' INTO ' + buildFromClause(this) + " (" + keys.join(', ') + ") VALUES (" + values.join(', ') + ")"; + return verb + 'INTO ' + this.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; }, - insert_ignore: function(table, set, callback, suffix) { - return this.insert(table, set, callback, true, suffix); + insert_ignore: function(table, set, suffix) { + return this.insert(table, set, true, suffix); }, - insert_batch: function(table,set,callback) { + insert_batch: function(table,set,ignore,suffix) { + var self = this; table = table || ''; set = set || null; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if(suffix == ' ') suffix = ''; if (table == '') { - if (this.fromArray.length === 0) { - throw new Error("You have not set any tables to insert into."); + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); } - table = this.fromArray[0]; + table = this.from_array[0]; } else { - this.fromArray = []; + this.from_array = []; this.from(table); } if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { - throw new Error('Array of objects must be provided for batch insert!'); + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (var key in set) { + var value = set[key]; + var is_object = Object.prototype.toString.call(value) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(value).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } } + if (set.length == 0) return false; var map = []; var columns = []; - + + // Obtain all the column names for (var key in set[0]) { if (set[0].hasOwnProperty(key)) { if (columns.indexOf(key) == -1) { - columns.push(protectIdentifiers(this,key)); + columns.push(protect_identifiers(this,key)); } } } - + for (var i = 0; i < set.length; i++) { - (function(i) { + (function(i,qb) { var row = []; for (var key in set[i]) { if (set[i].hasOwnProperty(key)) { - row.push(this.escape(set[i][key])); + row.push(qb_escape(qb,set[i][key])); } } if (row.length != columns.length) { - throw new Error('Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); } - map.push('(' + row.join(',') + ')'); - })(i); + map.push('(' + row.join(', ') + ')'); + })(i,self); } - return 'INSERT INTO ' + buildFromClause(this) + ' (' + keys.join(', ') + ') VALUES' + values.join(','); + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, get: function(table, callback) { if (typeof table !== 'function') { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } else { - if (this.fromArray.length == 0) { + if (this.from_array.length == 0) { throw new Error('You have not specified any tables to select from!'); } } - return compileSelect(this); + return compile_select(this); }, get_where: function(table, where) { @@ -1204,7 +1239,7 @@ var QueryBuilder = function() { throw new Error('You must specify a table as the first parameter when using the get_where method!'); return this; } else { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } @@ -1212,7 +1247,7 @@ var QueryBuilder = function() { this.where(where); } - return compileSelect(this); + return compile_select(this); }, update: function(table, set, where, callback) { @@ -1233,21 +1268,21 @@ var QueryBuilder = function() { this.set(set); } - if (this.setArray.length == 0) { + if (this.set_array.length == 0) { throw new Error("You must set a some field value pairs to update using the set method or in object for in the second parameter of the update method!"); } if (table == '') { - if (this.fromArray.length === 0) { + if (this.from_array.length === 0) { throw new Error("You have not set any tables to update!"); } - table = this.fromArray[0]; + table = this.from_array[0]; } else { - this.fromArray = []; + this.from_array = []; this.from(table); - table = this.fromArray[0]; + table = this.from_array[0]; } if (where != null) { @@ -1272,47 +1307,47 @@ var QueryBuilder = function() { } } - return compileDelete(this); + return compile_delete(this); }, get_compiled_select: function(table) { if (typeof table !== 'undefined') { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } else { - if (fromArray.length == 0) { + if (from_array.length == 0) { throw new Error('You have not specified any tables to build a select statement with!'); return this; } } - return compileSelect(this); + return compile_select(this); }, get_compiled_delete: function(table) { if (typeof table !== 'function') { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } - return compileDelete(this); + return compile_delete(this); }, get_compiled_update: function(table) { if (typeof table !== 'function') { - trackAliases(this,table); + track_aliases(this,table); this.from(table); } - return compileUpdate(this); + return compile_update(this); }, get_compiled_insert: function(table) { - throw new Error("This function has not yet been omplemented!"); + throw new Error("This function has not yet been implemented!"); }, _last_query: function() { - return this.lastQueryString; + return this.last_query_string; } } }; diff --git a/test/test-distinct.js b/test/test-distinct.js index 8576ddb..d50ba95 100755 --- a/test/test-distinct.js +++ b/test/test-distinct.js @@ -10,9 +10,9 @@ describe('distinct()', function() { it('should be a function', function() { qb.distinct.should.be.a('function'); }); - it('should override the default distinctClause with the "DISTINCT " keyword', function() { - qb.resetQuery(); + it('should override the default distinct_clause with the "DISTINCT " keyword', function() { + qb.reset_query(); qb.distinct(); - qb.distinctClause.should.eql('DISTINCT '); + qb.distinct_clause.should.eql('DISTINCT '); }); }); \ No newline at end of file diff --git a/test/test-limit.js b/test/test-limit.js index 173880e..7da49a0 100755 --- a/test/test-limit.js +++ b/test/test-limit.js @@ -11,13 +11,13 @@ describe('limit()', function() { qb.limit.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('limitTo'); + qb.should.have.property('limit_to'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.limitTo.should.be.false; + qb.limit_to.should.be.false; }); it('should require an integer (or integer in string form) in first parameter', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.limit(5); }, 'integer provided').to.not.throw(Error); expect(function() { qb.limit('5'); }, '5 in string form provided').to.not.throw(Error); expect(function() { qb.limit(5.7); }, 'float provided').to.throw(Error); @@ -34,7 +34,7 @@ describe('limit()', function() { expect(function() { qb.limit([5]); }, 'array with integer in it provided').to.throw(Error); }); it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.limit(10,5); }, 'integer provided').to.not.throw(Error); expect(function() { qb.limit(10,'5'); }, '5 in string form provided').to.not.throw(Error); expect(function() { qb.limit(10,5.7); }, 'float provided').to.throw(Error); @@ -49,39 +49,39 @@ describe('limit()', function() { expect(function() { qb.limit(10,[]); }, 'empty array provided').to.throw(Error); expect(function() { qb.limit(10,[5]); }, 'array with integer in it provided').to.throw(Error); }); - it('should override the default limitTo value when a limit is provided', function() { - qb.resetQuery(); + it('should override the default limit_to value when a limit is provided', function() { + qb.reset_query(); qb.limit(10); - qb.limitTo.should.eql(10); + qb.limit_to.should.eql(10); }); - it('should override the default limitTo and offsetVal values when a limit and an offset are provided', function() { - qb.resetQuery(); + it('should override the default limit_to and offset_val values when a limit and an offset are provided', function() { + qb.reset_query(); qb.limit(10,20); - qb.limitTo.should.eql(10); - qb.offsetVal.should.eql(20); + qb.limit_to.should.eql(10); + qb.offset_val.should.eql(20); }); it('should trim string values that are provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.limit('10 '); - qb.limitTo.should.eql(10); + qb.limit_to.should.eql(10); }); it('should trim string values that are provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.limit(' 10 ',' 12'); - qb.limitTo.should.eql(10); - qb.offsetVal.should.eql(12); + qb.limit_to.should.eql(10); + qb.offset_val.should.eql(12); }); it('should override values set by any previous calls to itself', function() { - qb.resetQuery(); + qb.reset_query(); qb.limit(10); - qb.limitTo.should.eql(10); + qb.limit_to.should.eql(10); qb.limit(20); - qb.limitTo.should.eql(20); + qb.limit_to.should.eql(20); }); it('should be chainable whereby the last call to the method will contain the value(s) used', function() { - qb.resetQuery(); + qb.reset_query(); qb.limit(10,5).limit(20).limit(100,30); - qb.limitTo.should.eql(100); - qb.offsetVal.should.eql(30); + qb.limit_to.should.eql(100); + qb.offset_val.should.eql(30); }); }); \ No newline at end of file diff --git a/test/test-offset.js b/test/test-offset.js index a4bfdfe..dcc658c 100755 --- a/test/test-offset.js +++ b/test/test-offset.js @@ -11,13 +11,13 @@ describe('offset()', function() { qb.offset.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('offsetVal'); + qb.should.have.property('offset_val'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.offsetVal.should.be.false; + qb.offset_val.should.be.false; }); it('should require an integer (or integer in string form) in first parameter', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.offset(5); }, 'integer provided').to.not.throw(Error); expect(function() { qb.offset('5'); }, '5 in string form provided').to.not.throw(Error); expect(function() { qb.offset(5.7); }, 'float provided').to.throw(Error); @@ -33,28 +33,28 @@ describe('offset()', function() { expect(function() { qb.offset([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.offset([5]); }, 'array with integer in it provided').to.throw(Error); }); - it('should override the default offsetVal value when a offset is provided', function() { - qb.resetQuery(); + it('should override the default offset_val value when a offset is provided', function() { + qb.reset_query(); qb.offset(10); - qb.offsetVal.should.eql(10); + qb.offset_val.should.eql(10); }); it('should trim string values that are provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.offset('10 '); qb.offset(' 10'); qb.offset(' 10 '); - qb.offsetVal.should.eql(10); + qb.offset_val.should.eql(10); }); it('should override values set by any previous calls to itself', function() { - qb.resetQuery(); + qb.reset_query(); qb.offset(10); - qb.offsetVal.should.eql(10); + qb.offset_val.should.eql(10); qb.offset(20); - qb.offsetVal.should.eql(20); + qb.offset_val.should.eql(20); }); it('should be chainable whereby the last call to the method will contain the value used', function() { - qb.resetQuery(); + qb.reset_query(); qb.offset(10).offset(20).offset(100); - qb.offsetVal.should.eql(100); + qb.offset_val.should.eql(100); }); }); \ No newline at end of file diff --git a/test/tests-delete.js b/test/tests-delete.js index b704a31..2c2eb18 100755 --- a/test/tests-delete.js +++ b/test/tests-delete.js @@ -10,67 +10,77 @@ describe('delete()', function() { it('should be a function', function() { qb.delete.should.be.a('function'); }); - it('should add a table to fromArray when a table is supplied', function() { - qb.resetQuery(); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); qb.delete('galaxies'); - qb.fromArray.should.eql(['`galaxies`']); + qb.from_array.should.eql(['`galaxies`']); }); - it('should add a set of tables to fromArray when an array of tables is supplied', function() { - qb.resetQuery(); + it('should add a set of tables to from_array when an array of tables is supplied', function() { + qb.reset_query(); qb.delete(['galaxies','star_systems','planets']); - qb.fromArray.should.eql(['`galaxies`','`star_systems`','`planets`']); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); }); - it('should add where conditions to whereArray when conditions are supplied', function() { - qb.resetQuery(); + it('should add where conditions to where_array when conditions are supplied', function() { + qb.reset_query(); qb.delete('planets', {continents: 7, star_system: 'Solar'}); - qb.whereArray.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); + qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); }); it('should accept an array of tables and an object of where conditions and put each in their respective arrays', function() { - qb.resetQuery(); + qb.reset_query(); qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); - qb.fromArray.should.eql(['`galaxies`','`star_systems`','`planets`']); - qb.whereArray.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); }); it('should return a string', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.delete('galaxies'); sql.should.eql('DELETE FROM (`galaxies`)'); }); it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.delete('galaxies', {class: 'M'}); sql.should.eql("DELETE FROM (`galaxies`) WHERE `class` = 'M'"); }); it('should build a properly-escaped delete statement that deletes all records in a set of tables if an array of tables is given', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.delete(['galaxies','star_systems','planets']); sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); }); it('should build a properly-escaped delete statement that deletes all records in a set of tables that match the passed conditions', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.delete(['galaxies','star_systems','planets'], {class: 'M'}); sql.should.eql("DELETE FROM (`galaxies`, `star_systems`, `planets`) WHERE `class` = 'M'"); }); it('should use tables added previously via the from() method', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('galaxies'); var sql = qb.delete(); sql.should.eql('DELETE FROM (`galaxies`)'); - qb.resetQuery(); + qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); }); it('should accept where conditions added previously via the where() method', function() { - qb.resetQuery(); + qb.reset_query(); var sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); sql.should.eql("DELETE FROM (`galaxies`) WHERE `created` >= 4600000000 AND `class` = 'M'"); }); + it('should accept a limit on the number of rows deleted', function() { + qb.reset_query(); + var sql = qb.limit(20).delete('galaxies'); + sql.should.eql("DELETE FROM (`galaxies`) LIMIT 20"); + }); + it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', function() { + qb.reset_query(); + var sql = qb.limit(20,10).delete('galaxies'); + sql.should.eql("DELETE FROM (`galaxies`) LIMIT 10, 20"); + }); }); \ No newline at end of file diff --git a/test/tests-from.js b/test/tests-from.js index 3fa57d9..f5a2755 100755 --- a/test/tests-from.js +++ b/test/tests-from.js @@ -10,101 +10,101 @@ describe('from()', function() { qb.from.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('fromArray'); + qb.should.have.property('from_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.fromArray.should.be.empty; + qb.from_array.should.be.empty; }); it('should add an item to an array and escape it properly', function() { qb.from('universe'); - qb.fromArray.should.eql(['`universe`']); + qb.from_array.should.eql(['`universe`']); }); it('should accept a comma-delimited string of items and trim and escape each properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe,galaxy , star_system, planet'); - qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); it('should have an empty array after resetting', function() { - qb.resetQuery(); - qb.fromArray.should.be.empty; + qb.reset_query(); + qb.from_array.should.be.empty; }); it('should be allowed to be called multiple times to add multiple items to the from array', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe').from('galaxy').from('star_system').from('planet'); - qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); it('should accept an array of items and add them individually to the from array', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['universe','galaxy','star_system','planet']); - qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); it('should not double-escape an item', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('`do`'); - qb.fromArray.should.eql(['`do`']); + qb.from_array.should.eql(['`do`']); }); it('should not double-escape items when provided with an array of pre-escaped items', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['`universe`','`galaxy`','`star_system`']); - qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); }); it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['`universe`','galaxy','`star_system`']); - qb.fromArray.should.eql(['`universe`','`galaxy`','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); }); it('should allow for aliases and it should escape them properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe u'); - qb.fromArray.should.eql(['`universe` `u`']); + qb.from_array.should.eql(['`universe` `u`']); }); it('should allow for the word AS to be used to alias an item', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe as u'); - qb.fromArray.should.eql(['`universe` as `u`']); + qb.from_array.should.eql(['`universe` as `u`']); }); it('should allow for an array of item + aliases and it should escape them all properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['universe u', 'galaxy g']); - qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['`universe` u', 'galaxy `g`']); - qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); it('should add aliases to alias-tracking array', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.aliasedTables.should.eql(['u','g']); + qb.aliased_tables.should.eql(['u','g']); }); it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.from(['universe u, galaxy g']); - qb.fromArray.should.eql(['`universe` `u`','`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); it('should allow for namespacing in field name (host.db.table)', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('star_system.planet'); - qb.fromArray.should.eql(['`star_system`.`planet`']); + qb.from_array.should.eql(['`star_system`.`planet`']); - qb.resetQuery(); + qb.reset_query(); qb.from('galaxy.star_system.planet'); - qb.fromArray.should.eql(['`galaxy`.`star_system`.`planet`']); + qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); }); it('should allow for namespacing in field name (host.db.table.column) + alias', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe.galaxy.star_system planet'); - qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); }); it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', function() { - qb.resetQuery(); + qb.reset_query(); qb.from('universe.galaxy.star_system as planet'); - qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); }) }); \ No newline at end of file diff --git a/test/tests-groupby.js b/test/tests-groupby.js index 730e26f..fab17f2 100755 --- a/test/tests-groupby.js +++ b/test/tests-groupby.js @@ -11,28 +11,28 @@ describe('group_by()', function() { qb.group_by.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('groupByArray'); + qb.should.have.property('group_by_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.groupByArray.should.be.empty; + qb.group_by_array.should.be.empty; }); it('should accept a single field in string form', function() { - qb.resetQuery(); + qb.reset_query(); qb.group_by('planet_type'); - qb.groupByArray.should.eql(['`planet_type`']); + qb.group_by_array.should.eql(['`planet_type`']); }); it('should accept a multiple fields delimited by commas', function() { - qb.resetQuery(); + qb.reset_query(); qb.group_by('planet_type, planet_position'); - qb.groupByArray.should.eql(['`planet_type`','`planet_position`']); + qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); }); it('should accept an array of fields', function() { - qb.resetQuery(); + qb.reset_query(); qb.group_by(['planet_type', 'planet_position']); - qb.groupByArray.should.eql(['`planet_type`','`planet_position`']); + qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); }); it('should not accept anything but a string or an array of strings', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); diff --git a/test/tests-having.js b/test/tests-having.js index 8dd1c4c..9b7ac8e 100755 --- a/test/tests-having.js +++ b/test/tests-having.js @@ -10,53 +10,56 @@ describe('having()', function() { it('should be a function', function() { qb.having.should.be.a('function'); }); + it('should have an array to put fields into', function() { + qb.should.have.property('having_array'); + }); it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class > "M"'); - qb.havingArray.should.eql(["`planet_class` > 'M'"]); + qb.having_array.should.eql(["`planet_class` > 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class < "M"'); - qb.havingArray.should.eql(["`planet_class` < 'M'"]); + qb.having_array.should.eql(["`planet_class` < 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class <> "M"'); - qb.havingArray.should.eql(["`planet_class` <> 'M'"]); + qb.having_array.should.eql(["`planet_class` <> 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class >= "M"'); - qb.havingArray.should.eql(["`planet_class` >= 'M'"]); + qb.having_array.should.eql(["`planet_class` >= 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class <= "M"'); - qb.havingArray.should.eql(["`planet_class` <= 'M'"]); + qb.having_array.should.eql(["`planet_class` <= 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class = "M"'); - qb.havingArray.should.eql(["`planet_class` = 'M'"]); + qb.having_array.should.eql(["`planet_class` = 'M'"]); - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class != "M"'); - qb.havingArray.should.eql(["`planet_class` != 'M'"]); + qb.having_array.should.eql(["`planet_class` != 'M'"]); }); it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.having('planet_class = "M", sentient_life = 1'); }, 'two conditions provided').to.throw(Error); }); it('should accept an array of conditions and prepend AND to each condition following the first one', function() { - qb.resetQuery(); + qb.reset_query(); qb.having(["planet_class = 'M'", 'sentient_life = 1']); - qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); }); it('should accept an object of conditions and prepend AND to each condition following the first one', function() { - qb.resetQuery(); + qb.reset_query(); var object = {planet_class: 'M', sentient_life: 1}; object['planet_order <='] = 3; qb.having(object); - qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); }); it('should not accept anything but a non-empty array, object, or string', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); @@ -73,19 +76,19 @@ describe('having()', function() { expect(function() { qb.group_by(['planet_type = "M"']); }, 'array of string(s) provided').to.not.throw(Error); }); it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', function() { - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class','M'); - qb.havingArray.should.eql(["`planet_class` = 'M'"]); + qb.having_array.should.eql(["`planet_class` = 'M'"]); }); it('should not escape conditions if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); - qb.havingArray.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); + qb.having_array.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); - qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); }); }); @@ -97,13 +100,13 @@ describe('or_having()', function() { qb.or_having.should.be.a('function'); }); it('should accept an array of conditions and prepend OR to each condition following the first one', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); - qb.havingArray.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); }); it('should be chainable with normal having', function() { - qb.resetQuery(); + qb.reset_query(); qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); - qb.havingArray.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); }); }); \ No newline at end of file diff --git a/test/tests-insert.js b/test/tests-insert.js new file mode 100755 index 0000000..b99b80c --- /dev/null +++ b/test/tests-insert.js @@ -0,0 +1,123 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var QueryBuilder = require('../lib/query_builder.js'); +var qb = new QueryBuilder(); + +var test_data = {id:3, name:'Milky Way', type: 'spiral'}; +var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +// table, data, callback, ignore, suffix + +describe('insert()', function() { + it('should exist', function() { + should.exist(qb.insert); + }); + it('should be a function', function() { + qb.insert.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.insert('galaxies', test_data); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a string for the table (first) parameter', function() { + qb.reset_query(); + + // Doing these to prevent other errors + qb.from('galaxies'); + + expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); + expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); + expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); + expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); + expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); + + expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); + expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { + qb.reset_query(); + + expect(function() { qb.insert('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); + + expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); + expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); + expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); + expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', function() { + qb.reset_query(); + qb.from('galaxies'); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in in setArray', function() { + qb.reset_query(); + qb.set(test_data); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should utilize pre-existing tables and values from setArray and from_array, respectively', function() { + qb.reset_query(); + qb.from('galaxies').set(test_data); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should accept a non-empty object for the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data_set); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should fail if any invalid values are passed in the data object.', function() { + qb.reset_query(); + var func = function() { console.log("foo"); }; + var regex = /foobar/; + var arr = [1,2,3]; + var obj = {foo: 'bar'}; + + expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + + }); + it('should support insert ignore statements', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); +}); \ No newline at end of file diff --git a/test/tests-join.js b/test/tests-join.js index 27460ae..dfa1183 100755 --- a/test/tests-join.js +++ b/test/tests-join.js @@ -12,10 +12,10 @@ describe('join()', function() { qb.join.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('joinArray'); + qb.should.have.property('join_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.joinArray.should.be.empty; + qb.join_array.should.be.empty; }); it('should require a string to be passed as first parameter', function() { var invalid_match = /must provide a table/; @@ -31,14 +31,14 @@ describe('join()', function() { expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); }); it('should except single item and add it to join array as basic join and escape item', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe'); - qb.joinArray.should.eql(['JOIN `universe` ']); + qb.join_array.should.eql(['JOIN `universe` ']); }); it('should except single item with alias and add it to join array as basic join and escape each part', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe u'); - qb.joinArray.should.eql(['JOIN `universe` `u` ']); + qb.join_array.should.eql(['JOIN `universe` `u` ']); }); it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', function() { var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; @@ -78,33 +78,33 @@ describe('join()', function() { expect(function() { qb.join('universe','foo = bar','LEFT OUTER'); }, 'two word direction').to.not.throw(Error); }); it('should except a valid second parameter as a join condition and escape it properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe u','u.type_id = ut.id'); - qb.joinArray.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); + qb.join_array.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); }); it('should escape compound objects properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe.galaxy.star_system s','s.type_id = st.id'); - qb.joinArray.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + qb.join_array.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); }); it('should add aliases to alias-tracking array', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe.galaxy.star_system s'); - qb.aliasedTables.should.eql(['s']); + qb.aliased_tables.should.eql(['s']); }); it('should properly place join direction into join clause', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); - qb.joinArray.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + qb.join_array.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); }); it('should be chainable to allow for multiple join clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); - qb.joinArray.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); + qb.join_array.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); }); it('should escape complex join conditions', function() { - qb.resetQuery(); + qb.reset_query(); qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); - qb.joinArray.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); + qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); }); }); \ No newline at end of file diff --git a/test/tests-like.js b/test/tests-like.js index 0200a0f..076d8c8 100755 --- a/test/tests-like.js +++ b/test/tests-like.js @@ -64,42 +64,42 @@ describe('like()', function() { expect(function() { qb.like('galaxy_name','milk','after'); }, 'after as third param').to.not.throw(Error); }); it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky', 'after'); - qb.whereArray.should.eql(["`galaxy_name` LIKE 'milky%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky', 'right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE 'milky%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); }); it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky', 'before'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky', 'left'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); }); it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky', 'both'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); }); it('should put AND in between multiple LIKE clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky'); qb.like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); }); }); @@ -111,20 +111,20 @@ describe('or_like()', function() { qb.or_like.should.be.a('function'); }); it('should put OR in between multiple OR LIKE clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_like('galaxy_name', 'milky'); qb.or_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); }); it('should be chainable with regular like clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); }); }); @@ -136,25 +136,25 @@ describe('not_like()', function() { qb.not_like.should.be.a('function'); }); it('should put NOT before LIKE', function() { - qb.resetQuery(); + qb.reset_query(); qb.not_like('galaxy_name', 'milky'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); }); it('should put AND in between multiple NOT LIKE clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.not_like('galaxy_name', 'milky'); qb.not_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); it('should be chainable with regular like clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); }); @@ -166,24 +166,24 @@ describe('or_not_like()', function() { qb.or_not_like.should.be.a('function'); }); it('should put NOT before LIKE', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_not_like('galaxy_name', 'milky'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); }); it('should put OR in between multiple NOT LIKE clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_not_like('galaxy_name', 'milky'); qb.or_not_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.whereArray.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); }); it('should be chainable with regular like clauses', function() { - qb.resetQuery(); + qb.reset_query(); qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.whereArray.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); }); }); \ No newline at end of file diff --git a/test/tests-orderby.js b/test/tests-orderby.js index b3e6937..fa5a11b 100755 --- a/test/tests-orderby.js +++ b/test/tests-orderby.js @@ -11,10 +11,10 @@ describe('order_by()', function() { qb.order_by.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('orderByArray'); + qb.should.have.property('order_by_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.orderByArray.should.be.empty; + qb.order_by_array.should.be.empty; }); it('should require non-empty string or array as first param unless random is provided as second parameter', function() { expect(function() { qb.order_by(); }, 'nothing provided').to.throw(Error); @@ -34,33 +34,33 @@ describe('order_by()', function() { expect(function() { qb.order_by([],'rand'); }, 'empty array and random direction provided').to.not.throw(Error); }); it('should accept a field and direction separated by a space as first parameter and escape the field', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position asc'); - qb.orderByArray.should.eql(['`planet_position` ASC']); + qb.order_by_array.should.eql(['`planet_position` ASC']); }); it('should accept a field and direction as seperate parameters and escape the field', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position', 'asc'); - qb.orderByArray.should.eql(['`planet_position` ASC']); + qb.order_by_array.should.eql(['`planet_position` ASC']); }); it('should add additional order_by calls to teh order by array', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position', 'asc'); qb.order_by('planet_size', 'desc'); - qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); - qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); }); it('should assume ASC when no direction is provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position'); - qb.orderByArray.should.eql(['`planet_position` ASC']); + qb.order_by_array.should.eql(['`planet_position` ASC']); }); it('should only accept valid ordering directions (ASC, DESC, random)', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.order_by('planet_position'); }).to.not.throw(Error); expect(function() { qb.order_by('planet_position','ASC'); }).to.not.throw(Error); expect(function() { qb.order_by('planet_position','DESC'); }).to.not.throw(Error); @@ -78,54 +78,54 @@ describe('order_by()', function() { expect(function() { qb.order_by('planet_position','FAKE'); }).to.throw(Error); }); it('should accept a comma-separated list of fields to order by with a single direction at the end', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position, planet_size asc'); - qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); }); it('should accept a comma-separated list of field + direction pairs', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position desc, planet_size asc'); - qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); }); it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position', 'random'); - qb.orderByArray.should.eql(['RAND()']); + qb.order_by_array.should.eql(['RAND()']); - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_size', 'RAND'); - qb.orderByArray.should.eql(['RAND()']); + qb.order_by_array.should.eql(['RAND()']); - qb.resetQuery(); + qb.reset_query(); qb.order_by('planet_position, planet_size', 'rand'); - qb.orderByArray.should.eql(['RAND()']); + qb.order_by_array.should.eql(['RAND()']); - qb.resetQuery(); + qb.reset_query(); qb.order_by(null, 'RAND()'); - qb.orderByArray.should.eql(['RAND()']); + qb.order_by_array.should.eql(['RAND()']); - qb.resetQuery(); + qb.reset_query(); qb.order_by('', 'rand'); - qb.orderByArray.should.eql(['RAND()']); + qb.order_by_array.should.eql(['RAND()']); }); it('should accept an array of field + direction pairs', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by(['planet_position DESC', 'planet_size ASC']); - qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); }); it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by(['planet_position', 'planet_size'], 'desc'); - qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); - qb.resetQuery(); + qb.reset_query(); qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); - qb.orderByArray.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); }); it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.order_by(['planet_position', 'planet_size']); - qb.orderByArray.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); }); }); \ No newline at end of file diff --git a/test/tests-select.js b/test/tests-select.js index 8421e42..dab2ca5 100755 --- a/test/tests-select.js +++ b/test/tests-select.js @@ -11,10 +11,10 @@ describe('select()', function() { qb.select.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('selectArray'); + qb.should.have.property('select_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.selectArray.should.be.empty; + qb.select_array.should.be.empty; }); it('should require an array or string to be passed as first parameter', function() { var invalid_match = /requires a string or array/; @@ -31,114 +31,114 @@ describe('select()', function() { expect(function() { qb.select('blah'); }, 'valid string provided').to.not.throw(Error); }); it('should add field to array and escape it properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('notes'); - qb.selectArray.should.eql(['`notes`']); + qb.select_array.should.eql(['`notes`']); }); it('should trim fields properly before placing them into the select array', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(' notes '); - qb.selectArray.should.eql(['`notes`']); + qb.select_array.should.eql(['`notes`']); }); it('should have an empty array after resetting', function() { - qb.resetQuery(); - qb.selectArray.should.be.empty; + qb.reset_query(); + qb.select_array.should.be.empty; }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('foo',false); - qb.selectArray.should.eql(['foo']); + qb.select_array.should.eql(['foo']); }); it('should accept a comma-delimited string of field names and trim and escape each properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('do,re , mi, fa'); - qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); + qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); }); it('should be allowed to be called multiple times to add multiple fields to the select array', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('do').select('re').select('mi').select('fa'); - qb.selectArray.should.eql(['`do`','`re`','`mi`','`fa`']); + qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); }); it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('do').select('re',false).select('mi',false).select('fa'); - qb.selectArray.should.eql(['`do`','re','mi','`fa`']); + qb.select_array.should.eql(['`do`','re','mi','`fa`']); }); it('should accept an array of fields and add them individually to the select array', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['sol','la','ti','do']); - qb.selectArray.should.eql(['`sol`','`la`','`ti`','`do`']); + qb.select_array.should.eql(['`sol`','`la`','`ti`','`do`']); }); it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['sol','la','ti','do'],false); - qb.selectArray.should.eql(['sol','la','ti','do']); + qb.select_array.should.eql(['sol','la','ti','do']); }); it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['`sol`','la','ti','`do`'],false); - qb.selectArray.should.eql(['`sol`','la','ti','`do`']); + qb.select_array.should.eql(['`sol`','la','ti','`do`']); }); it('should not double-escape a field', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('`do`'); - qb.selectArray.should.eql(['`do`']); + qb.select_array.should.eql(['`do`']); }); it('should not double-escape fields when provided with an array of pre-escaped fields', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['`do`','`re`','`mi`']); - qb.selectArray.should.eql(['`do`','`re`','`mi`']); + qb.select_array.should.eql(['`do`','`re`','`mi`']); }); it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['`do`','re','`mi`']); - qb.selectArray.should.eql(['`do`','`re`','`mi`']); + qb.select_array.should.eql(['`do`','`re`','`mi`']); }); it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('foo as bar'); - qb.selectArray.should.eql(['`foo` as `bar`']); + qb.select_array.should.eql(['`foo` as `bar`']); }); it('should not double-escape aliases', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['foo as `bar`']); - qb.selectArray.should.eql(['`foo` as `bar`']); + qb.select_array.should.eql(['`foo` as `bar`']); }); it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', function() { - qb.resetQuery(); + qb.reset_query(); qb.select(['foo as bar','bar as foo']); - qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`']); + qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`']); }); it('should allow for field aliases with spaces in them', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('notes as The Notes'); - qb.selectArray.should.eql(['`notes` as `The Notes`']); + qb.select_array.should.eql(['`notes` as `The Notes`']); }); it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); - qb.selectArray.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); + qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); }); it('should allow for namespacing in field name (host.db.table.field)', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('star_system.planet'); - qb.selectArray.should.eql(['`star_system`.`planet`']); + qb.select_array.should.eql(['`star_system`.`planet`']); - qb.resetQuery(); + qb.reset_query(); qb.select('galaxy.star_system.planet'); - qb.selectArray.should.eql(['`galaxy`.`star_system`.`planet`']); + qb.select_array.should.eql(['`galaxy`.`star_system`.`planet`']); - qb.resetQuery(); + qb.reset_query(); qb.select('universe.galaxy.star_system.planet'); - qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); + qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); }); it('should allow for namespacing in field name (host.db.table.column) + alias', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('universe.galaxy.star_system.planet as planet'); - qb.selectArray.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); + qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); }); it('should not allow subqueries or functions with commas in them without the second parameter being false', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); }).to.throw(Error); @@ -148,21 +148,21 @@ describe('select()', function() { }).to.not.throw(Error); }); it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', function() { - qb.resetQuery(); + qb.reset_query(); qb.select('count(*) as count', false); - qb.selectArray.should.eql(['count(*) AS `count`']); + qb.select_array.should.eql(['count(*) AS `count`']); - qb.resetQuery(); + qb.reset_query(); qb.select('count(*) as count, m.*, MIN(id) as min', false); - qb.selectArray.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); + qb.select_array.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); - qb.resetQuery(); + qb.reset_query(); qb.select('(select count(p.*) as count from planets p) as num_planets', false); - qb.selectArray.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); + qb.select_array.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); - qb.resetQuery(); + qb.reset_query(); qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); - qb.selectArray.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); + qb.select_array.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); }); }); @@ -177,9 +177,9 @@ for (var i in prefixes) { qb['select_' + type].should.be.a('function'); }); it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', function() { - qb.resetQuery(); + qb.reset_query(); qb['select_' + type]('s.star_systems'); - qb.selectArray.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); + qb.select_array.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); }); }); } \ No newline at end of file diff --git a/test/tests-set.js b/test/tests-set.js index 9d584eb..ce35208 100755 --- a/test/tests-set.js +++ b/test/tests-set.js @@ -11,13 +11,13 @@ describe('set()', function() { qb.set.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('setArray'); + qb.should.have.property('set_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.setArray.should.be.empty; + qb.set_array.should.be.empty; }); it('should not accept anything but a non-empty string or a non-empty object as first param', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.set(); }, 'nothing provided').to.throw(Error); expect(function() { qb.set(null); }, 'null provided').to.throw(Error); expect(function() { qb.set(false); }, 'false provided').to.throw(Error); @@ -37,7 +37,7 @@ describe('set()', function() { expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); }); it('should not accept anything but a string, number, null, or boolean as second param', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); expect(function() { qb.set('planet_position',NaN); }, 'NaN provided').to.throw(Error); @@ -56,48 +56,48 @@ describe('set()', function() { expect(function() { qb.set('planet_position','Three'); }, 'valid provided').to.not.throw(Error); }); it('should add first param (key) and second param (value) to hash and escape them properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.set('galaxy_name','Milky Way'); - qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'"}); + qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'"}); }); - it('should merge passed object into setArray and escape items properly', function() { - qb.resetQuery(); + it('should merge passed object into set_array and escape items properly', function() { + qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}); - qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'"}); + qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'"}); - qb.resetQuery(); + qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); - qb.setArray.should.eql({"`galaxy_name`": "'Milky Way'", "`galaxy_class`": "'C'"}); + qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'", "`galaxy_class`": "'C'"}); }); it('should not escape items if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.setArray.should.eql({galaxy_name: 'Milky Way'}); + qb.set_array.should.eql({galaxy_name: 'Milky Way'}); }); - it('should append more items to setArray as set() is called', function() { - qb.resetQuery(); + it('should append more items to set_array as set() is called', function() { + qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}, null, false); qb.set('galaxy_size','D'); - qb.setArray.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + qb.set_array.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); - qb.setArray.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + qb.set_array.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); }); it('should overwrite values of keys that have been set already', function() { - qb.resetQuery(); + qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D'); - qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'D'"}); + qb.set_array.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'D'"}); }); it('should NOT overwrite values of keys that are the same but have different escape flags', function() { - qb.resetQuery(); + qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D', false); - qb.setArray.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'C'", galaxy_class: 'D'}); + qb.set_array.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'C'", galaxy_class: 'D'}); }); }); \ No newline at end of file diff --git a/test/tests-where.js b/test/tests-where.js index 34d8749..5cab369 100755 --- a/test/tests-where.js +++ b/test/tests-where.js @@ -11,13 +11,13 @@ describe('where()', function() { qb.where.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('whereArray'); + qb.should.have.property('where_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.whereArray.should.be.empty; + qb.where_array.should.be.empty; }); it('should not accept anything but a non-empty string or a non-empty object', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.where(); }, 'nothing provided').to.throw(Error); expect(function() { qb.where(null); }, 'null provided').to.throw(Error); expect(function() { qb.where(false); }, 'false provided').to.throw(Error); @@ -34,112 +34,112 @@ describe('where()', function() { }); it('should accept a field name in the form of a string as the first parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet'); - qb.whereArray.should.eql(['`planet` IS NULL']); + qb.where_array.should.eql(['`planet` IS NULL']); }); it('should assume second param is NULL if not provided', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet'); - qb.whereArray.should.eql(['`planet` IS NULL']); + qb.where_array.should.eql(['`planet` IS NULL']); }); it('should accept NULL as second parameter and assume IS NULL', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',null); - qb.whereArray.should.eql(['`planet` IS NULL']); + qb.where_array.should.eql(['`planet` IS NULL']); }); it('should accept boolean values and will transform them properly', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',true); - qb.whereArray.should.eql(['`planet` = 1']); + qb.where_array.should.eql(['`planet` = 1']); - qb.resetQuery(); + qb.reset_query(); qb.where('planet',false); - qb.whereArray.should.eql(['`planet` = 0']); + qb.where_array.should.eql(['`planet` = 0']); }); it('should accept integer and float values', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',5); - qb.whereArray.should.eql(['`planet` = 5']); + qb.where_array.should.eql(['`planet` = 5']); - qb.resetQuery(); + qb.reset_query(); qb.where('planet',123.456); - qb.whereArray.should.eql(['`planet` = 123.456']); + qb.where_array.should.eql(['`planet` = 123.456']); }); it('should accept string values', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet','Earth'); - qb.whereArray.should.eql(["`planet` = 'Earth'"]); + qb.where_array.should.eql(["`planet` = 'Earth'"]); - qb.resetQuery(); + qb.reset_query(); qb.where('galaxy','Milky Way'); - qb.whereArray.should.eql(["`galaxy` = 'Milky Way'"]); + qb.where_array.should.eql(["`galaxy` = 'Milky Way'"]); }); it('should accept arrays of values and assume a WHERE IN clause', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']); - qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); }); it('should concatenate multiple where clauses with AND by default', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']); qb.where('galaxy','Milky Way'); - qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); - qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); }); it('should accept an object of key/value pairs (field: value)', function() { - qb.resetQuery(); + qb.reset_query(); qb.where({planet:'Earth',star_system:'Solar'}); - qb.whereArray.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); + qb.where_array.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); }); it('should accept an object of key/value pairs (field: value) where values can be arrays', function() { - qb.resetQuery(); + qb.reset_query(); qb.where({star_system:'Solar',planet:['Earth','Mars']}); - qb.whereArray.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); + qb.where_array.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); }); it('should accept an operators in the first parameter', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('position >',3); - qb.whereArray.should.eql(["`position` > 3"]); + qb.where_array.should.eql(["`position` > 3"]); - qb.resetQuery(); + qb.reset_query(); qb.where('position <',3); - qb.whereArray.should.eql(["`position` < 3"]); + qb.where_array.should.eql(["`position` < 3"]); - qb.resetQuery(); + qb.reset_query(); qb.where('position >=',3); - qb.whereArray.should.eql(["`position` >= 3"]); + qb.where_array.should.eql(["`position` >= 3"]); - qb.resetQuery(); + qb.reset_query(); qb.where('position <=',3); - qb.whereArray.should.eql(["`position` <= 3"]); + qb.where_array.should.eql(["`position` <= 3"]); - qb.resetQuery(); + qb.reset_query(); qb.where('position <>',3); - qb.whereArray.should.eql(["`position` <> 3"]); + qb.where_array.should.eql(["`position` <> 3"]); - qb.resetQuery(); + qb.reset_query(); qb.where('position !=',3); - qb.whereArray.should.eql(["`position` != 3"]); + qb.where_array.should.eql(["`position` != 3"]); }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.where({star_system:'Solar',planet:['Earth','Mars']},false); - qb.whereArray.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); + qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", function() { - qb.resetQuery(); + qb.reset_query(); qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - qb.whereArray.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); }); it("should escape (quote) functions and subqueries as strings when provided as second parameter", function() { - qb.resetQuery(); + qb.reset_query(); qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - qb.whereArray.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); + qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); }); }); @@ -151,19 +151,19 @@ describe('or_where()', function() { qb.or_where.should.be.a('function'); }); it('should prepend tertiary WHERE clauses with "OR"', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where('planet','Mars'); qb.or_where('planet','Venus'); - qb.whereArray.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where('planet','Mars').or_where('planet','Venus'); - qb.whereArray.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); }); it('should be chainable with normal where', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); - qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); }); }); \ No newline at end of file diff --git a/test/tests-where_in.js b/test/tests-where_in.js index 72e3369..b228d96 100755 --- a/test/tests-where_in.js +++ b/test/tests-where_in.js @@ -11,13 +11,13 @@ describe('where_in()', function() { qb.where_in.should.be.a('function'); }); it('should have an array to put fields into', function() { - qb.should.have.property('whereInArray'); + qb.should.have.property('where_in_array'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.whereInArray.should.be.empty; + qb.where_in_array.should.be.empty; }); it('should not accept anything but a non-empty string as first parameter', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.where_in(); }, 'nothing provided').to.throw(Error); expect(function() { qb.where_in(null); }, 'null provided').to.throw(Error); expect(function() { qb.where_in(false); }, 'false provided').to.throw(Error); @@ -36,7 +36,7 @@ describe('where_in()', function() { expect(function() { qb.where_in('planet_position',[1,2,3]); }, 'valid string provided').to.not.throw(Error); }); it('should not accept anything but a non-empty array of values as second parameter', function() { - qb.resetQuery(); + qb.reset_query(); expect(function() { qb.where_in('planet'); }, 'nothing provided').to.throw(Error); expect(function() { qb.where_in('planet',null); }, 'null provided').to.throw(Error); expect(function() { qb.where_in('planet',false); }, 'false provided').to.throw(Error); @@ -54,25 +54,25 @@ describe('where_in()', function() { expect(function() { qb.where_in('planet',['Mars','Earth','Venus','Mercury']); }, 'non-empty array provided').to.not.throw(Error); }); it('should require both a field name an array of values as first and second parameters, respectively', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_in('planet_position',[1,2,3]); - qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)']); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)']); }); it('should concatenate multiple WHERE IN clauses with AND ', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']); qb.where_in('galaxy_id',[123,456,789,0110]); - qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); - qb.whereArray.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_in('planet_position',[1,2,3],false); - qb.whereArray.should.eql(['planet_position IN (1, 2, 3)']); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); @@ -84,30 +84,30 @@ describe('where_not_in()', function() { qb.where_not_in.should.be.a('function'); }); it('should prepend "NOT " to "IN"', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); }); it('should prepend tertiary WHERE clauses with "AND"', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]); qb.where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); }); it('should be chainable with normal where', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.where_not_in('planet_position',[1,2,3],false); - qb.whereArray.should.eql(['planet_position NOT IN (1, 2, 3)']); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); }); }); @@ -119,25 +119,25 @@ describe('or_where_in()', function() { qb.or_where_in.should.be.a('function'); }); it('should prepend tertiary WHERE clauses with "OR"', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_in('planet_position',[1,2,3]); qb.or_where_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); }); it('should be chainable with normal where', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_in('planet_position',[1,2,3],false); - qb.whereArray.should.eql(['planet_position IN (1, 2, 3)']); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); @@ -149,29 +149,29 @@ describe('or_where_not_in()', function() { qb.or_where_in.should.be.a('function'); }); it('should prepend "NOT " to "IN"', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); }); it('should prepend tertiary WHERE clauses with "OR"', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]); qb.or_where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); }); it('should be chainable', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); }); it('should be chainable with normal where', function() { - qb.resetQuery(); + qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); - qb.whereArray.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); }); it('should not escape fields if asked not to', function() { - qb.resetQuery(); + qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3],false); - qb.whereArray.should.eql(['planet_position NOT IN (1, 2, 3)']); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); }); }); \ No newline at end of file From a110f1befc794cffc4550196ec3a4cf4410d9140 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 3 Oct 2014 18:07:11 -0400 Subject: [PATCH 027/200] MAJOR departure from node-mysql-activerecord. This update is actually splitting from being a mysql-specific library to being a 'Universal QueryBuilder' much like Codeigniter's QueryBuilder library. Major updates to code, documentation, etc... --- .gitignore | 1 + HISTORY.md | 8 +- Makefile | 2 +- README.md | 497 +++++++------------ drivers/drivers.json | 69 +++ drivers/mysql/connect.js | 177 +++++++ {lib => drivers/mysql}/query_builder.js | 113 +++-- drivers/mysql/query_exec.js | 136 ++++++ drivers/template/connect.js | 82 ++++ index.js | 624 ++++++++++-------------- index_old.js | 379 ++++++++++++++ package.json | 12 +- test/{ => mysql}/test-distinct.js | 2 +- test/{ => mysql}/test-limit.js | 2 +- test/{ => mysql}/test-offset.js | 2 +- test/{ => mysql}/tests-delete.js | 2 +- test/{ => mysql}/tests-from.js | 2 +- test/{ => mysql}/tests-general.js | 2 +- test/{ => mysql}/tests-groupby.js | 2 +- test/{ => mysql}/tests-having.js | 2 +- test/{ => mysql}/tests-insert.js | 2 +- test/{ => mysql}/tests-join.js | 3 +- test/{ => mysql}/tests-like.js | 2 +- test/{ => mysql}/tests-orderby.js | 2 +- test/{ => mysql}/tests-select.js | 2 +- test/{ => mysql}/tests-set.js | 2 +- test/{ => mysql}/tests-where.js | 2 +- test/{ => mysql}/tests-where_in.js | 2 +- 28 files changed, 1350 insertions(+), 783 deletions(-) mode change 100644 => 100755 Makefile create mode 100755 drivers/drivers.json create mode 100755 drivers/mysql/connect.js rename {lib => drivers/mysql}/query_builder.js (94%) create mode 100755 drivers/mysql/query_exec.js create mode 100755 drivers/template/connect.js create mode 100755 index_old.js rename test/{ => mysql}/test-distinct.js (87%) rename test/{ => mysql}/test-limit.js (98%) rename test/{ => mysql}/test-offset.js (97%) rename test/{ => mysql}/tests-delete.js (98%) rename test/{ => mysql}/tests-from.js (98%) rename test/{ => mysql}/tests-general.js (73%) rename test/{ => mysql}/tests-groupby.js (97%) rename test/{ => mysql}/tests-having.js (98%) rename test/{ => mysql}/tests-insert.js (99%) rename test/{ => mysql}/tests-join.js (99%) rename test/{ => mysql}/tests-like.js (97%) rename test/{ => mysql}/tests-orderby.js (98%) rename test/{ => mysql}/tests-select.js (99%) rename test/{ => mysql}/tests-set.js (98%) rename test/{ => mysql}/tests-where.js (99%) rename test/{ => mysql}/tests-where_in.js (99%) diff --git a/.gitignore b/.gitignore index 8bb42f4..0571778 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store node_modules npm-debug* +index2.js diff --git a/HISTORY.md b/HISTORY.md index 2a067a5..6348e4a 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2014-07-17) +## v0.9.0 (2014-09-24) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -27,4 +27,8 @@ you spot any mistakes. * New package dependencies (for testing): chai and mocha. * Tests have been written for better-ensuring future enhancements and fixes to not break functionality * Library has been broken into 3 objects to allow for prpoer testing. This won't affect the API and is fully-backwards compatible with the previous version. -* Officially announcing that how third parameter of the `where()` method works is deprecated... starting with v1.0.0, third param will be `(bool) escape` and not `(bool) isRaw`. So, all calls to this method using the 3rd parameter will, in future, have to be changed by converting `true` to `false` and vice versa. This is so that we have a consistent API throughout. \ No newline at end of file +* Officially announcing that how third parameter of the `where()` method works is deprecated... starting with v1.0.0, third param will be `(bool) escape` and not `(bool) isRaw`. So, all calls to this method using the 3rd parameter will, in future, have to be changed by converting `true` to `false` and vice versa. This is so that we have a consistent API throughout the library. +* Name officially changed to node-mysql-querybuilder. +* Author officially changed to Kyle Farris due to the substantial changes to this fork and Martin Tajur demoted to primary contributor (thanks for the great starting place Martin!!) +* Name of internal methods and properties have been normalized to use the "lower_case" syntax. +* Dependency for node-mysql upgraded to 2.5.*. \ No newline at end of file diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index 6a0dc75..66adf16 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = test/*.js +TESTS = test/mysql/*.js test: mocha --timeout 5000 --check-leaks --reporter nyan $(TESTS) diff --git a/README.md b/README.md index bb4f7ff..b2daffc 100755 --- a/README.md +++ b/README.md @@ -1,34 +1,45 @@ -MySQL ActiveRecord Adapter for Node.js +Universal QueryBuilder for Node.js ====================================== -Active Record Database Pattern implementation on top of node-mysql module (https://github.com/felixge/node-mysql). +Node-QueryBuilder is an ambitious attempt to create a kind of "universal translator" which provides programmers a consistent API to connect to and query *any* database (traditional and NoSQL) supported by the module. The module is highly extensible and, in theory, can suppport any database provided that the driver has been written for it. -To me, the main benefit of Active Record is the ability to direct JavaScript objects straight to MySQL query components without having to worry about constructing the query itself. Although Active Record is maybe a tiny step closer to ORM, I see a lot of value in the Active Record as it allows more control over database queries than traditional ORM. +The API of this module very closely mimics Codeigniter's Active Record (now called "Query Builder") library and much of the code has been directly translated from the PHP libraries in Codeigniter to JavaScript. A lot of credit needs to go to he folks over at EllisLab (https://ellislab.com/codeigniter) and all the contributors to the Codeigniter project (of which I am one): https://github.com/EllisLab/CodeIgniter/ - * Light-weight - * Supports all basic MySQL commands +The primary benefits of this module (currently) are: + + * Ability to write queries agnostically to the database you intend to query + * Supports all basic database commands (insert, update, delete, select, etc...) + * Extend commands from the most popular native database drivers in NPM. * Supports method chaining * Automatically escapes field values - * Does not need any other modules to operate (it already includes the underlying node-mysql module) + * Is fully unit tested + * Allows for greater flexibility and more control over a full ORM + * Ligher-weight than an ORM + * Allows you to drop down to the native methods of your driver if you choose + * Allows for different drivers for different versions (SQLite 2 vs SQLite 3) + +Database Drivers +================= + +Currently Written: +------------------ +* MySQL + +Coming Soon: +------------ + +* Postgres +* Microsoft SQL Server +* Oracle +* SQLite +* MongoDB How to install ============== - npm install mysql-activerecord - - -Get started ------------ - - var Db = require('mysql-activerecord'); - var db = new Db.Adapter({ - server: 'localhost', - username: 'root', - password: '12345', - database: 'test' - }); + npm install node-querybuilder -Licence info +Licence Info ============ Licensed under the GPL license and MIT: @@ -36,360 +47,190 @@ Licensed under the GPL license and MIT: * http://www.opensource.org/licenses/GPL-license.php * http://www.opensource.org/licenses/mit-license.php -Basic support of MySQL commands -=============================== +Quick Example +============= - * SELECT - * DISTINCT - * FROM - * IN - * MIN - * MAX - * AVG - * SUM - * UPDATE - * INSERT (single-row and multi-row) - * INSERT IGNORE - * DELETE - * JOIN - * LIMIT and OFFSET - * ORDER BY - * GROUP BY - * COUNT - * HAVING - -Methods -======= - -# .select() - -## .select(selectFieldName) -Specifies the field(s) to use in the SELECT query as a atring. - - db.select("id, CONCAT(first_name, ' ', last_name) as full_name, email"); - // This would produce: SELECT id, CONCAT(first_name, ' ', last_name) as full_name, email … - -You can call .select() multiple times within the scope of one query — all parameters will be used in the final query. E.g. +```javascript +var settings = { + host: 'localhost', + database: 'mydatabase', + user: 'myuser', + password: 'MyP@ssw0rd' +}; +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'standard'); - db.select('id'); - // do some advanced checking and calculations here (only synchronous work, though!) - db.select('first_name, last_name'); - // This would procude: SELECT id, first_name, last_name … - -## .select([selectFieldName, selectFieldName, … ]) -Same as above, with a difference of taking in fields list as an array. - - db.select(['id', 'first_name', 'last_name']); - // This would produce: SELECT id, first_name, last_name … - -# .where() - -## .where(rawClause) -Specifies a where clause component. - - db.where('add_time is null'); - // This would produce: … WHERE add_time is null … - -You can call .where() multiple times within the scope of one query — all parameters will be used in the final query. - -## .where(fieldName, [possibleWhereInValue, possibleWhereInValue]) -Specifies a WHERE IN structure to use in the query. +qb.select('name','position').where({type: 'rocky', 'diameter <': 12000}).get('planets', function(err,rows) { + if (err) console.error("Uh oh! Couldn't get results: " + err.msg); + + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); + + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.dir(rows); +}); +``` - db.where('first_name', ['John', 'Maria', 'Jason', 'Herbert']); - // This would produce: … WHERE first_name in ('John', 'Maria', 'Jason', 'Herbert') … +Connecting to Your Database +=========================== -## .where(fieldName, fieldValue) -Specifies a single WHERE condition to use in the query. +Quick Reference +--------------- - db.where('first_name', 'John'); - // This would produce: … WHERE first_name = 'John' … +| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | +| :-------- | :------ | :----- | :------- | :--- | :------ | :------------------------------------------------------- | +| mysql | X | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | +| mssql | | No | Yes | ??? | ??? | | +| sqlite | | No | Yes | ??? | ??? | | +| oracle | | No | Yes | ??? | ??? | | +| postgres | | No | Yes | ??? | ??? | | +| mongodb | | No | Yes | ??? | ??? | | -## .where({ fieldName: fieldValue, fieldName: fieldValue, … }) -Specifies multiple WHERE conditions to use in the query. - var conditions = { - first_name: 'John', - last_name: 'Smith' - }; - db.where(conditions); - // This would produce: … WHERE first_name = 'John' AND last_name = 'Smith' … +Standard Connection Settings +---------------------------- -# .order_by() +The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. -## .order_by(orderByCondition) -Specifies the ORDER BY condition as a full string. +| Option | Default | Description | +| :-------- | :----- | :-------------------------------------------- | +| host | localhost | The server you're connecting to | +| user | NULL | The database user | +| password | NULL | The database `user`'s password | +| database | NULL | The database to connect to | +| pool_size | 10 | Max connections for `pool` connection type | - db.order_by('name asc'); - // This would produce: … ORDER BY name asc … +The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. -You can call .order_by() multiple times within the scope of one query — all parameters will be used in the final query. +**Example JSON File** -## .order_by([orderByCondition, orderByCondition, … ]) -Specifies multiple ORDER BY conditions as an array. +We'll call this `db.json` (you can also just have a normal javascript object directly within your code somwhere if you're risky like that). - db.order_by(['name asc', 'last_name desc']); - // This would produce: … ORDER BY name asc, last_name desc … +```javascript +{ + "host": "db.myserver.com", + "user": "myusername", + "password": "P@s$w0rD", + "database": "myDB", + "pool_size": 50 +} +``` -# .group_by() +**Example App** -## .group_by(groupByCondition) -Specifies the GROUP BY condition as a full string. +```javascript +var settings = require('db.json'); +var qb = require('node-querybuilder').QueryBuilder(settings); +``` - db.group_by('name asc'); - // This would produce: … GROUP BY name asc … +Choosing the Database Type +-------------------------- -You can call .group_by() multiple times within the scope of one query — all parameters will be used in the final query. +This part is super simple. Just pass which one you'd like to use as the second paramter to the constructor (`mysql` is the default): -## .group_by([groupByCondition, groupByCondition, … ]) -Specifies the GROUP BY condition as a full string. +***Example:*** - db.group_by(['name asc', 'last_name desc']); - // This would produce: … GROUP BY name asc, last_name desc … +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings, 'postgres'); +``` -# .join() +Choosing the Connection Type +---------------------------- -## .join(tableName, joinCondition, joinDirection) -Join additional tables to the query. +This library currently supports 3 connection methods: - db.join('pets', 'pets.owner_id = people.id', 'LEFT'); - // This would produce: … LEFT JOIN pets ON pets.owner_id = people.id … +* standard (default) + * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications buy might be quite suitable for command line scripts and the like. + * **All drivers must have this connection type**. +* pool + * This will utilize the driver's connection pooling capabilities if it is provided. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. +* cluster + * When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. - db.join('pets', 'pets.owner_id = people.id'); - // This would produce: … JOIN pets ON pets.owner_id = people.id … +**Note:** +You will specify the type of connection as the third parameter to the contructor -# .limit() +**Example:** -## .limit(limitNumber) -Adds a row limit to query results. +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +``` - db.limit(10); - // Limits query results to 10 rows. -## .limit(limitNumber, offsetNumber) -Adds a row limit with an offset pointer position to query results. +API Methods +=============================== - db.limit(10, 30); - // Limits query results to 10 rows, starting from the 30th row in the full matching set. + * SELECT + * DISTINCT + * FROM + * IN + * MIN + * MAX + * AVG + * SUM + * UPDATE + * INSERT (single-row and multi-row) + * INSERT IGNORE + * DELETE + * JOIN + * LIMIT and OFFSET + * ORDER BY + * GROUP BY + * COUNT + * HAVING -# Query execution commands +Methods +------- -After execution of a query, all query conditions are cleared. Results are passed down to responseCallback function. The parameters handed over to responseCallback match exactly what the underlying node-mysql module produces. See documentation from https://github.com/felixge/node-mysql +### SELECT -## .update(tableName, newData, responseCallback) -Produces and executes UPDATE query. +This method is used to specify the fields to pull into the resultset when running SELECT-like queries. - db.update('people', { first_name: 'John', last_name: 'Smith' }, function(err) { ... }); - // This would produce: … UPDATE people SET first_name = 'John', last_name = 'Smith' … +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------- | +| fields | String|Array | N/A | The fields in which to grab from the database | +| escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | -## .delete(tableName, responseCallback) -Produces and executes DELETE query. Be sure to specify some WHERE clause components using .where() not to truncate an entire table. ✌ - db.delete('people', function(err) { ... }); - -## .insert(tableName, newData, responseCallback) -Produces and executes a single-row INSERT query. +#### .select(fields) - db.insert('people', { first_name: 'John', last_name: 'Smith' }, function(err, info) { ... }); - // This would produce: … INSERT INTO people SET first_name = 'John', last_name = 'Smith' … +The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways: -## .insert(tableName, [newData, newData, newData, …], responseCallback) -Produces and executes a multi-row INSERT query. - - var person1 = { first_name: 'John', last_name: 'Smith' }; - var person2 = { first_name: 'Jason', last_name: 'Binder' }; - var person3 = { first_name: 'Herbert', last_name: 'von Kellogg' }; - db.insert('people', [person1, person2, person3], function(err, info) { ... }); - // This would produce: … INSERT INTO people (first_name, last_name) VALUES (('John','Smith'),('Jason','Binder'),('Herbert','von Kellogg')) … - -## .insert_ignore(tableName, newData, responseCallback, onDuplicateKeyClause) -Produces and executes an INSERT IGNORE query. Note that the newData parameter can be either a string (produces single-row INSERT) or an array (produces multi-row INSERT). You can also specify an optional onDuplicateKeyClause, e.g. +* String with fields seperated by a comma: + * `.select('foo, bar, baz')` +* Array of field names + * `.select(['foo','bar','baz'])` - db.insert_ignore('people', { first_name: 'John', last_name: 'Smith' }, function(err, info) { ... }, 'ON DUPLICATE KEY UPDATE duplicate_count = duplicate_count + 1'); - // This would produce: … INSERT IGNORE INTO people SET first_name = 'John', last_name = 'Smith' … ON DUPLICATE KEY UPDATE duplicate_count = duplicate_count + 1 +**Examples** -## .get(tableName, responseCallback) -Produces and executes a SELECT query. +```javascript +// SELECT `foo`, `bar`, `baz` +qb.select(['foo','bar','baz']); +``` - db.get('people', function(err, rows, fields) { ... }); - // This would produce: SELECT … FROM people … +You can chain the method together using different patterns if you want: -## .count(tableName, responseCallback) -Produces and executes a SELECT query with count. +```javascript +// SELECT `foo`, `bar`, `baz`, `this`, `that`, `the_other` +qb.select(['foo','bar','baz']).select('this,that,the_other'); +``` - db.get('people', function(err, rows, fields) { ... }); - // This would produce: SELECT count(*) FROM people … +You can alias your field names and they will be escaped properly as well: -## .query(sqlQueryString, responseCallback) -Produces and executes a raw query. Note that while no set query conditions will be used in this query, they will all be reset nevertheless with the execution. +```javascript +// SELECT `foo` as `f`, `bar` as `b`, `baz` as `z` +qb.select(['foo as f','bar as b','baz as z']); +``` - db.query('SHOW TABLES FROM test_database', function(err, results) { ... }); +#### .select(fields,escape) -## .ping() -Pings the connection. This is useful when extending idle timeouts. +You can optionally choose not to have the driver auto-escape the fieldnames (dangerous, but useful if you a function in your select statement, for instance): -## ._last_query() -Returns the last executed query as a string. - -## .connection() -Returns the underlying database connection object, ultimately what https://github.com/felixge/node-mysql .createConnection() returns. - -Pooling connections -=================== - -Single or multiple connections can be pooled with the Pool object. - - var Db = require('mysql-activerecord'); - - var pool = new Db.Pool({ - server: 'localhost', - username: 'root', - password: '12345', - database: 'test' - }); - - pool.getNewAdapter(function(db) { - db - .where({ name: 'Martin' }) - .get('people', function(err, results, fields) { - console.log(results); - db.releaseConnection(); - // do not do anything with db that has been released. - }); - }); - -Some more usage examples -======================== - -Establishing a connection -------------------------- - - var Db = require('mysql-activerecord'); - var db = new Db.Adapter({ - server: 'localhost', - username: 'root', - password: '12345', - database: 'test' - }); - -Basic SELECT query ------------------- - - db.get('people', function(err, results, fields) { - console.log(results); - }); - -INSERT query ------------- - - var data = { - name: 'Martin', - email: 'martin@example.com' - }; - - db.insert('people', data, function(err, info) { - console.log('New row ID is ' + info.insertId); - }); - -INSERT IGNORE query with ON DUPLICATE KEY clause ------------------------------------------------- - - var data = { - name: 'Martin', - email: 'martin@example.com' - }; - - db.insert_ignore('people', data, function(err, info) { - console.log('New row ID is ' + info.insertId); - }, 'ON DUPLICATE KEY SET counter = counter + 1'); - -SELECT query with WHERE clause ------------------------------- - - db - .where({ name: 'Martin' }) - .get('people', function(err, results, fields) { - console.log(results); - }); - -SELECT query with custom fields, WHERE, JOIN and LIMIT ------------------------------------------------------- - - db - .select(['people.id', 'people.name', 'people.email', 'songs.title']) - .join('songs', 'people.favorite_song_id', 'left') - .where({ - 'people.name': 'Martin', - 'songs.title': 'Yesterday' - }) - .limit(5, 10) - .order_by('people.name asc') - .get('people', function(err, results, fields) { - console.log(results); - }); - -Basic counting ------------------------------------------------------- - - db - .where({ - 'people.name': 'Martin', - 'songs.title': 'Yesterday' - }) - .count('people', function(err, results, fields) { - console.log(results); - }); - -SELECT query with custom fields and GROUP BY --------------------------------------------- - - db - .select('name, COUNT(name) AS name_count') - .group_by('name') - .order_by('name_count DESC') - .get('people', function(err, results, fields) { - console.log(results); - }); - -Basic UPDATE query ------------------- - - var newData = { - name: 'John', - email: 'john@doe.com' - }; - - db - .where({ id: 1 }); - .update('people', newData, function(err) { - if (!err) { - console.log('Updated!'); - } - }); - -Basic DELETE query ------------------- +**Example** - db - .where({ id: 1 }) - .delete('people', function(err) { - if (!err) { - console.log('Deleted!') - } - }); - - -Advanced WHERE conditions -------------------------- - - db - .where("title not like '%Jackson%'") - .where("date_created > '2012-03-10'") - .where({ owner_id: 32 }) - .delete('records', function(err) { - if (!err) { - console.log('Deleted!') - } - }); +```javascript +// SELECT MAX(id) as `max_id` +qb.select('MAX(id) as `max_id`',false); +``` Contribute ========== diff --git a/drivers/drivers.json b/drivers/drivers.json new file mode 100755 index 0000000..ce574c1 --- /dev/null +++ b/drivers/drivers.json @@ -0,0 +1,69 @@ +{ + "mysql": { + "connection_types": { "standard": true, "pool": true, "cluster": true }, + "versions": { + "default": { + "path": "./drivers/mysql/", + "active": true + }, + "6": { + "path": "./drivers/mysql/6/", + "active": false + } + } + }, + "mssql": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/mssql/", + "active": false + } + } + }, + "oracle": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/mssql/", + "active": false + } + } + }, + "postgres": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/postgres/", + "active": false + } + } + }, + "sqlite": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/sqlite/", + "active": false + } + } + }, + "sqlite3": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/sqlite3/", + "active": false + } + } + }, + "mongo": { + "connection_types": { "standard": true, "pool": false, "cluster": false }, + "versions": { + "default": { + "path": "./drivers/mongo/", + "active": false + } + } + } +} \ No newline at end of file diff --git a/drivers/mysql/connect.js b/drivers/mysql/connect.js new file mode 100755 index 0000000..14e721e --- /dev/null +++ b/drivers/mysql/connect.js @@ -0,0 +1,177 @@ +/** + * MySQL Connection Library + * Version 0.0.1 + * + * Fundamentally, this is a wrapper to the awesome `node-mysql` project that + * simply allows it to work generically within the Query Builder module. + * + * Supported connection types: + * - Single + * - Pool + * - PoolCluster + * + * Dependencies: node-mysql + * +**/ + +var Adapter = function(settings,type) { + var mysql = require('mysql'); + + this.debugging = false; + this.connection = null; + this.pool = null; + this.connection_type = 'single'; + + // Enable debugging if necessary + if (settings.hasOwnProperty('qb_debug') && settings.debug === true) { + this.debugging = true; + delete settings.qb_debug; + } + + this.settings = settings; + if (this.settings.hasOwnProperty('connection_type')) { + this.connection_type = this.settings.connection_type; + delete settings.connection_type; + } + + // Verify that required fields are provided... + if (!this.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + if (!this.settings.hasOwnProperty('password')) throw new Error("No user property provided. Hint: It can be NULL"); + + // **************************************************************************** + // Sets up a cluster of pooled connections to different servers for load + // balancing and failover + // ----- + // @param Object settings Connection settings + // @return VOID + // **************************************************************************** + this.cluster = function() { + throw new Error("Not implemented yet!"); + }; + + // **************************************************************************** + // Method for immediately destoying all open connections to MySQL--even if there + // are active queries being processed. + // ----- + // @return VOID + // **************************************************************************** + this.destroy = function() { + return that.connection.destroy(); + }; + + // **************************************************************************** + // Method for gracefully closing all connections to MySQL after all active + // queries have completed. + // ----- + // @param Function callback What to do once connection has been closed. + // @return VOID + // **************************************************************************** + this.disconnect = function(callback) { + return that.connection.end(callback); + }; + + // **************************************************************************** + // Escape values for MySQL + // ----- + // @param Mixed item String to escape + // @return String Escaped value + // **************************************************************************** + this.escape = function(item) { + that.get_connection(function(connection) { + return connection.escape(item); + }); + } + + // **************************************************************************** + // Returns a connection hangle (this will mostly be used for cluster and pool + // connection types, but, nonetheless can be used as a getter for the single + // type as well. + // ----- + // @param Function callback What to do once the connection is obtained + // @return VOID + // **************************************************************************** + this.get_connection = function(callback) { + switch(that.connection_type) { + case 'cluster': + break; + case 'pool': + that.pool.getConnection(function (err, connection) { + if (err) { + throw err; + } + callback(connection); + }); + break; + case 'single': + default: + callback(this.connection); + break; + } + }; + + // **************************************************************************** + // Sets up a connection pool + // ----- + // @param Object settings Connection settings + // @return VOID + // **************************************************************************** + this.pool = function() { + that.pool = mysql.createPool(this.settings); + + that.pool.query('SELECT 1 + 1 AS solution', function(err) { + if (err) throw err; + if (that.debugging === true) { + console.log('mysql connection pool created'); + } + }); + + return { + disconnect: function() { + that.pool().end(responseCallback); + } + } + }; + + // **************************************************************************** + // Actually executes a query. This is probably the single-most essential method + // in this class! + // ----- + // @param String sql The SQL to execute + // @param Function What to do after the query is executed + // @return VOID + // **************************************************************************** + this.query = function(sql,callback) { + that.get_connection(function(connection) { + connection.query(sql,callback); + }); + }; + + // **************************************************************************** + // Sets up a standard one-time connection (no pooling). This one is used by the + // other two methods to stay DRY. + // ----- + // @param Object settings Connection settings + // @return VOID + // **************************************************************************** + this.standard = function() { + that.connection = mysql.createConnection(this.settings); + that.connection.connect(function(err) { + if (err) { + console.error('error connecting to mysql: ' + err.stack); + return; + } + if (this.debugging === true) { + console.log('connected to mysql as id ' + connection.threadId); + } + }); + + that.connection.on('error', function(err) { + console.error(err.code); + }); + }; + + var that = this; + return that; +} + +exports.Adapter = Adapter; \ No newline at end of file diff --git a/lib/query_builder.js b/drivers/mysql/query_builder.js similarity index 94% rename from lib/query_builder.js rename to drivers/mysql/query_builder.js index 62dc0c2..66087ac 100755 --- a/lib/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -86,6 +86,11 @@ var QueryBuilder = function() { return key_array; } + // Simply setting all properties to [] causes reference issues in the parent class. + var clear_array = function(a) { + while (a.length) { a.pop(); } + }; + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // var track_aliases = function(qb,table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { @@ -258,7 +263,7 @@ var QueryBuilder = function() { if(qb.hasOwnProperty('escape')) { var do_escape = qb.escape; } else { - var SqlString = require('../node_modules/mysql/lib/protocol/SqlString.js'); + var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); var do_escape = SqlString.escape; } @@ -326,10 +331,6 @@ var QueryBuilder = function() { var sql = ' ORDER BY '; sql += qb.order_by_array.join(', '); - if (qb.order_by_dir !== false) { - sql += (qb.order_by_dir == 'desc' ? ' DESC' : ' ASC'); - } - return sql; }; @@ -348,7 +349,8 @@ var QueryBuilder = function() { }; var compile_select = function(qb) { - var sql = 'SELECT ' + qb.distinct_clause; + var distinct_clause = qb.distinct_clause[0] || ''; + var sql = 'SELECT ' + distinct_clause; if (qb.select_array.length === 0) { sql += '*'; } else { @@ -362,7 +364,10 @@ var QueryBuilder = function() { + build_having_clause(qb) + build_order_by_clause(qb); - sql = build_limit_clause(sql,qb.limit_to,qb.offset_val); + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + sql = build_limit_clause(sql,limit_to,offset_val); return sql; }; @@ -372,8 +377,11 @@ var QueryBuilder = function() { return this; } + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); - return build_limit_clause(sql,qb.limit_to,qb.offset_val); + return build_limit_clause(sql,limit_to,offset_val); }; var compile_update = function(qb) { @@ -388,7 +396,10 @@ var QueryBuilder = function() { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; } - var limit = (!qb.limit_to ? '' : ' LIMIT ' + qb.limit_to); + + var limit_to = qb.limit_to[0] || false; + + var limit = (!limit_to ? '' : ' LIMIT ' + limit_to); sql = 'UPDATE ' + table + " SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); @@ -406,33 +417,35 @@ var QueryBuilder = function() { select_array: [], set_array: [], order_by_array: [], - order_by_dir: false, group_by_array: [], having_array: [], - limit_to: false, - offset_val: false, + limit_to: [], // has to be array to work as reference + offset_val: [], // has to be array to work as reference join_clause: [], - last_query_string: '', - distinct_clause: '', + last_query_string: [], // has to be array to work as reference + distinct_clause: [], // has to be array to work as reference aliased_tables: [], reset_query: function(new_last_query) { - this.where_array = []; - this.where_in_array = []; - this.from_array = []; - this.join_array = []; - this.select_array = []; - this.set_array = []; - this.order_by_array = []; - this.order_by_dir = false; - this.group_by_array = []; - this.having_array = []; - this.limit_to = false; - this.offset_val = false; - this.join_clause = []; - this.distinct_clause = ''; - this.last_query_string = (typeof new_last_query === 'string' ? new_last_query : ''); - this.aliased_tables = []; + clear_array(this.where_array); + clear_array(this.where_in_array); + clear_array(this.from_array); + clear_array(this.join_array); + clear_array(this.select_array); + clear_array(this.set_array); + clear_array(this.order_by_array); + clear_array(this.group_by_array); + clear_array(this.having_array); + clear_array(this.limit_to); + clear_array(this.offset_val); + clear_array(this.join_clause); + clear_array(this.distinct_clause); + clear_array(this.aliased_tables); + + clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } }, where: function(key, value, escape) { @@ -570,7 +583,7 @@ var QueryBuilder = function() { this.where_array.push(where_in); // reset the array for multiple calls - this.where_in_array = []; + clear_array(this.where_in_array); return this; }, @@ -769,7 +782,7 @@ var QueryBuilder = function() { }, select: function(select,escape) { - // First param must be a non-empty string or object + // First param must be a non-empty string or array if (typeof select === 'string') { select = select.trim(); if (select.length == 0) { @@ -870,10 +883,11 @@ var QueryBuilder = function() { do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); if (do_distinct) { - this.distinct_clause = 'DISTINCT '; + clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); } else { - this.distinct_clause = ''; + clear_array(this.distinct_clause); } return this; }, @@ -1045,7 +1059,8 @@ var QueryBuilder = function() { }, limit: function(limit, offset) { - this.limit_to = prepare_for_limit_and_offset(limit,'limit'); + clear_array(this.limit_to); + this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); if (offset !== undefined) { return this.offset(offset); @@ -1055,7 +1070,8 @@ var QueryBuilder = function() { }, offset: function(offset) { - this.offset_val = prepare_for_limit_and_offset(offset,'offset'); + clear_array(this.limit_to); + this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); return this; }, @@ -1140,7 +1156,7 @@ var QueryBuilder = function() { } table = this.from_array[0]; } else { - this.from_array = []; + clear_array(this.from_array); this.from(table); } @@ -1169,7 +1185,7 @@ var QueryBuilder = function() { table = this.from_array[0]; } else { - this.from_array = []; + clear_array(this.from_array); this.from(table); } @@ -1219,7 +1235,7 @@ var QueryBuilder = function() { return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, - get: function(table, callback) { + get: function(table) { if (typeof table !== 'function') { track_aliases(this,table); this.from(table); @@ -1250,20 +1266,9 @@ var QueryBuilder = function() { return compile_select(this); }, - update: function(table, set, where, callback) { + update: function(table, set, where) { set = set || null; - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } - if (set !== null) { this.set(set); } @@ -1279,7 +1284,7 @@ var QueryBuilder = function() { table = this.from_array[0]; } else { - this.from_array = []; + clear_array(this.from_array); this.from(table); table = this.from_array[0]; @@ -1347,9 +1352,9 @@ var QueryBuilder = function() { }, _last_query: function() { - return this.last_query_string; + return this.last_query_string[0] || ''; } } }; -module.exports = QueryBuilder; \ No newline at end of file +exports.QueryBuilder = QueryBuilder; \ No newline at end of file diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js new file mode 100755 index 0000000..fdc2c27 --- /dev/null +++ b/drivers/mysql/query_exec.js @@ -0,0 +1,136 @@ +// **************************************************************************** +// QueryBuilder "Query Execution" methods. +// ----- +// @param Object qb The QueryBuilder object +// @param Object adapter The connection adapter object +// **************************************************************************** +var QueryExec = function(qb, adapter) { + return { + count: function(table, callback) { + var sql = qb.count(table); + + adapter.get_connection(function(connection) { + connection.query(sql, function(err, res) { + if (err) + callback(err, null); + else + callback(null, res[0]['count']); + }); + }); + }, + + get: function(table,callback) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); + } + + var sql = qb.get(table); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + get_where: function(table,where,callback) { + var sql = qb.get_where(table,where); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + insert: function(table,set,callback,ignore,suffix) { + var sql = qb.insert(table,set,ignore,suffix); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql,callback); + }); + }, + + insert_ignore: function(table,set,callback) { + var sql = qb.insert_ignore(table,set); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + insert_batch: function(table,set,callback) { + var sql = qb.insert_batch(table,set); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + update: function(table,set,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sql = qb.update(table,set,where); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + // TODO: Write this complicated-ass function + update_batch: function(table,set,where,callback) { + callback(new Error("This function is not currently available!"),null); + }, + + delete: function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } + + var sql = qb.delete(table, where); + + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql, callback); + }); + }, + + empty_table: function(table, callback) { + var sql = qb.empty_table(table,callback); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql,callback); + }); + }, + + truncate: function(table, callback) { + var sql = qb.truncate(table,callback); + qb.reset_query(sql); + adapter.get_connection(function(connection) { + connection.query(sql,callback); + }); + }, + } +} + +exports.QueryExec = QueryExec; \ No newline at end of file diff --git a/drivers/template/connect.js b/drivers/template/connect.js new file mode 100755 index 0000000..38fe981 --- /dev/null +++ b/drivers/template/connect.js @@ -0,0 +1,82 @@ +/** + * [DRIVERNAME] Connection Library + * Version 0.0.1 + * + * [PUT DESCRIPTION HERE] + * + * Supported connection types: + * - Single + * - Pool + * - PoolCluster + * + * Dependencies: + * +**/ + +var connect, Standard, Pool, PoolCluster; + +// **************************************************************************** +// Sets up a standard one-time connection (no pooling). This one is used by the +// other two methods to stay DRY. +// ----- +// @param Object settings Connection settings +// @return Object Connection handle +// **************************************************************************** +Standard = function(settings) { + +}; + +// **************************************************************************** +// Sets up a connection pool +// ----- +// @param Object settings Connection settings +// @return Object Connection handle +// **************************************************************************** +Pool = function(settings) { + +}; + +// **************************************************************************** +// Sets up a cluster of pooled connections to different servers for load +// balancing and failover +// ----- +// @param Object settings Connection settings +// @return Object Connection handle +// **************************************************************************** +PoolCluster = function(settings) { + +}; + +// **************************************************************************** +// Generic function for creating connections to databases +// ----- +// @param Object settings Connection settings (including the type) +// @return Object Connection handle +// **************************************************************************** +connect = function(settings,type) { + type = type || 'single'; + + var connection = null; + + switch(type) { + case 'single': + connection = Standard(settings); + break; + case 'pool': + connection = Pool(settings); + break; + case 'cluster': + connection = PoolCluster(settings); + break; + default: + throw new Error("Invalid connection type specified!"); + break; + } + + if (connection === null) { + throw new Error("A connection could not be established!"); + } + return connection; +} + +exports.connect = connect; \ No newline at end of file diff --git a/index.js b/index.js index bbdafe7..1569cbb 100755 --- a/index.js +++ b/index.js @@ -1,378 +1,252 @@ -/** - * MySQL ActiveRecord Adapter for Node.js - * (C) Martin Tajur 2011-2013 - * martin@tajur.ee - * - * Active Record Database Pattern implementation for use with node-mysql as MySQL connection driver. - * - * Dual licensed under the MIT and GPL licenses. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * -**/ -var Adapter = function(settings) { - var mysql = require('mysql'); - - this.qb = require('./lib/query_builder.js').QueryBuilder(); - - // Non-Public QueryBuilder APIs - this._where = this.qb._where; - this._where_in = this.qb._where_in; - this._like = this.qb._like; - this._min_max_avg_sum = this.qb._min_max_avg_sum; - this._having = this.qb._having; - this._update = this.qb._update; - this.resetQuery = this.qb.resetQuery; - - // QueryBuilder Properties - this.whereArray = this.qb.whereArray; - this.whereInArray = this.qb.whereInArray; - this.fromArray = this.qb.fromArray; - this.joinArray = this.qb.joinArray; - this.selectArray = this.qb.selectArray; - this.setArray = this.qb.setArray; - this.orderByArray = this.qb.orderByArray; - this.orderByDir = this.qb.orderByDir; - this.groupByArray = this.qb.groupByArray; - this.havingArray = this.qb.havingArray; - this.limitTo = this.qb.limitTo; - this.offsetVal = this.qb.offsetVal; - this.joinClause = this.qb.joinClause; - this.lastQueryString = this.qb.lastQueryString; - this.distinctClause = this.qb.distinctClause; - this.aliasedTables = this.qb.aliasedTables; - - // QueryBuilder method mappings for backwards compatibility - this.where = this.qb.where; - this.or_where = this.qb.or_where; - this.where_in = this.qb.where_in; - this.or_where_in = this.qb.or_where_in; - this.where_not_in = this.qb.where_not_in; - this.or_where_not_in = this.qb.or_where_not_in; - this.like = this.qb.like; - this.not_like = this.qb.not_like; - this.or_like = this.qb.or_like; - this.or_not_like = this.qb.or_not_like; - this.from = this.qb.from; - this.join = this.qb.join; - this.select = this.qb.select; - this.select_min = this.qb.select_min; - this.select_max = this.qb.select_max; - this.select_avg = this.qb.select_avg; - this.select_sum = this.qb.select_sum; - this.distinct = this.qb.distinct; - this.group_by = this.qb.group_by; - this.having = this.qb.having; - this.or_having = this.qb.or_having; - this.order_by = this.qb.order_by; - this.limit = this.qb.limit; - this.offset = this.qb.offset; - this.set = this.qb.set; - this.get_compiled_select = this.qb.get_compiled_select; - this.get_compiled_insert = this.qb.get_compiled_insert; - this.get_compiled_update = this.qb.get_compiled_update; - this.get_compiled_delete = this.qb.get_compiled_delete; - this._last_query = this.qb._last_query; - this.last_query = this.qb._last_query; - - // QueryBuilder "Query Execution" methods: - this.count = function(table, callback) { - var sql = this.qb.count(table); - - connection.query(sql, function(err, res) { - if (err) - callback(err, null); - else - callback(null, res[0]['count']); - }); - - return that; - }; - - this.get = function(table,callback) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); - } - - var sql = this.qb.get(table,callback); - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.get_where = function(table,where,callback) { - var sql = this.qb.get_where(table,where); - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.insert = function(table,set,callback,verb,suffix) { - var sql = this.qb.insert(table,set,callback,verb,suffix); - this.resetQuery(sql); - connection.query(sql); - return that; - }; - - this.insert_ignore = function(table,set,callback) { - var sql = this.qb.insert_ignore(table,set,callback); - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.insert_batch = function(table,set,callback) { - var sql = this.qb.insert_batch(table,set,callback); - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.update = function(table,set,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'update' call!"); - } - - var sql = this.qb.update(table,set,where,callback); - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.update_batch = function() { - - return that; - }; - - this.delete = function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } - - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } - - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - - var sql = this.delete(table, where); - - this.resetQuery(sql); - connection.query(sql, callback); - return that; - }; - - this.empty_table - function(table, callback) { - var sql = this.qb.empty_table(table,callback); - this.resetQuery(sql); - connection.query(sql); - return that; - }); - - this.truncate - function(table, callback) { - var sql = this.qb.truncate(table,callback); - this.resetQuery(sql); - connection.query(sql); - return that; - }); - - var initializeConnectionSettings = function () { - if(settings.server) { - settings.host = settings.server; - } - if(settings.username) { - settings.user = settings.username; - } - - if (!settings.host) { - throw new Error('Unable to start ActiveRecord - no server given.'); - } - if (!settings.port) { - settings.port = 3306; - } - if (!settings.user) { - settings.user = ''; - } - if (!settings.password) { - settings.password = ''; - } - if (!settings.database) { - throw new Error('Unable to start ActiveRecord - no database given.'); - } - - return settings; - }; - - var connection; - var connectionSettings; - var pool; - - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } else { - connectionSettings = initializeConnectionSettings(); - connection = new mysql.createConnection(connectionSettings); - } - - if (settings.charset) { - connection.query('SET NAMES ' + settings.charset); - } - - this.connectionSettings = function() { return connectionSettings; }; - this.connection = function() { return connection; }; - - this.query = function(sqlQueryString, responseCallback) { - connection.query(sqlQueryString, responseCallback); - this.resetQuery(sqlQueryString); - return that; - }; - - this.disconnect = function() { - return connection.end(); - }; - - this.forceDisconnect = function() { - return connection.destroy(); - }; - - this.releaseConnection = function() { - pool.releaseConnection(connection); - }; - - this.releaseConnection = function() { - pool.releaseConnection(connection); - }; - - this.ping = function() { - connection.ping(); - return that; - }; - - this.escape = function(str) { - return connection.escape(str); - }; - - var reconnectingTimeout = false; - - function handleDisconnect(connectionInstance) { - connectionInstance.on('error', function(err) { - if (!err.fatal || reconnectingTimeout) { - return; - } - - if (err.code !== 'PROTOCOL_CONNECTION_LOST' && err.code !== 'ECONNREFUSED') { - throw err; - } - - var reconnectingTimeout = setTimeout(function() { - connection = mysql.createConnection(connectionInstance.config); - handleDisconnect(connection); - connection.connect(); - }, 2000); - }); - } - - if (!pool) { - handleDisconnect(connection); - } - - var that = this; - - return this; -}; - -var mysqlPool; // this should be initialized only once. -var mysqlCharset; - -var Pool = function (settings) { - if (!mysqlPool) { - var mysql = require('mysql'); - - var poolOption = { - createConnection: settings.createConnection, - waitForConnections: settings.waitForConnections, - connectionLimit: settings.connectionLimit, - queueLimit: settings.queueLimit - }; - Object.keys(poolOption).forEach(function (element) { - // Avoid pool option being used by mysql connection. - delete settings[element]; - // Also remove undefined elements from poolOption - if (!poolOption[element]) { - delete poolOption[element]; - } - }); - - // Confirm settings with Adapter. - var db = new Adapter(settings); - var connectionSettings = db.connectionSettings(); - - Object.keys(connectionSettings).forEach(function (element) { - poolOption[element] = connectionSettings[element]; - }); - - mysqlPool = mysql.createPool(poolOption); - mysqlCharset = settings.charset; - } - - this.pool = function () { - return mysqlPool; - }; - - this.getNewAdapter = function (responseCallback) { - mysqlPool.getConnection(function (err, connection) { - if (err) { - throw err; - } - var adapter = new Adapter({ - pool: { - pool: mysqlPool, - enabled: true, - connection: connection - }, - charset: mysqlCharset - }); - responseCallback(adapter); - }); - }; - - this.disconnect = function (responseCallback) { - this.pool().end(responseCallback); - }; - +/** + * QueryBuilder for Node.js + * (C) Kyle Farris 2014 + * kyle@chomponllc.com + * + * A generic Query Builder for any SQL or NOSQL database adapter. + * + * Current adapters: + * - MySQL + * + * Requested Adapters: + * - MSSQL + * - postgres + * - sqlite + * - sqlite3 + * - oracle + * - mongo + * + * Dual licensed under the MIT and GPL licenses. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * +**/ +var QueryBuilder = function(settings,driver,type) { + + this.settings = settings || {}; + this.driver = driver || 'mysql'; + this.connection_type = type || 'standard'; + this.drivers = require('./drivers/drivers.json'); + this.driver_version = 'default'; + this.driver_info = null; + this.connection = null; + this.qb = null; + this.qe = null; + this.adapter = null; + + // **************************************************************************** + // Get information about the driver the user wants to use and modify QB object + // ----- + // @param Object qb The QueryBuilder object + // @return Object Modified QueryBuilder object + // **************************************************************************** + var get_driver_info = function(qb) { + // A driver must be specified + if (typeof driver !== 'string') { + throw new Error("No database driver specified!"); + } + + qb.driver = driver.toLowerCase(); + + // Verify that the driver is one we fundamentally support + if (Object.keys(qb.drivers).indexOf(qb.driver) === -1) { + throw new Error("Invalid driver specified!"); + } + + // Determine version of driver to use + if (qb.settings.hasOwnProperty('version') && (typeof qb.settings.version).match(/^(string|number)$/i)) { + qb.driver_version = qb.settings.version; + delete qb.settings.version; + } + + // Retrieve info about driver if available, error if not + if (Object.keys(qb.drivers[qb.driver].versions).indexOf(qb.driver_version) !== -1) { + qb.driver_info = qb.drivers[qb.driver].versions[qb.driver_version]; + } else { + throw new Error(qb.driver_version + " is not a version of the " + qb.driver + " driver that this library specifically supports. Try being more generic."); + } + + // Fail if specified driver is inactive + if (qb.driver_info.active === false) { + var err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) + + " of the " + qb.driver + " driver you are attempting to load is not currently available!"; + throw new Error(err); + } + }; + get_driver_info(this); + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object Modified QueryBuilder object + // **************************************************************************** + var get_query_builder = function(qb) { + try { + qb.qb = require(qb.driver_info.path + 'query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + qb.driver + ": " + e); + } + return qb; + }; + get_query_builder(this); + + // Non-Public QueryBuilder APIs + this._where = this.qb._where; + this._where_in = this.qb._where_in; + this._like = this.qb._like; + this._min_max_avg_sum = this.qb._min_max_avg_sum; + this._having = this.qb._having; + this._update = this.qb._update; + this.reset_query = this.qb.reset_query; + + // QueryBuilder Properties + this.where_array = this.qb.where_array; + this.where_in_array = this.qb.where_in_array; + this.from_array = this.qb.from_array; + this.join_array = this.qb.join_array; + this.select_array = this.qb.select_array; + this.set_array = this.qb.set_array; + this.order_by_array = this.qb.order_by_array; + this.group_by_array = this.qb.group_by_array; + this.having_array = this.qb.having_array; + this.limit_to = this.qb.limit_to; + this.offset_val = this.qb.offset_val; + this.join_clause = this.qb.join_clause; + this.last_query_string = this.qb.last_query_string; + this.distinct_clause = this.qb.distinct_clause; + this.aliased_tables = this.qb.aliased_tables; + + // QueryBuilder method mappings + this.where = this.qb.where; + this.or_where = this.qb.or_where; + this.where_in = this.qb.where_in; + this.or_where_in = this.qb.or_where_in; + this.where_not_in = this.qb.where_not_in; + this.or_where_not_in = this.qb.or_where_not_in; + this.like = this.qb.like; + this.not_like = this.qb.not_like; + this.or_like = this.qb.or_like; + this.or_not_like = this.qb.or_not_like; + this.from = this.qb.from; + this.join = this.qb.join; + this.select = this.qb.select; + this.select_min = this.qb.select_min; + this.select_max = this.qb.select_max; + this.select_avg = this.qb.select_avg; + this.select_sum = this.qb.select_sum; + this.distinct = this.qb.distinct; + this.group_by = this.qb.group_by; + this.having = this.qb.having; + this.or_having = this.qb.or_having; + this.order_by = this.qb.order_by; + this.limit = this.qb.limit; + this.offset = this.qb.offset; + this.set = this.qb.set; + this.get_compiled_select = this.qb.get_compiled_select; + this.get_compiled_insert = this.qb.get_compiled_insert; + this.get_compiled_update = this.qb.get_compiled_update; + this.get_compiled_delete = this.qb.get_compiled_delete; + this.last_query = this.qb._last_query; + + // **************************************************************************** + // Determine the type of connection (single, pool, cluster, etc...) + // ----- + // @param Object qb The QueryBuilder object + // @return Object Modified QueryBuilder object + // **************************************************************************** + var get_connection_type = function(qb) { + if (Object.keys(qb.drivers[qb.driver].connection_types).indexOf(qb.connection_type) === -1) { + throw new Error("You have specified a invalid database connection method."); + } + if (qb.drivers[qb.driver].connection_types[qb.connection_type] !== true) { + throw new Error("You cannot connect to a " + qb.driver + " database using the " + qb.connection_type + " connection type using this library."); + } + return qb; + } + get_connection_type(this); + + // **************************************************************************** + // Try to create a connection to the database using the driver's connection library + // ----- + // @param Object qb The QueryBuilder object + // @return Object Modified QueryBuilder object + // **************************************************************************** + var get_adapter = function(qb) { + try { + qb.adapter = require(qb.driver_info.path + 'connect.js').Adapter(qb.settings); + } catch(e) { + throw new Error("Could not connect to database: " + e); + } + + if (!qb.adapter.hasOwnProperty(qb.connection_type)) { + throw new Error('"' + qb.connection_type + '" is an invalid connection type for ' + qb.driver + '!'); + } + + // Create connection + qb.adapter[qb.connection_type](); + qb.adapter.get_connection(function(connection) { + this.connection = connection; + return qb; + }); + } + get_adapter(this); + + this.disconnect = this.adapter.disconnect; + this.destroy = this.adapter.destroy; + this.escape = this.adapter.escape; + this.get_connection_id = this.adapter.get_connection_id; + this.query = this.adapter.query; + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @return Object Modified QueryBuilder object + // **************************************************************************** + var get_query_exec = function(qb) { + try { + qb.qe = require(qb.driver_info.path + 'query_exec.js').QueryExec(qb.qb, qb.adapter); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + qb.driver + ": " + e); + } + return qb; + }; + get_query_exec(this); + + // QueryExecute method mappings: + this.count = this.qe.count; + this.get = this.qe.get; + this.get_where = this.qe.get_where; + this.insert = this.qe.insert; + this.insert_ignore = this.qe.insert_ignore; + this.insert_batch = this.qe.insert_batch; + this.update = this.qe.insert_batch; + this.update_batch = this.qe.update_batch; + this.delete = this.qe.delete; + this.empty_table = this.qe.empty_table; + this.truncate = this.qe.truncate; + + var that = this; return this; }; -exports.Adapter = Adapter; -exports.Pool = Pool; +exports.QueryBuilder = QueryBuilder; \ No newline at end of file diff --git a/index_old.js b/index_old.js new file mode 100755 index 0000000..7688bad --- /dev/null +++ b/index_old.js @@ -0,0 +1,379 @@ +/** + * MySQL ActiveRecord Adapter for Node.js + * (C) Martin Tajur 2011-2013 + * martin@tajur.ee + * + * Active Record Database Pattern implementation for use with node-mysql as MySQL connection driver. + * + * Dual licensed under the MIT and GPL licenses. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * +**/ +var Adapter = function(settings) { + var mysql = require('mysql'); + + this.qb = require('./lib/query_builder.js').QueryBuilder(); + + // Non-Public QueryBuilder APIs + this._where = this.qb._where; + this._where_in = this.qb._where_in; + this._like = this.qb._like; + this._min_max_avg_sum = this.qb._min_max_avg_sum; + this._having = this.qb._having; + this._update = this.qb._update; + this.reset_query = this.qb.reset_query; + + // QueryBuilder Properties + this.whereArray = this.qb.whereArray; + this.whereInArray = this.qb.whereInArray; + this.fromArray = this.qb.fromArray; + this.joinArray = this.qb.joinArray; + this.selectArray = this.qb.selectArray; + this.setArray = this.qb.setArray; + this.orderByArray = this.qb.orderByArray; + this.orderByDir = this.qb.orderByDir; + this.groupByArray = this.qb.groupByArray; + this.havingArray = this.qb.havingArray; + this.limitTo = this.qb.limitTo; + this.offsetVal = this.qb.offsetVal; + this.joinClause = this.qb.joinClause; + this.lastQueryString = this.qb.lastQueryString; + this.distinctClause = this.qb.distinctClause; + this.aliasedTables = this.qb.aliasedTables; + + // QueryBuilder method mappings for backwards compatibility + this.where = this.qb.where; + this.or_where = this.qb.or_where; + this.where_in = this.qb.where_in; + this.or_where_in = this.qb.or_where_in; + this.where_not_in = this.qb.where_not_in; + this.or_where_not_in = this.qb.or_where_not_in; + this.like = this.qb.like; + this.not_like = this.qb.not_like; + this.or_like = this.qb.or_like; + this.or_not_like = this.qb.or_not_like; + this.from = this.qb.from; + this.join = this.qb.join; + this.select = this.qb.select; + this.select_min = this.qb.select_min; + this.select_max = this.qb.select_max; + this.select_avg = this.qb.select_avg; + this.select_sum = this.qb.select_sum; + this.distinct = this.qb.distinct; + this.group_by = this.qb.group_by; + this.having = this.qb.having; + this.or_having = this.qb.or_having; + this.order_by = this.qb.order_by; + this.limit = this.qb.limit; + this.offset = this.qb.offset; + this.set = this.qb.set; + this.get_compiled_select = this.qb.get_compiled_select; + this.get_compiled_insert = this.qb.get_compiled_insert; + this.get_compiled_update = this.qb.get_compiled_update; + this.get_compiled_delete = this.qb.get_compiled_delete; + this._last_query = this.qb._last_query; + this.last_query = this.qb._last_query; + + // QueryBuilder "Query Execution" methods: + this.count = function(table, callback) { + var sql = this.qb.count(table); + + connection.query(sql, function(err, res) { + if (err) + callback(err, null); + else + callback(null, res[0]['count']); + }); + + return that; + }; + + this.get = function(table,callback) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); + } + + var sql = this.qb.get(table,callback); + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + this.get_where = function(table,where,callback) { + var sql = this.qb.get_where(table,where); + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + this.insert = function(table,set,callback,verb,suffix) { + var sql = this.qb.insert(table,set,callback,verb,suffix); + this.reset_query(sql); + connection.query(sql); + return that; + }; + + this.insert_ignore = function(table,set,callback) { + var sql = this.qb.insert_ignore(table,set,callback); + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + this.insert_batch = function(table,set,callback) { + var sql = this.qb.insert_batch(table,set,callback); + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + this.update = function(table,set,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'update' call!"); + } + + var sql = this.qb.update(table,set,where,callback); + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + // TODO: Write this complicated-ass function + this.update_batch = function() { + + return that; + }; + + this.delete = function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } + + var sql = this.delete(table, where); + + this.reset_query(sql); + connection.query(sql, callback); + return that; + }; + + this.empty_table - function(table, callback) { + var sql = this.qb.empty_table(table,callback); + this.reset_query(sql); + connection.query(sql); + return that; + }); + + this.truncate - function(table, callback) { + var sql = this.qb.truncate(table,callback); + this.reset_query(sql); + connection.query(sql); + return that; + }); + + var initializeConnectionSettings = function () { + if(settings.server) { + settings.host = settings.server; + } + if(settings.username) { + settings.user = settings.username; + } + + if (!settings.host) { + throw new Error('Unable to start ActiveRecord - no server given.'); + } + if (!settings.port) { + settings.port = 3306; + } + if (!settings.user) { + settings.user = ''; + } + if (!settings.password) { + settings.password = ''; + } + if (!settings.database) { + throw new Error('Unable to start ActiveRecord - no database given.'); + } + + return settings; + }; + + var connection; + var connectionSettings; + var pool; + + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } else { + connectionSettings = initializeConnectionSettings(); + connection = new mysql.createConnection(connectionSettings); + } + + if (settings.charset) { + connection.query('SET NAMES ' + settings.charset); + } + + this.connectionSettings = function() { return connectionSettings; }; + this.connection = function() { return connection; }; + + this.query = function(sqlQueryString, responseCallback) { + connection.query(sqlQueryString, responseCallback); + this.reset_query(sqlQueryString); + return that; + }; + + this.disconnect = function() { + return connection.end(); + }; + + this.forceDisconnect = function() { + return connection.destroy(); + }; + + this.releaseConnection = function() { + pool.releaseConnection(connection); + }; + + this.releaseConnection = function() { + pool.releaseConnection(connection); + }; + + this.ping = function() { + connection.ping(); + return that; + }; + + this.escape = function(str) { + return connection.escape(str); + }; + + var reconnectingTimeout = false; + + function handleDisconnect(connectionInstance) { + connectionInstance.on('error', function(err) { + if (!err.fatal || reconnectingTimeout) { + return; + } + + if (err.code !== 'PROTOCOL_CONNECTION_LOST' && err.code !== 'ECONNREFUSED') { + throw err; + } + + var reconnectingTimeout = setTimeout(function() { + connection = mysql.createConnection(connectionInstance.config); + handleDisconnect(connection); + connection.connect(); + }, 2000); + }); + } + + if (!pool) { + handleDisconnect(connection); + } + + var that = this; + + return this; +}; + +var mysqlPool; // this should be initialized only once. +var mysqlCharset; + +var Pool = function (settings) { + if (!mysqlPool) { + var mysql = require('mysql'); + + var poolOption = { + createConnection: settings.createConnection, + waitForConnections: settings.waitForConnections, + connectionLimit: settings.connectionLimit, + queueLimit: settings.queueLimit + }; + Object.keys(poolOption).forEach(function (element) { + // Avoid pool option being used by mysql connection. + delete settings[element]; + // Also remove undefined elements from poolOption + if (!poolOption[element]) { + delete poolOption[element]; + } + }); + + // Confirm settings with Adapter. + var db = new Adapter(settings); + var connectionSettings = db.connectionSettings(); + + Object.keys(connectionSettings).forEach(function (element) { + poolOption[element] = connectionSettings[element]; + }); + + mysqlPool = mysql.createPool(poolOption); + mysqlCharset = settings.charset; + } + + this.pool = function () { + return mysqlPool; + }; + + this.getNewAdapter = function (responseCallback) { + mysqlPool.getConnection(function (err, connection) { + if (err) { + throw err; + } + var adapter = new Adapter({ + pool: { + pool: mysqlPool, + enabled: true, + connection: connection + }, + charset: mysqlCharset + }); + responseCallback(adapter); + }); + }; + + this.disconnect = function (responseCallback) { + this.pool().end(responseCallback); + }; + + return this; +}; + +exports.Adapter = Adapter; +exports.Pool = Pool; diff --git a/package.json b/package.json index 1bca5e7..572226e 100755 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name" : "mysql-activerecord", - "version": "0.8.4", - "author": "Martin Tajur ", - "description": "MySQL ActiveRecord pattern implementation on top of the mysql module.", + "name" : "node-querybuilder", + "version": "0.9.0", + "author": "Kyle Farris ", + "description": "MySQL QueryBuilder pattern implementation on top of the mysql module.", "scripts": { "test": "make test" }, @@ -12,12 +12,12 @@ "url": "git://github.com/martintajur/node-mysql-activerecord.git" }, "contributors": [ + "Martin Tajur ", "Daniel Bretoi ", - "Kyle Farris ", "Daehyub Kim " ], "dependencies": { - "mysql": "~2.1", + "mysql": "2.5.*", "chai": "~1.4.2", "mocha": "*" }, diff --git a/test/test-distinct.js b/test/mysql/test-distinct.js similarity index 87% rename from test/test-distinct.js rename to test/mysql/test-distinct.js index d50ba95..73ac857 100755 --- a/test/test-distinct.js +++ b/test/mysql/test-distinct.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('distinct()', function() { diff --git a/test/test-limit.js b/test/mysql/test-limit.js similarity index 98% rename from test/test-limit.js rename to test/mysql/test-limit.js index 7da49a0..18a4690 100755 --- a/test/test-limit.js +++ b/test/mysql/test-limit.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('limit()', function() { diff --git a/test/test-offset.js b/test/mysql/test-offset.js similarity index 97% rename from test/test-offset.js rename to test/mysql/test-offset.js index dcc658c..65fb69a 100755 --- a/test/test-offset.js +++ b/test/mysql/test-offset.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('offset()', function() { diff --git a/test/tests-delete.js b/test/mysql/tests-delete.js similarity index 98% rename from test/tests-delete.js rename to test/mysql/tests-delete.js index 2c2eb18..2f0c75f 100755 --- a/test/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('delete()', function() { diff --git a/test/tests-from.js b/test/mysql/tests-from.js similarity index 98% rename from test/tests-from.js rename to test/mysql/tests-from.js index f5a2755..deda48d 100755 --- a/test/tests-from.js +++ b/test/mysql/tests-from.js @@ -1,5 +1,5 @@ var should = require('chai').should(); -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('from()', function() { diff --git a/test/tests-general.js b/test/mysql/tests-general.js similarity index 73% rename from test/tests-general.js rename to test/mysql/tests-general.js index 9562139..bae6e68 100755 --- a/test/tests-general.js +++ b/test/mysql/tests-general.js @@ -1,7 +1,7 @@ var should = require('chai').should(); describe('QueryBuilder', function() { - var QueryBuilder = require('../lib/query_builder.js'); + var QueryBuilder = require('../../drivers/mysql/query_builder.js'); it('actually exists and can be initialized', function() { var qb = new QueryBuilder(); diff --git a/test/tests-groupby.js b/test/mysql/tests-groupby.js similarity index 97% rename from test/tests-groupby.js rename to test/mysql/tests-groupby.js index fab17f2..d12638f 100755 --- a/test/tests-groupby.js +++ b/test/mysql/tests-groupby.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('group_by()', function() { diff --git a/test/tests-having.js b/test/mysql/tests-having.js similarity index 98% rename from test/tests-having.js rename to test/mysql/tests-having.js index 9b7ac8e..7a28f01 100755 --- a/test/tests-having.js +++ b/test/mysql/tests-having.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('having()', function() { diff --git a/test/tests-insert.js b/test/mysql/tests-insert.js similarity index 99% rename from test/tests-insert.js rename to test/mysql/tests-insert.js index b99b80c..4965b27 100755 --- a/test/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); var test_data = {id:3, name:'Milky Way', type: 'spiral'}; diff --git a/test/tests-join.js b/test/mysql/tests-join.js similarity index 99% rename from test/tests-join.js rename to test/mysql/tests-join.js index dfa1183..46c57b2 100755 --- a/test/tests-join.js +++ b/test/mysql/tests-join.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; - -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('join()', function() { diff --git a/test/tests-like.js b/test/mysql/tests-like.js similarity index 97% rename from test/tests-like.js rename to test/mysql/tests-like.js index 076d8c8..801e89e 100755 --- a/test/tests-like.js +++ b/test/mysql/tests-like.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('like()', function() { diff --git a/test/tests-orderby.js b/test/mysql/tests-orderby.js similarity index 98% rename from test/tests-orderby.js rename to test/mysql/tests-orderby.js index fa5a11b..3fa4482 100755 --- a/test/tests-orderby.js +++ b/test/mysql/tests-orderby.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('order_by()', function() { diff --git a/test/tests-select.js b/test/mysql/tests-select.js similarity index 99% rename from test/tests-select.js rename to test/mysql/tests-select.js index dab2ca5..76aa593 100755 --- a/test/tests-select.js +++ b/test/mysql/tests-select.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('select()', function() { diff --git a/test/tests-set.js b/test/mysql/tests-set.js similarity index 98% rename from test/tests-set.js rename to test/mysql/tests-set.js index ce35208..38aa0a0 100755 --- a/test/tests-set.js +++ b/test/mysql/tests-set.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('set()', function() { diff --git a/test/tests-where.js b/test/mysql/tests-where.js similarity index 99% rename from test/tests-where.js rename to test/mysql/tests-where.js index 5cab369..4939f04 100755 --- a/test/tests-where.js +++ b/test/mysql/tests-where.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('where()', function() { diff --git a/test/tests-where_in.js b/test/mysql/tests-where_in.js similarity index 99% rename from test/tests-where_in.js rename to test/mysql/tests-where_in.js index b228d96..8eb468a 100755 --- a/test/tests-where_in.js +++ b/test/mysql/tests-where_in.js @@ -1,6 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../lib/query_builder.js'); +var QueryBuilder = require('../../drivers/mysql/query_builder.js'); var qb = new QueryBuilder(); describe('where_in()', function() { From 1c44b9fc362c4827f873c4616172b6217f858bdf Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 6 Oct 2014 16:37:43 -0400 Subject: [PATCH 028/200] A lot of documentation written. --- README.md | 334 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 305 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index b2daffc..e4af5b1 100755 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ The primary benefits of this module (currently) are: * Ligher-weight than an ORM * Allows you to drop down to the native methods of your driver if you choose * Allows for different drivers for different versions (SQLite 2 vs SQLite 3) + * The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. Database Drivers ================= @@ -103,7 +104,7 @@ The best way to store these options is in a JSON file outsite of your web root w **Example JSON File** -We'll call this `db.json` (you can also just have a normal javascript object directly within your code somwhere if you're risky like that). +We'll call this `db.json`. ```javascript { @@ -122,6 +123,20 @@ var settings = require('db.json'); var qb = require('node-querybuilder').QueryBuilder(settings); ``` +Of course you can also just have a normal javascript object directly within your code somwhere if you're honing your inner Chuck Norris: + +**Chuck Norris App** + +```javascript +var qb = require('node-querybuilder').QueryBuilder({ + host: 'db.myserver.com', + user: 'myusername', + password: 'P@s$w0rD', + database: 'MyDB', + pool_size: 50 +}); +``` + Choosing the Database Type -------------------------- @@ -139,12 +154,12 @@ Choosing the Connection Type This library currently supports 3 connection methods: * standard (default) - * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications buy might be quite suitable for command line scripts and the like. + * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. * **All drivers must have this connection type**. * pool - * This will utilize the driver's connection pooling capabilities if it is provided. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. + * This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. * cluster - * When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. + * When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. This is ideal for high-traffic web sites and applications that utilize a farm of database servers as opposed to just one. **Note:** You will specify the type of connection as the third parameter to the contructor @@ -159,27 +174,53 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); API Methods =============================== - * SELECT - * DISTINCT - * FROM - * IN - * MIN - * MAX - * AVG - * SUM - * UPDATE - * INSERT (single-row and multi-row) - * INSERT IGNORE - * DELETE - * JOIN - * LIMIT and OFFSET - * ORDER BY - * GROUP BY - * COUNT - * HAVING - -Methods -------- +SQL Commands +------------- + +| SQL Command | API Method | Notes | +| :------------ | :---------------- | :---- | +| SELECT | [select()](#select) | | +| DISTINCT | [distinct()](#distinct) | | +| MIN | [select_min()](#select_min) | | +| MAX | [select_max()](#select_max) | | +| AVG | [select_avg()](#select_avg) | | +| SUM | [select_sum()](#select_sum) | | +| FROM | [from()](#from) | | +| JOIN | [join()](#join) | | +| WHERE | [where()](#where) | | +| IN | [where_in()](#where_in) | | +| GROUP BY | [group_by()](#group_by) | | +| HAVING | [having()](#having) | | +| ORDER BY | [order_by()](#order_by) | | +| LIMIT | [limit()](#limit) | | +| OFFSET | [offset()](#offset) | | +| COUNT | [count()](#count) | | +| SET | [set()](#set) | | +| UPDATE | [update()](#update) | | +| INSERT | [insert()](#insert) | single-row and multi-row | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | | +| DELETE | [delete()](#delete) | | + +Library-Specific Methods +------------------------ + +* get() +* count_all() +* get_where() +* where_not_in() +* or_where() +* or_where_in() +* or_where_not_in() +* or_like() +* or_not_like() +* not_like() +* or_having() +* count_all_results() +* insert_batch() +* update_batch() +* query() +* last_query() + ### SELECT @@ -187,13 +228,13 @@ This method is used to specify the fields to pull into the resultset when runnin | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :-------------------------------------------- | -| fields | String|Array | N/A | The fields in which to grab from the database | +| fields | String/Array | N/A | The fields in which to grab from the database | | escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | #### .select(fields) -The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways: +The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): * String with fields seperated by a comma: * `.select('foo, bar, baz')` @@ -228,8 +269,243 @@ You can optionally choose not to have the driver auto-escape the fieldnames (dan **Example** ```javascript -// SELECT MAX(id) as `max_id` -qb.select('MAX(id) as `max_id`',false); +// SELECT MAX(id) AS `max_id` +qb.select('MAX(id) AS `max_id`',false); +``` + +***NOTE:*** This example is contrived and can be achieved more-easily using the `.select_max()` method described below. + +### DISTINCT + +This SQL command is used to prevent duplicate rows from being returned in the resultset at the database level. + +***This method takes no parameters*** + +#### .distinct() + +This should only be used when querying data (execution method: get()/get_where()) (not inserting, updating or removing). If it's provided to another execution method, it will simply be ignored. + +**Example** + +```javascript +// SELECT DISTINCT `id`, `name`, `description` FROM `users` +qb.distinct().select('id,name,description').get('users',callback); +``` + +### MIN + +This SQL command is used to find the minimum value for a specific field within a resultset. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the minimum value of | +| alias | String | NULL | Optional alias to rename field | + +#### .select_min(field) + +**Examples** + +```javascript +// SELECT MIN(`age`) FROM `users` +qb.select_min('age').get('users',callback); +``` + +You can optionally include a second parameter to rename the resulting field + +```javascript +// SELECT MIN(`age`) AS `min_age` FROM `users` +qb.select_min('age','min_age').get('users',callback); +``` + +### MAX + +This SQL command is used to find the maximum value for a specific field within a resultset. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the maximum value of | +| alias | String | NULL | Optional alias to rename field | + +#### .select_max(field) + +**Examples** + +```javascript +// SELECT MAX(`age`) FROM `users` +qb.select_max('age').get('users',callback); +``` + +You can optionally include a second parameter to rename the resulting field + +```javascript +// SELECT MAX(`age`) AS `max_age` FROM `users` +qb.select_max('age','max_age').get('users',callback); +``` + +### AVG + +This SQL command is used to find the average value for a specific field within a resultset. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the average value of | +| alias | String | NULL | Optional alias to rename field | + +#### .select_avg(field) + +**Examples** + +```javascript +// SELECT AVG(`age`) FROM `users` +qb.select_avg('age').get('users',callback); +``` + +You can optionally include a second parameter to rename the resulting field + +```javascript +// SELECT AVG(`age`) AS `avg_age` FROM `users` +qb.select_avg('age','avg_age').get('users',callback); +``` + + +### SUM + +This SQL command is used to find the minimum value for a specific field within a result set. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the minimum value of | +| alias | String | NULL | Optional alias to rename field | + +#### .select_sum(field) + +**Examples** + +```javascript +// SELECT SUM(`age`) FROM `users` +qb.select_sum('age').get('users',callback); +``` + +You can optionally include a second parameter to rename the resulting field + +```javascript +// SELECT SUM(`age`) AS `sum_age` FROM `users` +qb.select_sum('age','sum_age').get('users',callback); +``` + +### FROM + +This SQL command is used to determine which sources, available to the active connection, to obtain data from. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------- | +| tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | + +#### .from(tables) + +You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). + +Aliases can be provided and they will be escaped properly. + +**Examples** + +***Basic*** + +```javascript +// SELECT `id`, `name`, `description` FROM `users` +qb.select('id,name,description').from('users').get(callback); +``` + +***Comma-Seperated*** + +```javascript +// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` +// FROM (`users` `u`, `groups` `g`) +qb.select('u.id,u.name,u,description,g.name as group_name') + .from('users u, groups g') + .get(callback); +``` + +***Array of Tables*** + +```javascript +// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` +// FROM (`users` `u`, `groups` `g`) +qb.select('u.id,u.name,u,description,g.name as group_name') + .from(['users u', 'groups g']) + .get(callback); +``` + +***Multiple From Calls*** + +```javascript +// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` +// FROM (`users` `u`, `groups` `g`) +qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') + .from('users u') + .get(callback); +``` + +### JOIN + +This SQL command is used query multiple tables related and connected by keys and get a single resultset. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------------------- | +| table | String | Required | The table or view to join to. | +| relation | String | Required | The "ON" statement that relates to table together | +| direction | String | "left" | Direction of the join (see join types list below) | + +**Join Types** + +* left +* right +* outer +* inner +* left outer +* right outer + + +#### .join(table,relation) + +The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. + +**Examples** + +If no direction is specified, "left" will be used: + +```javascript +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` +// FROM `users` `u` +// LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` +qb.select('u.id,u.name,t.name as type_name').from('users u') + .join('types t','t.id=u.type_id') + .get(callback); +``` + +You may specify a direction: + +```javascript +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` +// FROM `users` `u` +// RIGHT OUTER JOIN `types` `t` ON `t`.`id`=`u`.`type_id` +qb.select('u.id,u.name,t.name as type_name').from('users u') + .join('types t','t.id=u.type_id','right outer') + .get(callback); +``` + +Multiple function calls can be made if you need several joins in one query: + +```javascript +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type`, `l`.`name` AS `location` +// FROM `users` `u` +// LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` +// LEFT JOIN `locations` `l` ON `l`.`id`=`u`.`location_id` +var select = ['u.id','u.name','t.name as type','l.name as location']; +qb.select(select).from('users u') + .join('types t','t.id=u.type_id','right outer') + .join('locations l','l.id=u.location_id','left') + .get(callback); ``` Contribute From 385d638f226a0292177dac465f9298990f3cde65 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 6 Oct 2014 16:50:00 -0400 Subject: [PATCH 029/200] More documentation changes/additions. --- README.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e4af5b1..96f0bb9 100755 --- a/README.md +++ b/README.md @@ -177,29 +177,31 @@ API Methods SQL Commands ------------- -| SQL Command | API Method | Notes | -| :------------ | :---------------- | :---- | -| SELECT | [select()](#select) | | -| DISTINCT | [distinct()](#distinct) | | -| MIN | [select_min()](#select_min) | | -| MAX | [select_max()](#select_max) | | -| AVG | [select_avg()](#select_avg) | | -| SUM | [select_sum()](#select_sum) | | -| FROM | [from()](#from) | | -| JOIN | [join()](#join) | | -| WHERE | [where()](#where) | | -| IN | [where_in()](#where_in) | | -| GROUP BY | [group_by()](#group_by) | | -| HAVING | [having()](#having) | | -| ORDER BY | [order_by()](#order_by) | | -| LIMIT | [limit()](#limit) | | -| OFFSET | [offset()](#offset) | | -| COUNT | [count()](#count) | | -| SET | [set()](#set) | | -| UPDATE | [update()](#update) | | -| INSERT | [insert()](#insert) | single-row and multi-row | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | | -| DELETE | [delete()](#delete) | | +***NOTE:*** This table is subject to change + +| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :------------ | :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | +| SELECT | [select()](#select) | x | x | x | x | x | | +| DISTINCT | [distinct()](#distinct) | x | x | x | | x | | +| MIN | [select_min()](#min) | x | x | x | | x | | +| MAX | [select_max()](#max) | x | x | x | | x | | +| AVG | [select_avg()](#avg) | x | x | x | | x | | +| SUM | [select_sum()](#sum) | x | x | x | | x | | +| FROM | [from()](#from) | x | x | x | x | x | | +| JOIN | [join()](#join) | x | x | x | | x | | +| WHERE | [where()](#where) | x | x | x | x | x | | +| IN | [where_in()](#where_in) | x | x | x | x | x | | +| GROUP BY | [group_by()](#group_by) | x | x | x | x | x | | +| HAVING | [having()](#having) | x | x | x | x | x | | +| ORDER BY | [order_by()](#order_by) | x | x | x | x | x | | +| LIMIT | [limit()](#limit) | x | x | x | x | x | | +| OFFSET | [offset()](#offset) | x | x | x | x | x | | +| COUNT | [count()](#count) | x | x | x | x | x | | +| SET | [set()](#set) | x | x | x | x | x | | +| UPDATE | [update()](#update) | x | x | x | x | x | | +| INSERT | [insert()](#insert) | x | x | x | x | x | | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | x | x | x | | x | | +| DELETE | [delete()](#delete) | x | x | x | x | x | | Library-Specific Methods ------------------------ From e8462f3914827d9bed62e7d1bd827587f54639c2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 7 Oct 2014 13:18:36 -0400 Subject: [PATCH 030/200] Fixed some bugs in testing and writing documentation as well as added some additional documentation and refined the format of the documentation. --- README.md | 150 +++++++++++++++++++++------------ drivers/mysql/query_builder.js | 78 ++++++++++++++--- test/mysql/test-distinct.js | 5 +- test/mysql/test-limit.js | 25 +++--- test/mysql/test-offset.js | 15 ++-- test/mysql/tests-delete.js | 3 +- test/mysql/tests-from.js | 3 +- test/mysql/tests-general.js | 6 +- test/mysql/tests-groupby.js | 3 +- test/mysql/tests-having.js | 3 +- test/mysql/tests-insert.js | 3 +- test/mysql/tests-join.js | 3 +- test/mysql/tests-like.js | 3 +- test/mysql/tests-orderby.js | 3 +- test/mysql/tests-select.js | 3 +- test/mysql/tests-set.js | 5 +- test/mysql/tests-where.js | 8 +- test/mysql/tests-where_in.js | 3 +- 18 files changed, 203 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 96f0bb9..8e1693e 100755 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The primary benefits of this module (currently) are: * Allows you to drop down to the native methods of your driver if you choose * Allows for different drivers for different versions (SQLite 2 vs SQLite 3) * The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. + * Can used as a learning tool/Rosetta stone Database Drivers ================= @@ -174,54 +175,56 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); API Methods =============================== +***NOTE:*** The compatibility portions of these tables are subject to change as features and drivers are written! + SQL Commands ------------- -***NOTE:*** This table is subject to change - | SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | | :------------ | :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | -| SELECT | [select()](#select) | x | x | x | x | x | | -| DISTINCT | [distinct()](#distinct) | x | x | x | | x | | -| MIN | [select_min()](#min) | x | x | x | | x | | -| MAX | [select_max()](#max) | x | x | x | | x | | -| AVG | [select_avg()](#avg) | x | x | x | | x | | -| SUM | [select_sum()](#sum) | x | x | x | | x | | -| FROM | [from()](#from) | x | x | x | x | x | | -| JOIN | [join()](#join) | x | x | x | | x | | -| WHERE | [where()](#where) | x | x | x | x | x | | -| IN | [where_in()](#where_in) | x | x | x | x | x | | -| GROUP BY | [group_by()](#group_by) | x | x | x | x | x | | -| HAVING | [having()](#having) | x | x | x | x | x | | -| ORDER BY | [order_by()](#order_by) | x | x | x | x | x | | -| LIMIT | [limit()](#limit) | x | x | x | x | x | | -| OFFSET | [offset()](#offset) | x | x | x | x | x | | -| COUNT | [count()](#count) | x | x | x | x | x | | -| SET | [set()](#set) | x | x | x | x | x | | -| UPDATE | [update()](#update) | x | x | x | x | x | | -| INSERT | [insert()](#insert) | x | x | x | x | x | | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | x | x | x | | x | | -| DELETE | [delete()](#delete) | x | x | x | x | x | | +| SELECT | [select()](#select) | x | | | | | | +| DISTINCT | [distinct()](#distinct) | x | | | | | | +| MIN | [select_min()](#min) | x | | | | | | +| MAX | [select_max()](#max) | x | | | | | | +| AVG | [select_avg()](#avg) | x | | | | | | +| SUM | [select_sum()](#sum) | x | | | | | | +| FROM | [from()](#from) | x | | | | | | +| JOIN | [join()](#join) | x | | | | | | +| WHERE | [where()](#where) | x | | | | | | +| IN | [where_in()](#where_in) | x | | | | | | +| GROUP BY | [group_by()](#group_by) | x | | | | | | +| HAVING | [having()](#having) | x | | | | | | +| ORDER BY | [order_by()](#order_by) | x | | | | | | +| LIMIT | [limit()](#limit) | x | | | | | | +| OFFSET | [offset()](#offset) | x | | | | | | +| COUNT | [count()](#count) | x | | | | | | +| SET | [set()](#set) | x | | | | | | +| UPDATE | [update()](#update) | x | | | | | | +| INSERT | [insert()](#insert) | x | | | | | | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | x | | | | | | +| DELETE | [delete()](#delete) | x | | | | | | Library-Specific Methods ------------------------ -* get() -* count_all() -* get_where() -* where_not_in() -* or_where() -* or_where_in() -* or_where_not_in() -* or_like() -* or_not_like() -* not_like() -* or_having() -* count_all_results() -* insert_batch() -* update_batch() -* query() -* last_query() +| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | +| [get()](#get) | x | | | | | | +| [get_where()](#get_where) | x | | | | | | +| [count_all()](#count_all) | x | | | | | | +| [where_not_in()](#where_not_in) | x | | | | | | +| [or_where()](#or_where) | x | | | | | | +| [or_where_in()](#or_where_in) | x | | | | | | +| [or_where_not_in()](#or_where_not_in) | x | | | | | | +| [or_like()](#()](#) | x | | | | | | +| [or_not_like()](#or_not_like) | x | | | | | | +| [not_like()](#not_like) | x | | | | | | +| [or_having()](#or_having) | x | | | | | | +| [count_all_results()](#count_all_results) | x | | | | | | +| [insert_batch()](#insert_batch) | x | | | | | | +| [update_batch()](#update_batch) | x | | | | | | +| [query()](#query) | x | | | | | | +| [last_query()](#last_query) | x | | | | | | ### SELECT @@ -234,7 +237,7 @@ This method is used to specify the fields to pull into the resultset when runnin | escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | -#### .select(fields) +#### .select() The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): @@ -264,12 +267,8 @@ You can alias your field names and they will be escaped properly as well: qb.select(['foo as f','bar as b','baz as z']); ``` -#### .select(fields,escape) - You can optionally choose not to have the driver auto-escape the fieldnames (dangerous, but useful if you a function in your select statement, for instance): -**Example** - ```javascript // SELECT MAX(id) AS `max_id` qb.select('MAX(id) AS `max_id`',false); @@ -303,7 +302,7 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_min(field) +#### .select_min() **Examples** @@ -328,7 +327,7 @@ This SQL command is used to find the maximum value for a specific field within a | field | String | Required | The field to get the maximum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_max(field) +#### .select_max() **Examples** @@ -353,7 +352,7 @@ This SQL command is used to find the average value for a specific field within a | field | String | Required | The field to get the average value of | | alias | String | NULL | Optional alias to rename field | -#### .select_avg(field) +#### .select_avg() **Examples** @@ -379,7 +378,7 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_sum(field) +#### .select_sum() **Examples** @@ -403,12 +402,14 @@ This SQL command is used to determine which sources, available to the active con | :-------- | :-------- | :----- | :-------------------------------------------- | | tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | -#### .from(tables) +#### .from() You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). Aliases can be provided and they will be escaped properly. +***NOTE:*** You can also pass table/view names into the `.get()` and `.get_where()` methods and forego this method entirely. + **Examples** ***Basic*** @@ -455,7 +456,7 @@ This SQL command is used query multiple tables related and connected by keys and | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :-------------------------------------------------------- | | table | String | Required | The table or view to join to. | -| relation | String | Required | The "ON" statement that relates to table together | +| relation | String | Required | The "ON" statement that relates two tables together | | direction | String | "left" | Direction of the join (see join types list below) | **Join Types** @@ -467,8 +468,7 @@ This SQL command is used query multiple tables related and connected by keys and * left outer * right outer - -#### .join(table,relation) +#### .join() The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. @@ -510,6 +510,52 @@ qb.select(select).from('users u') .get(callback); ``` +### WHERE + +This SQL command is used to limit the resultset based on filters. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| field/filters | String/Object | Required | A field name, a WHERE clause, or an object of key/value pairs | +| value | Mixed | N/A | When the first parameter is a field name, this is the value | +| escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping | + + +#### .where() + +This method can be called in many different ways depending on your style and the format of the data that you have at the time of execution. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. + +**Examples** + +If you just want to pass a single filter at a time: + +```javascript +// SELECT `galaxy` FROM `universe` WHERE `planet_name` = 'Earth' +qb.select('galaxy').where('planet_name','Earth').get('universe',callback); +``` + +If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter: + +```javascript +// SELECT `planet` FROM `planets` WHERE `order` <= 3 +qb.select('planet').where('order <=',3).get('planets',callback); +``` + +You can conveniently pass an object of key:value pairs (which can also contain custom operators): + +```javascript +// SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' +qb.select('planet').where({'order <=':'3',class:'M'}).get('planets',callback); +``` + +You can construct complex WHERE clauses manually (however, this may cause conflicts between database drivers): + +```javascript +// SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' +qb.select('planet').where("order <= 3 AND class = 'M'").get('planets',callback); +``` + + Contribute ========== diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 66087ac..967f7e7 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -55,10 +55,10 @@ var QueryBuilder = function() { var extract_having_parts = function(key,key_array) { var m; key = key.trim().replace(/\s+/g,' '); - var str_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - var sstr_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - var num_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - var bool_condition = /^([^\s]+\s(<|>|<=|>=|<>|=|!=))+\s((true|false)+)$/; + var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; if (m = str_condition.exec(key)) { key_array[m[1]] = m[3]; @@ -87,8 +87,27 @@ var QueryBuilder = function() { } // Simply setting all properties to [] causes reference issues in the parent class. - var clear_array = function(a) { - while (a.length) { a.pop(); } + var clear_array = function(a,debug) { + if (debug === true) { + console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { + for (var key in a) { + if (a.hasOwnProperty(key)) { + delete a[key]; + } + } + } + else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { + while (a.length > 0) { + a.pop(); + } + } + if (debug === true) { + console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } }; // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // @@ -415,7 +434,7 @@ var QueryBuilder = function() { from_array: [], join_array: [], select_array: [], - set_array: [], + set_array: {}, order_by_array: [], group_by_array: [], having_array: [], @@ -426,8 +445,8 @@ var QueryBuilder = function() { distinct_clause: [], // has to be array to work as reference aliased_tables: [], - reset_query: function(new_last_query) { - clear_array(this.where_array); + reset_query: function(new_last_query,debug) { + clear_array(this.where_array,debug); clear_array(this.where_in_array); clear_array(this.from_array); clear_array(this.join_array); @@ -462,11 +481,10 @@ var QueryBuilder = function() { return this._where(key, value, 'AND ', escape); }, - or_where: function(key, value, isRaw) { - isRaw = (typeof isRaw === 'boolean' ? isRaw : false); + or_where: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); value = value || null; - var escape = (isRaw ? false : true); if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { return this._where_in(key, value, false, 'OR '); } @@ -478,14 +496,46 @@ var QueryBuilder = function() { type = type || 'AND '; escape = (typeof escape === 'boolean' ? escape : true); + // Must be an object or a string if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string if (typeof key !== 'string') { throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); } else { + // If it is a string, it can't be an empty one if (key.length == 0) { - throw new Error("where(): Invalid field name provided."); + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { + var filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + var that = this; + var parse_statement = function(statement,joiner) { + var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || joiner.match(/AND/i)) { + that.where(key, value, true); + } else { + that.or_where(key, value, true); + } + } + }; + parse_statement(filters.shift(),null); + while (filters.length > 0) { + var joiner = filters.shift(); + var statement = filters.shift(); + parse_statement(statement, joiner); + } + return this; } } + var key_array = {}; key_array[key] = value; key = key_array; @@ -1070,7 +1120,7 @@ var QueryBuilder = function() { }, offset: function(offset) { - clear_array(this.limit_to); + clear_array(this.offset_val); this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); return this; }, diff --git a/test/mysql/test-distinct.js b/test/mysql/test-distinct.js index 73ac857..da55495 100755 --- a/test/mysql/test-distinct.js +++ b/test/mysql/test-distinct.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('distinct()', function() { it('should exist', function() { @@ -13,6 +12,6 @@ describe('distinct()', function() { it('should override the default distinct_clause with the "DISTINCT " keyword', function() { qb.reset_query(); qb.distinct(); - qb.distinct_clause.should.eql('DISTINCT '); + qb.distinct_clause.should.eql(['DISTINCT ']); }); }); \ No newline at end of file diff --git a/test/mysql/test-limit.js b/test/mysql/test-limit.js index 18a4690..f6ff94f 100755 --- a/test/mysql/test-limit.js +++ b/test/mysql/test-limit.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('limit()', function() { it('should exist', function() { @@ -14,7 +13,7 @@ describe('limit()', function() { qb.should.have.property('limit_to'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.limit_to.should.be.false; + qb.limit_to.should.be.empty; }); it('should require an integer (or integer in string form) in first parameter', function() { qb.reset_query(); @@ -52,36 +51,36 @@ describe('limit()', function() { it('should override the default limit_to value when a limit is provided', function() { qb.reset_query(); qb.limit(10); - qb.limit_to.should.eql(10); + qb.limit_to.should.eql([10]); }); it('should override the default limit_to and offset_val values when a limit and an offset are provided', function() { qb.reset_query(); qb.limit(10,20); - qb.limit_to.should.eql(10); - qb.offset_val.should.eql(20); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([20]); }); it('should trim string values that are provided', function() { qb.reset_query(); qb.limit('10 '); - qb.limit_to.should.eql(10); + qb.limit_to.should.eql([10]); }); it('should trim string values that are provided', function() { qb.reset_query(); qb.limit(' 10 ',' 12'); - qb.limit_to.should.eql(10); - qb.offset_val.should.eql(12); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([12]); }); it('should override values set by any previous calls to itself', function() { qb.reset_query(); qb.limit(10); - qb.limit_to.should.eql(10); + qb.limit_to.should.eql([10]); qb.limit(20); - qb.limit_to.should.eql(20); + qb.limit_to.should.eql([20]); }); it('should be chainable whereby the last call to the method will contain the value(s) used', function() { qb.reset_query(); qb.limit(10,5).limit(20).limit(100,30); - qb.limit_to.should.eql(100); - qb.offset_val.should.eql(30); + qb.limit_to.should.eql([100]); + qb.offset_val.should.eql([30]); }); }); \ No newline at end of file diff --git a/test/mysql/test-offset.js b/test/mysql/test-offset.js index 65fb69a..9b13290 100755 --- a/test/mysql/test-offset.js +++ b/test/mysql/test-offset.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('offset()', function() { it('should exist', function() { @@ -14,7 +13,7 @@ describe('offset()', function() { qb.should.have.property('offset_val'); }); it('should have an empty array to put fields into at the beginning', function() { - qb.offset_val.should.be.false; + qb.offset_val.should.be.empty; }); it('should require an integer (or integer in string form) in first parameter', function() { qb.reset_query(); @@ -36,25 +35,25 @@ describe('offset()', function() { it('should override the default offset_val value when a offset is provided', function() { qb.reset_query(); qb.offset(10); - qb.offset_val.should.eql(10); + qb.offset_val.should.eql([10]); }); it('should trim string values that are provided', function() { qb.reset_query(); qb.offset('10 '); qb.offset(' 10'); qb.offset(' 10 '); - qb.offset_val.should.eql(10); + qb.offset_val.should.eql([10]); }); it('should override values set by any previous calls to itself', function() { qb.reset_query(); qb.offset(10); - qb.offset_val.should.eql(10); + qb.offset_val.should.eql([10]); qb.offset(20); - qb.offset_val.should.eql(20); + qb.offset_val.should.eql([20]); }); it('should be chainable whereby the last call to the method will contain the value used', function() { qb.reset_query(); qb.offset(10).offset(20).offset(100); - qb.offset_val.should.eql(100); + qb.offset_val.should.eql([100]); }); }); \ No newline at end of file diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 2f0c75f..60fa86c 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('delete()', function() { it('should exist', function() { diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js index deda48d..56da175 100755 --- a/test/mysql/tests-from.js +++ b/test/mysql/tests-from.js @@ -1,6 +1,5 @@ var should = require('chai').should(); -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('from()', function() { it('should exist', function() { diff --git a/test/mysql/tests-general.js b/test/mysql/tests-general.js index bae6e68..d53cddb 100755 --- a/test/mysql/tests-general.js +++ b/test/mysql/tests-general.js @@ -1,10 +1,8 @@ var should = require('chai').should(); -describe('QueryBuilder', function() { - var QueryBuilder = require('../../drivers/mysql/query_builder.js'); - +describe('QueryBuilder', function() { it('actually exists and can be initialized', function() { - var qb = new QueryBuilder(); + var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); qb.should.be.instanceOf(Object); }); }); \ No newline at end of file diff --git a/test/mysql/tests-groupby.js b/test/mysql/tests-groupby.js index d12638f..733a6bf 100755 --- a/test/mysql/tests-groupby.js +++ b/test/mysql/tests-groupby.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('group_by()', function() { it('should exist', function() { diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js index 7a28f01..cbb0dce 100755 --- a/test/mysql/tests-having.js +++ b/test/mysql/tests-having.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('having()', function() { it('should exist', function() { diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 4965b27..163fba1 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_data = {id:3, name:'Milky Way', type: 'spiral'}; var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index 46c57b2..69a58bb 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('join()', function() { it('should exist', function() { diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js index 801e89e..f93de08 100755 --- a/test/mysql/tests-like.js +++ b/test/mysql/tests-like.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('like()', function() { it('should exist', function() { diff --git a/test/mysql/tests-orderby.js b/test/mysql/tests-orderby.js index 3fa4482..377fbc9 100755 --- a/test/mysql/tests-orderby.js +++ b/test/mysql/tests-orderby.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('order_by()', function() { it('should exist', function() { diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index 76aa593..888d51e 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('select()', function() { it('should exist', function() { diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index 38aa0a0..7224289 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('set()', function() { it('should exist', function() { @@ -10,7 +9,7 @@ describe('set()', function() { it('should be a function', function() { qb.set.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an object to put fields into', function() { qb.should.have.property('set_array'); }); it('should have an empty array to put fields into at the beginning', function() { diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js index 4939f04..bf4ce84 100755 --- a/test/mysql/tests-where.js +++ b/test/mysql/tests-where.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('where()', function() { it('should exist', function() { @@ -131,6 +130,11 @@ describe('where()', function() { qb.where({star_system:'Solar',planet:['Earth','Mars']},false); qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); + it("should split out and escape custom WHERE strings when that is the only thing provided (except when string containing parenthesis)", function() { + qb.reset_query(); + qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); + qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); + }); it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", function() { qb.reset_query(); qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); diff --git a/test/mysql/tests-where_in.js b/test/mysql/tests-where_in.js index 8eb468a..48b43ba 100755 --- a/test/mysql/tests-where_in.js +++ b/test/mysql/tests-where_in.js @@ -1,7 +1,6 @@ var should = require('chai').should(); var expect = require('chai').expect; -var QueryBuilder = require('../../drivers/mysql/query_builder.js'); -var qb = new QueryBuilder(); +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('where_in()', function() { it('should exist', function() { From e69c9f24710a1ee165f5c0725d49fce336c87617 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 7 Oct 2014 14:03:14 -0400 Subject: [PATCH 031/200] Additional documentation. Just trying to keep it up to date. --- README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8e1693e..99e93ab 100755 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ Library-Specific Methods | [or_where()](#or_where) | x | | | | | | | [or_where_in()](#or_where_in) | x | | | | | | | [or_where_not_in()](#or_where_not_in) | x | | | | | | -| [or_like()](#()](#) | x | | | | | | +| [or_like()](#or_like) | x | | | | | | | [or_not_like()](#or_not_like) | x | | | | | | | [not_like()](#not_like) | x | | | | | | | [or_having()](#or_having) | x | | | | | | @@ -226,6 +226,7 @@ Library-Specific Methods | [query()](#query) | x | | | | | | | [last_query()](#last_query) | x | | | | | | +------------- ### SELECT @@ -276,6 +277,8 @@ qb.select('MAX(id) AS `max_id`',false); ***NOTE:*** This example is contrived and can be achieved more-easily using the `.select_max()` method described below. +------------- + ### DISTINCT This SQL command is used to prevent duplicate rows from being returned in the resultset at the database level. @@ -293,6 +296,8 @@ This should only be used when querying data (execution method: get()/get_where() qb.distinct().select('id,name,description').get('users',callback); ``` +------------- + ### MIN This SQL command is used to find the minimum value for a specific field within a resultset. @@ -302,7 +307,7 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_min() +#### .select_min(field[,alias]) **Examples** @@ -318,6 +323,8 @@ You can optionally include a second parameter to rename the resulting field qb.select_min('age','min_age').get('users',callback); ``` +------------- + ### MAX This SQL command is used to find the maximum value for a specific field within a resultset. @@ -327,7 +334,7 @@ This SQL command is used to find the maximum value for a specific field within a | field | String | Required | The field to get the maximum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_max() +#### .select_max(field[,alias]) **Examples** @@ -343,6 +350,8 @@ You can optionally include a second parameter to rename the resulting field qb.select_max('age','max_age').get('users',callback); ``` +------------- + ### AVG This SQL command is used to find the average value for a specific field within a resultset. @@ -352,7 +361,7 @@ This SQL command is used to find the average value for a specific field within a | field | String | Required | The field to get the average value of | | alias | String | NULL | Optional alias to rename field | -#### .select_avg() +#### .select_avg(field[,alias]) **Examples** @@ -368,6 +377,7 @@ You can optionally include a second parameter to rename the resulting field qb.select_avg('age','avg_age').get('users',callback); ``` +------------- ### SUM @@ -378,7 +388,7 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_sum() +#### .select_sum(field[,alias]) **Examples** @@ -394,6 +404,8 @@ You can optionally include a second parameter to rename the resulting field qb.select_sum('age','sum_age').get('users',callback); ``` +------------- + ### FROM This SQL command is used to determine which sources, available to the active connection, to obtain data from. @@ -402,7 +414,7 @@ This SQL command is used to determine which sources, available to the active con | :-------- | :-------- | :----- | :-------------------------------------------- | | tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | -#### .from() +#### .from(tables) You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). @@ -449,6 +461,8 @@ qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') .get(callback); ``` +------------- + ### JOIN This SQL command is used query multiple tables related and connected by keys and get a single resultset. @@ -459,7 +473,7 @@ This SQL command is used query multiple tables related and connected by keys and | relation | String | Required | The "ON" statement that relates two tables together | | direction | String | "left" | Direction of the join (see join types list below) | -**Join Types** +**Join Types/Directions** * left * right @@ -468,7 +482,7 @@ This SQL command is used query multiple tables related and connected by keys and * left outer * right outer -#### .join() +#### .join(table,relation[,direction]) The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. @@ -510,6 +524,8 @@ qb.select(select).from('users u') .get(callback); ``` +------------- + ### WHERE This SQL command is used to limit the resultset based on filters. @@ -521,9 +537,11 @@ This SQL command is used to limit the resultset based on filters. | escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping | -#### .where() +#### .where(field[,value[,escape]]) -This method can be called in many different ways depending on your style and the format of the data that you have at the time of execution. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. +This method can be called in many different ways depending on your style and the format of the data that you have at the time of calling it. For standard SQL, all clauses will be joined with 'AND'—if you need to join clauses by 'OR', please us `.or_where()`. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. + +If a valid field name is passed in the first parameter, you can pass an array the second parameter and the call will be treated as a [.where_in()](#where_in). **Examples** @@ -534,7 +552,7 @@ If you just want to pass a single filter at a time: qb.select('galaxy').where('planet_name','Earth').get('universe',callback); ``` -If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter: +If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 @@ -555,6 +573,84 @@ You can construct complex WHERE clauses manually (however, this may cause confli qb.select('planet').where("order <= 3 AND class = 'M'").get('planets',callback); ``` +You can pass a non-empty array as a value and that portion will be treated as a call to `.where_in()`: + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `planet_count` >= 4, `star` IN('Sun','Betelgeuse') +qb.select('star_system') + .where({'planet_count >=': 4, star: ['Sun','Betelgeuse']) + .get('star_systems',callback); +``` + +#### .or_where(field[,value[,escape]]) + +This method functions identically to [.where()](#where) except that it joins clauses with 'OR' instead of 'AND'. + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `star` = 'Sun' OR `star` = 'Betelgeuse' +qb.select('star_system').where('star','Sun') + .or_where('star','Betelgeuse') + .get('star_systems',callback); +``` + +#### .where_in(field,values[,escape]) + +This will create a "WHERE IN" statement in traditional SQL which is useful when you're trying to find rows with fields matching many different values... It will be joined with existing "WHERE" statements with 'AND'. + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `star` IN('Sun','Betelgeuse','Sirius','Vega','Alpha Centauri') +var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; +qb.select('star_system').where_in('star',stars).get('star_systems',callback); +``` + +#### .or_where_in(field,values[,escape]) + +Same as `.where_in()` except the clauses are joined by 'OR'. + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `planet_count` = 4 OR `star` IN('Sun','Betelgeuse') +var stars = ['Sun','Betelgeuse']; +qb.select('star_system').where('planet_count',4) + .or_where_in('star',stars) + .get('star_systems',callback); +``` + +#### .where_not_in(field,values[,escape]) + +Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clauses are joined with 'AND'. + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `star` NOT IN('Sun','Betelgeuse','Sirius','Vega','Alpha Centauri') +var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; +qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); +``` + +#### .or_where_not_in(field,values[,escape]) + +Same as `.where_not_in()` except that clauses are joined with 'OR'. + +```javascript +// SELECT `star_system` FROM `star_systems` +// WHERE `star` NOT IN('Sun','Betelgeuse') +// OR `planet_count` IN [2,4,6,8] +var stars = ['Sun','Betelgeuse']; +var planet_sizes = [2,4,6,8]; +qb.select('star_system') + .where_not_in('star',stars) + .or_where_not_in('planet_size',planet_sizes) + .get('star_systems',callback); +``` + +------------- + +### LIKE + + Contribute ========== From 54ac2c04c3d3dca43f41a6fdd134e421501dd0ea Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 7 Oct 2014 14:17:15 -0400 Subject: [PATCH 032/200] Added a table of contents. --- README.md | 93 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 99e93ab..19eeace 100755 --- a/README.md +++ b/README.md @@ -7,18 +7,35 @@ The API of this module very closely mimics Codeigniter's Active Record (now call The primary benefits of this module (currently) are: - * Ability to write queries agnostically to the database you intend to query - * Supports all basic database commands (insert, update, delete, select, etc...) - * Extend commands from the most popular native database drivers in NPM. - * Supports method chaining - * Automatically escapes field values - * Is fully unit tested - * Allows for greater flexibility and more control over a full ORM - * Ligher-weight than an ORM - * Allows you to drop down to the native methods of your driver if you choose - * Allows for different drivers for different versions (SQLite 2 vs SQLite 3) - * The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. - * Can used as a learning tool/Rosetta stone +* Ability to write queries agnostically to the database you intend to query +* Supports all basic database commands (insert, update, delete, select, etc...) +* Extend commands from the most popular native database drivers in NPM. +* Supports method chaining +* Automatically escapes field values +* Is fully unit tested +* Allows for greater flexibility and more control over a full ORM +* Ligher-weight than an ORM +* Allows you to drop down to the native methods of your driver if you choose +* Allows for different drivers for different versions (SQLite 2 vs SQLite 3) +* The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. +* Can used as a learning tool/Rosetta stone + +Table of Contents +================= + +* [Database Drivers](#database-drivers) +* [How to Install](#how-to-install) +* [License Info](#license-info) +* [Quick Example](#quick-example) +* [Connecting to Your Database](#connecting-to-your-database) + * [Quick Reference](#quick-reference) + * [Standard Connection Settings](#standard-connection-settings) + * [Choosing the Database Type](#choosing-the-database-type) + * [Choosing the Connection Type](#choosing-the-connection-type) +* [API Methods](#api-methods) + * [SQL Commands](#sql-commands) + * [Library-Specific Methods](#library-specific-methods) +* [Contibute](#contribute) Database Drivers ================= @@ -61,15 +78,18 @@ var settings = { }; var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'standard'); -qb.select('name','position').where({type: 'rocky', 'diameter <': 12000}).get('planets', function(err,rows) { - if (err) console.error("Uh oh! Couldn't get results: " + err.msg); - - // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 - console.log("Query Ran: " + qb.last_query()); - - // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] - console.dir(rows); -}); +qb.select('name','position') + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets', function(err,rows) { + if (err) console.error("Uh oh! Couldn't get results: " + err.msg); + + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); + + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.dir(rows); + } +); ``` Connecting to Your Database @@ -229,6 +249,7 @@ Library-Specific Methods ------------- ### SELECT +#### .select(fields[,escape]) This method is used to specify the fields to pull into the resultset when running SELECT-like queries. @@ -238,8 +259,6 @@ This method is used to specify the fields to pull into the resultset when runnin | escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | -#### .select() - The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): * String with fields seperated by a comma: @@ -280,15 +299,12 @@ qb.select('MAX(id) AS `max_id`',false); ------------- ### DISTINCT +#### .distinct() -This SQL command is used to prevent duplicate rows from being returned in the resultset at the database level. +This SQL command is used to prevent duplicate rows from being returned in the resultset at the database level. It should only be used when querying data (execution methods: `.get()` & `.get_where()`) (not inserting, updating or removing). If it's provided to another execution method, it will simply be ignored. ***This method takes no parameters*** -#### .distinct() - -This should only be used when querying data (execution method: get()/get_where()) (not inserting, updating or removing). If it's provided to another execution method, it will simply be ignored. - **Example** ```javascript @@ -299,6 +315,7 @@ qb.distinct().select('id,name,description').get('users',callback); ------------- ### MIN +#### .select_min(field[,alias]) This SQL command is used to find the minimum value for a specific field within a resultset. @@ -307,8 +324,6 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_min(field[,alias]) - **Examples** ```javascript @@ -326,6 +341,7 @@ qb.select_min('age','min_age').get('users',callback); ------------- ### MAX +#### .select_max(field[,alias]) This SQL command is used to find the maximum value for a specific field within a resultset. @@ -334,8 +350,6 @@ This SQL command is used to find the maximum value for a specific field within a | field | String | Required | The field to get the maximum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_max(field[,alias]) - **Examples** ```javascript @@ -353,6 +367,7 @@ qb.select_max('age','max_age').get('users',callback); ------------- ### AVG +#### .select_avg(field[,alias]) This SQL command is used to find the average value for a specific field within a resultset. @@ -361,8 +376,6 @@ This SQL command is used to find the average value for a specific field within a | field | String | Required | The field to get the average value of | | alias | String | NULL | Optional alias to rename field | -#### .select_avg(field[,alias]) - **Examples** ```javascript @@ -380,6 +393,7 @@ qb.select_avg('age','avg_age').get('users',callback); ------------- ### SUM +#### .select_sum(field[,alias]) This SQL command is used to find the minimum value for a specific field within a result set. @@ -388,8 +402,6 @@ This SQL command is used to find the minimum value for a specific field within a | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | -#### .select_sum(field[,alias]) - **Examples** ```javascript @@ -407,6 +419,7 @@ qb.select_sum('age','sum_age').get('users',callback); ------------- ### FROM +#### .from(tables) This SQL command is used to determine which sources, available to the active connection, to obtain data from. @@ -414,8 +427,6 @@ This SQL command is used to determine which sources, available to the active con | :-------- | :-------- | :----- | :-------------------------------------------- | | tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | -#### .from(tables) - You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). Aliases can be provided and they will be escaped properly. @@ -464,6 +475,7 @@ qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') ------------- ### JOIN +#### .join(table,relation[,direction]) This SQL command is used query multiple tables related and connected by keys and get a single resultset. @@ -482,8 +494,6 @@ This SQL command is used query multiple tables related and connected by keys and * left outer * right outer -#### .join(table,relation[,direction]) - The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. **Examples** @@ -536,7 +546,6 @@ This SQL command is used to limit the resultset based on filters. | value | Mixed | N/A | When the first parameter is a field name, this is the value | | escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping | - #### .where(field[,value[,escape]]) This method can be called in many different ways depending on your style and the format of the data that you have at the time of calling it. For standard SQL, all clauses will be joined with 'AND'—if you need to join clauses by 'OR', please us `.or_where()`. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. @@ -649,7 +658,7 @@ qb.select('star_system') ------------- ### LIKE - +#### .like() Contribute From b274883b0f6dff274571655a89008e89f1a9aad8 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 7 Oct 2014 17:55:41 -0400 Subject: [PATCH 033/200] A lot more documentation and some random/minor bug fixes. --- README.md | 349 +++++++++++++++++++++++++++++++-- drivers/mysql/query_builder.js | 19 +- 2 files changed, 346 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 19eeace..cdf912d 100755 --- a/README.md +++ b/README.md @@ -35,10 +35,10 @@ Table of Contents * [API Methods](#api-methods) * [SQL Commands](#sql-commands) * [Library-Specific Methods](#library-specific-methods) -* [Contibute](#contribute) +* [Contribute](#contribute) -Database Drivers -================= +Database Drivers +================ Currently Written: ------------------ @@ -58,7 +58,7 @@ How to install npm install node-querybuilder -Licence Info +License Info ============ Licensed under the GPL license and MIT: @@ -193,7 +193,7 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); API Methods -=============================== +=========== ***NOTE:*** The compatibility portions of these tables are subject to change as features and drivers are written! @@ -261,6 +261,8 @@ This method is used to specify the fields to pull into the resultset when runnin The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): +***NOTE:*** If the select method is never called before an execution method is ran, 'SELECT *' will be assumed. + * String with fields seperated by a comma: * `.select('foo, bar, baz')` * Array of field names @@ -268,6 +270,15 @@ The fields provided to this method will be automatically escaped by the database **Examples** +`.select()` is not called ('*' assumed) + +```javascript +// SELECT * FROM galaxies +qb.get('galaxies',callback); +``` + +An array of field names: + ```javascript // SELECT `foo`, `bar`, `baz` qb.select(['foo','bar','baz']); @@ -287,14 +298,14 @@ You can alias your field names and they will be escaped properly as well: qb.select(['foo as f','bar as b','baz as z']); ``` -You can optionally choose not to have the driver auto-escape the fieldnames (dangerous, but useful if you a function in your select statement, for instance): +You can optionally choose not to have the driver auto-escape the fieldnames (dangerous, but useful if you a utilize function in your select statement, for instance): ```javascript -// SELECT MAX(id) AS `max_id` -qb.select('MAX(id) AS `max_id`',false); +// SELECT CONCAT(first_name,' ',last_name) AS `full_name` +qb.select('CONCAT(first_name,' ',last_name) AS `full_name`',false); ``` -***NOTE:*** This example is contrived and can be achieved more-easily using the `.select_max()` method described below. +***NOTE:*** If you use this technique to add driver-specific functions, it may (and probably will) cause unexpected outcomes with other database drivers! ------------- @@ -572,10 +583,10 @@ You can conveniently pass an object of key:value pairs (which can also contain c ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' -qb.select('planet').where({'order <=':'3',class:'M'}).get('planets',callback); +qb.select('planet').where({'order <=':3, class:'M'}).get('planets',callback); ``` -You can construct complex WHERE clauses manually (however, this may cause conflicts between database drivers): +You can construct complex WHERE clauses manually and they will be escaped properly as long as there are no paranthesis within it. *Please, for custom clauses containing subqueries, make sure you escape everything properly!* ***ALSO NOTE:*** with this method, there may be conflicts between database drivers! ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' @@ -646,7 +657,7 @@ Same as `.where_not_in()` except that clauses are joined with 'OR'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun','Betelgeuse') -// OR `planet_count` IN [2,4,6,8] +// OR `planet_count` NOT IN [2,4,6,8] var stars = ['Sun','Betelgeuse']; var planet_sizes = [2,4,6,8]; qb.select('star_system') @@ -658,8 +669,320 @@ qb.select('star_system') ------------- ### LIKE -#### .like() +This SQL command is used to find close matches where as the "WHERE" command is for precise matches. This is useful for doing searches. + +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :------------------------------------------------ | +| field/filters | String/Object | Required | Field name or object of field/match pairs | +| value | String/Number | Required | The value you want the field to closely match | +| side | String | 'both' | before: '%value'; after: 'value%', both: '%value%'| + +#### .like(field,match[,side]) + +All fields are escaped automatically, no exceptions. Multiple calls will be joined together with 'AND'. You can also pass an object of field/match pairs. Wildcard sides are interchangeable between before/left and after/right--choose the one that makes the most sense to you (there are examples of each below). + +**Examples** + +By default, the match string will be wrapped on both sides with the wildcard (%): + +```javascript +// SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber%' +// Potential results: [{first_name: 'Kimberly'},{first_name: 'Amber'}] +qb.select('first_name').like('first_name','mber').get('users',callback); +``` + +You can specify a side to place the wildcard (%) on if you'd like (before/left, after/right, both): + +```javascript +// SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber' +// Potential results: [{first_name: 'Amber'}] +qb.select('first_name').like('first_name','mber','before').get('users',callback); + +// SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'Kim%' +// Potential results: [{first_name: 'Kim'},{first_name: 'Kimberly'}] +qb.select('first_name').like('first_name','Kim','right').get('users',callback); +``` + +You can also pass 'none' if you don't want to use the wildcard (%) + +```javascript +// SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'kim' +// Potential results: [{first_name: 'Kim'}] +qb.select('first_name').like('first_name','kim','none').get('users',callback); +``` + +If you'd like to have multiple like clauses, you can do that by calling like multiple times: + +```javascript +// SELECT `first_name` FROM `users` +// WHERE `first_name` LIKE 'Kim%' +// AND `middle_name` LIKE '%lyt%' +// AND `last_name` LIKE '%arris' +qb.select('first_name') + .like('first_name','Kim','right') + .like('middle_name','lyt') + .like('last_name','arris','left') + .get('users',callback); +``` + +Or you can do it with an object of field/match pairs. If you want to pass a wildcard side, provide `null` as the second paramter and the side as the third. **Note**: All `match` values in an object will share the same wildcard side. + +```javascript +// SELECT `first_name` FROM `users` +// WHERE `first_name` LIKE '%ly' +// AND `middle_name` LIKE '%the' +// AND `last_name` LIKE '%is' +qb.select('first_name') + .like({first_name: 'ly', middle_name: 'the', last_name: 'is'}, null, 'before') + .get('users',callback); +``` + +#### .or_like(field,match[,side]) + +This is exactly the same as the `.like()` method except that the clauses are joined by 'OR' not 'AND'. + +**Example** + +```javascript +// SELECT `first_name` FROM `users` +// WHERE `first_name` LIKE 'Kim%' +// OR `middle_name` LIKE '%lyt%' +// OR `last_name` LIKE '%arris' +qb.select('first_name') + .or_like('first_name','Kim','right') + .or_like('middle_name','lyt') + .or_like('last_name','arris','left') + .get('users',callback); +``` + + +#### .not_like(field,match[,side]) + +This is exactly the same as the `.like()` method except that it creates "NOT LIKE" statements. + +**Example** + +```javascript +// SELECT `first_name` FROM `users` +// WHERE `first_name` NOT LIKE 'A%' +// AND `middle_name` NOT LIKE 'B%' +// AND `last_name` NOT LIKE 'C%' +qb.select('first_name') + .not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') + .get('users',callback); +``` + +#### .or_not_like(field,match[,side]) + +This is exactly the same as the `.not_like()` method except that the clauses are joined by 'OR' not 'AND'. + +**Example** + +```javascript +// SELECT `first_name` FROM `users` +// WHERE `first_name` NOT LIKE 'A%' +// OR `middle_name` NOT LIKE 'B%' +// OR `last_name` NOT LIKE 'C%' +qb.select('first_name') + .or_not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') + .get('users',callback); +``` + +### GROUP BY +#### .group_by(fields) + +This SQL command allows you to get the first (depending on ORDER) result of a group of results related by a shared value or values. + +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :------------------------------------ | +| field(s) | String/Object | Required | Field name or array of field names | + +**Examples** + +Group by a single field: + +```javascript +// SELECT * FROM `users` GROUP BY `department_id` +qb.group_by('department_id').get('users',callback); +``` + +Group by multiple fields: + +```javascript +// SELECT * FROM `users` GROUP BY `department_id`, `position_id` +qb.group_by(['department_id','position_id']).get('users',callback); +``` + +### HAVING +#### .having(field,value) + +This SQL command is similar to the 'WHERE' command but is used when aggregate functions are used in the "SELECT" portion of the query. + +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :----------------------------------------------------- | +| field/filters | String/Object | Required | Field name or object of field/value pairs to filter on | +| value | Mixed | NULL | Value to filter by | +| escape | Boolean | true | TRUE: Escape fields and values; FALSE: Don't escape. | + +This method works exactly the same way as the `.where()` method works with the exception of the fact that there is no 'HAVING' equivalent to 'WHERE IN'. See the [.where()](#where) documentation if you need additional information. + +**Examples** + +If you just want to add a single having clause: + +```javascript +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// GROUP BY `id` +// HAVING `num_planets` = 5 +qb.group_by('id').having('num_planets',5).count('star_systems',callback); +``` + +If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: + +```javascript +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// GROUP BY `id` +// HAVING `num_planets` > 5 +qb.group_by('id').having('num_planets >',5).count('star_systems',callback); +``` + +You can conveniently pass an object of key:value pairs (which can also contain custom operators): + +```javascript +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// GROUP BY `id` +// HAVING `num_planets` > 5 +qb.group_by('id').having({'num_planets >': 5}).count('star_systems',callback); +``` + +You can construct complex WHERE clauses manually and they will be escaped properly. *Please, for custom clauses containing subqueries, make sure you escape everything properly!* ***ALSO NOTE:*** with this method, there may be conflicts between database drivers! + +```javascript +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// GROUP BY `id` +// HAVING `num_planets` > (5+2) +qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems',callback); +``` + +#### .or_having(field[,value[,escape]]) + +This method functions identically to [.having()](#having) except that it joins clauses with 'OR' instead of 'AND'. + +```javascript +// SELECT SUM(planets) AS `num_planets`, SUM(moons) AS `num_moons` FROM `star_systems` +// GROUP BY `id` +// HAVING `num_planets` >= 5 OR `num_moons` <= 10 +qb.group_by('id') + .having('num_planets >=',5) + .or_having('num_moons <=', 10) + .count('star_systems',callback); +``` + +### ORDER BY +#### .order_by(field[,direction]) + +This SQL command is used to order the resultset by a field or fields in descending, ascending, or random order(s). + +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :------------------------------------------------------------------------ | +| fields | String/Array | Required | Field name or an array of field names, possibly with directions as well | +| direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. | + +This is a very flexible method, offerring a wide variety of ways you can call it. Variations include: + +* Pass the field name and ommit the direction +* Pass the field name and the direction as the first and second parameters, respectively (most common) +* Pass an array of fields to first paramter, direction to second parameter. +* Pass an array of fields + directions in first parameter and ommit the second one. +* Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter. +* Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. + +**Examples** + +Pass the field name and ommit the direction + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` ASC +qb.order_by('galaxy_name').get('galaxies',callback); +``` + +Pass the field name and the direction as the first and second parameters, respectively + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC +qb.order_by('galaxy_name','desc').get('galaxies',callback); +``` + +Pass an array of fields to first paramter, direction to second parameter + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` DESC +qb.order_by(['galaxy_name','galaxy_size'],'desc').get('galaxies',callback); +``` + +Pass an array of fields + directions in first parameter and ommit the second one. + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC +qb.order_by(['galaxy_name desc','galaxy_size asc']).get('galaxies',callback); +``` + +Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC +qb.order_by(['galaxy_name desc','galaxy_size'],'asc').get('galaxies',callback); +``` + +Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. + +```javascript +// SELECT * FROM `galaxies` ORDER BY `galaxy_name` ASC, `galaxy_size` DESC +qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies',callback); +``` + +### LIMIT +#### .limit(limit_to,offset) + +This SQL command is used to limit a result set to a maximum number of results, regardless of the actual number of results that might be returned by a non-limited query. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------- | +| limit_to | Integer | Required | The maximum number of results you want from the query | +| offset | Integer | NULL | Optional offset value (where to start before limiting)| + +**Example** + +```javascript +// SELECT * FROM `users` LIMIT 5 +qb.limit(5).get('users',callback); +``` + +You can provide an option offset value instead of calling [.offset()](#offset) seperately: + +```javascript +// SELECT * FROM `users` LIMIT 5, 5 +qb.limit(5,5).get('users',callback); +``` + +### OFFSET +#### .offset(offset) + +This SQL command is tell the "LIMIT" where to start grabbing data. If cannot be used without a limit having been set first. + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------- | +| offset | Integer | NULL | where to start before limiting | + +The practical uses of this method are probably miniscule since the `.limit()` method must be called in order to use it and the limit method provides a means by which to set the offset. In any case, the method is very simple: pass the result row index that you want to start from when limiting. This is most useful for pagination of search results and similar scenarios. + +**Example** + +```javascript +// SELECT * FROM `users` LIMIT 5, 25 +qb.limit(5).offset(25).get('users',callback); +``` Contribute ========== diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 967f7e7..f54fe8f 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -660,18 +660,18 @@ var QueryBuilder = function() { not = not || ''; if (typeof field === 'string' && field.length == 0) { - throw new Error("The field provided in your like call is empty."); + throw new Error("like(): The field you provided is empty."); } else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("The object provided in your like call is empty."); + throw new Error("like(): The object you provided is empty."); } else if ((typeof field).match(/^(string|object)$/) === null) { - throw new Error("You have provided an invalid value as the first parameter in your like() call. Only valid strings and objects are allowed."); + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); } if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { if (match === null) { - throw new Error("like(): Since your first parameters is a string, your second param must a valid number, boolean, or string."); + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } var field_array = {}; @@ -686,11 +686,11 @@ var QueryBuilder = function() { // Make sure value is only string, number, or boolean if ((typeof v).match(/^(string|number|boolean)$/) === null) { - throw new Error("You have provided an invalid value as the second parameter in your like() call. Only valid strings, numbers, and booleans are allowed."); + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } - // if number, no Infinity or NaN + // If number, don't allow Infinity or NaN else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("You have provided an invalid number value as the second parameter in your like() call. Only valid strings, numbers, and booleans are allowed."); + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } if (side === 'none') { @@ -724,6 +724,7 @@ var QueryBuilder = function() { + build_from_clause(this) + build_join_string(this) + build_where_clause(this) + + build_having_clause(this) return sql; }, @@ -993,7 +994,7 @@ var QueryBuilder = function() { if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... - throw new Error("The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); } key_array[key] = value; key = key_array; @@ -1013,7 +1014,7 @@ var QueryBuilder = function() { //console.log("Key is NOT a string"); for (var i in key) { if (typeof key[i] !== 'string') { - throw new Error("You've provided an unparseable format to the having() method.."); + throw new Error("having(): You've provided an unparseable format to the having() method.."); } else { key_array = extract_having_parts(key[i],key_array); From 0739d69af93af5fb426509c9b82efb4333f092f2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 7 Oct 2014 17:56:22 -0400 Subject: [PATCH 034/200] Removed index_old.js file. --- index_old.js | 379 --------------------------------------------------- 1 file changed, 379 deletions(-) delete mode 100755 index_old.js diff --git a/index_old.js b/index_old.js deleted file mode 100755 index 7688bad..0000000 --- a/index_old.js +++ /dev/null @@ -1,379 +0,0 @@ -/** - * MySQL ActiveRecord Adapter for Node.js - * (C) Martin Tajur 2011-2013 - * martin@tajur.ee - * - * Active Record Database Pattern implementation for use with node-mysql as MySQL connection driver. - * - * Dual licensed under the MIT and GPL licenses. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * -**/ -var Adapter = function(settings) { - var mysql = require('mysql'); - - this.qb = require('./lib/query_builder.js').QueryBuilder(); - - // Non-Public QueryBuilder APIs - this._where = this.qb._where; - this._where_in = this.qb._where_in; - this._like = this.qb._like; - this._min_max_avg_sum = this.qb._min_max_avg_sum; - this._having = this.qb._having; - this._update = this.qb._update; - this.reset_query = this.qb.reset_query; - - // QueryBuilder Properties - this.whereArray = this.qb.whereArray; - this.whereInArray = this.qb.whereInArray; - this.fromArray = this.qb.fromArray; - this.joinArray = this.qb.joinArray; - this.selectArray = this.qb.selectArray; - this.setArray = this.qb.setArray; - this.orderByArray = this.qb.orderByArray; - this.orderByDir = this.qb.orderByDir; - this.groupByArray = this.qb.groupByArray; - this.havingArray = this.qb.havingArray; - this.limitTo = this.qb.limitTo; - this.offsetVal = this.qb.offsetVal; - this.joinClause = this.qb.joinClause; - this.lastQueryString = this.qb.lastQueryString; - this.distinctClause = this.qb.distinctClause; - this.aliasedTables = this.qb.aliasedTables; - - // QueryBuilder method mappings for backwards compatibility - this.where = this.qb.where; - this.or_where = this.qb.or_where; - this.where_in = this.qb.where_in; - this.or_where_in = this.qb.or_where_in; - this.where_not_in = this.qb.where_not_in; - this.or_where_not_in = this.qb.or_where_not_in; - this.like = this.qb.like; - this.not_like = this.qb.not_like; - this.or_like = this.qb.or_like; - this.or_not_like = this.qb.or_not_like; - this.from = this.qb.from; - this.join = this.qb.join; - this.select = this.qb.select; - this.select_min = this.qb.select_min; - this.select_max = this.qb.select_max; - this.select_avg = this.qb.select_avg; - this.select_sum = this.qb.select_sum; - this.distinct = this.qb.distinct; - this.group_by = this.qb.group_by; - this.having = this.qb.having; - this.or_having = this.qb.or_having; - this.order_by = this.qb.order_by; - this.limit = this.qb.limit; - this.offset = this.qb.offset; - this.set = this.qb.set; - this.get_compiled_select = this.qb.get_compiled_select; - this.get_compiled_insert = this.qb.get_compiled_insert; - this.get_compiled_update = this.qb.get_compiled_update; - this.get_compiled_delete = this.qb.get_compiled_delete; - this._last_query = this.qb._last_query; - this.last_query = this.qb._last_query; - - // QueryBuilder "Query Execution" methods: - this.count = function(table, callback) { - var sql = this.qb.count(table); - - connection.query(sql, function(err, res) { - if (err) - callback(err, null); - else - callback(null, res[0]['count']); - }); - - return that; - }; - - this.get = function(table,callback) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); - } - - var sql = this.qb.get(table,callback); - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - this.get_where = function(table,where,callback) { - var sql = this.qb.get_where(table,where); - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - this.insert = function(table,set,callback,verb,suffix) { - var sql = this.qb.insert(table,set,callback,verb,suffix); - this.reset_query(sql); - connection.query(sql); - return that; - }; - - this.insert_ignore = function(table,set,callback) { - var sql = this.qb.insert_ignore(table,set,callback); - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - this.insert_batch = function(table,set,callback) { - var sql = this.qb.insert_batch(table,set,callback); - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - this.update = function(table,set,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'update' call!"); - } - - var sql = this.qb.update(table,set,where,callback); - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - // TODO: Write this complicated-ass function - this.update_batch = function() { - - return that; - }; - - this.delete = function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } - - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } - - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - - var sql = this.delete(table, where); - - this.reset_query(sql); - connection.query(sql, callback); - return that; - }; - - this.empty_table - function(table, callback) { - var sql = this.qb.empty_table(table,callback); - this.reset_query(sql); - connection.query(sql); - return that; - }); - - this.truncate - function(table, callback) { - var sql = this.qb.truncate(table,callback); - this.reset_query(sql); - connection.query(sql); - return that; - }); - - var initializeConnectionSettings = function () { - if(settings.server) { - settings.host = settings.server; - } - if(settings.username) { - settings.user = settings.username; - } - - if (!settings.host) { - throw new Error('Unable to start ActiveRecord - no server given.'); - } - if (!settings.port) { - settings.port = 3306; - } - if (!settings.user) { - settings.user = ''; - } - if (!settings.password) { - settings.password = ''; - } - if (!settings.database) { - throw new Error('Unable to start ActiveRecord - no database given.'); - } - - return settings; - }; - - var connection; - var connectionSettings; - var pool; - - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } else { - connectionSettings = initializeConnectionSettings(); - connection = new mysql.createConnection(connectionSettings); - } - - if (settings.charset) { - connection.query('SET NAMES ' + settings.charset); - } - - this.connectionSettings = function() { return connectionSettings; }; - this.connection = function() { return connection; }; - - this.query = function(sqlQueryString, responseCallback) { - connection.query(sqlQueryString, responseCallback); - this.reset_query(sqlQueryString); - return that; - }; - - this.disconnect = function() { - return connection.end(); - }; - - this.forceDisconnect = function() { - return connection.destroy(); - }; - - this.releaseConnection = function() { - pool.releaseConnection(connection); - }; - - this.releaseConnection = function() { - pool.releaseConnection(connection); - }; - - this.ping = function() { - connection.ping(); - return that; - }; - - this.escape = function(str) { - return connection.escape(str); - }; - - var reconnectingTimeout = false; - - function handleDisconnect(connectionInstance) { - connectionInstance.on('error', function(err) { - if (!err.fatal || reconnectingTimeout) { - return; - } - - if (err.code !== 'PROTOCOL_CONNECTION_LOST' && err.code !== 'ECONNREFUSED') { - throw err; - } - - var reconnectingTimeout = setTimeout(function() { - connection = mysql.createConnection(connectionInstance.config); - handleDisconnect(connection); - connection.connect(); - }, 2000); - }); - } - - if (!pool) { - handleDisconnect(connection); - } - - var that = this; - - return this; -}; - -var mysqlPool; // this should be initialized only once. -var mysqlCharset; - -var Pool = function (settings) { - if (!mysqlPool) { - var mysql = require('mysql'); - - var poolOption = { - createConnection: settings.createConnection, - waitForConnections: settings.waitForConnections, - connectionLimit: settings.connectionLimit, - queueLimit: settings.queueLimit - }; - Object.keys(poolOption).forEach(function (element) { - // Avoid pool option being used by mysql connection. - delete settings[element]; - // Also remove undefined elements from poolOption - if (!poolOption[element]) { - delete poolOption[element]; - } - }); - - // Confirm settings with Adapter. - var db = new Adapter(settings); - var connectionSettings = db.connectionSettings(); - - Object.keys(connectionSettings).forEach(function (element) { - poolOption[element] = connectionSettings[element]; - }); - - mysqlPool = mysql.createPool(poolOption); - mysqlCharset = settings.charset; - } - - this.pool = function () { - return mysqlPool; - }; - - this.getNewAdapter = function (responseCallback) { - mysqlPool.getConnection(function (err, connection) { - if (err) { - throw err; - } - var adapter = new Adapter({ - pool: { - pool: mysqlPool, - enabled: true, - connection: connection - }, - charset: mysqlCharset - }); - responseCallback(adapter); - }); - }; - - this.disconnect = function (responseCallback) { - this.pool().end(responseCallback); - }; - - return this; -}; - -exports.Adapter = Adapter; -exports.Pool = Pool; From b27b4bb1e7b4578037e4929ff325fa71c30f6ee5 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 8 Oct 2014 11:41:41 -0400 Subject: [PATCH 035/200] Trying checkmarks instead of X's in tables. --- README.md | 86 +++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index cdf912d..749bb64 100755 --- a/README.md +++ b/README.md @@ -200,51 +200,51 @@ API Methods SQL Commands ------------- -| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :------------ | :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | -| SELECT | [select()](#select) | x | | | | | | -| DISTINCT | [distinct()](#distinct) | x | | | | | | -| MIN | [select_min()](#min) | x | | | | | | -| MAX | [select_max()](#max) | x | | | | | | -| AVG | [select_avg()](#avg) | x | | | | | | -| SUM | [select_sum()](#sum) | x | | | | | | -| FROM | [from()](#from) | x | | | | | | -| JOIN | [join()](#join) | x | | | | | | -| WHERE | [where()](#where) | x | | | | | | -| IN | [where_in()](#where_in) | x | | | | | | -| GROUP BY | [group_by()](#group_by) | x | | | | | | -| HAVING | [having()](#having) | x | | | | | | -| ORDER BY | [order_by()](#order_by) | x | | | | | | -| LIMIT | [limit()](#limit) | x | | | | | | -| OFFSET | [offset()](#offset) | x | | | | | | -| COUNT | [count()](#count) | x | | | | | | -| SET | [set()](#set) | x | | | | | | -| UPDATE | [update()](#update) | x | | | | | | -| INSERT | [insert()](#insert) | x | | | | | | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | x | | | | | | -| DELETE | [delete()](#delete) | x | | | | | | +| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :------------ | :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | +| SELECT | [select()](#select) | ✓ | | | | | | +| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | +| MIN | [select_min()](#min) | ✓ | | | | | | +| MAX | [select_max()](#max) | ✓ | | | | | | +| AVG | [select_avg()](#avg) | ✓ | | | | | | +| SUM | [select_sum()](#sum) | ✓ | | | | | | +| FROM | [from()](#from) | ✓ | | | | | | +| JOIN | [join()](#join) | ✓ | | | | | | +| WHERE | [where()](#where) | ✓ | | | | | | +| IN | [where_in()](#where_in) | ✓ | | | | | | +| GROUP BY | [group_by()](#group_by) | ✓ | | | | | | +| HAVING | [having()](#having) | ✓ | | | | | | +| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | +| LIMIT | [limit()](#limit) | ✓ | | | | | | +| OFFSET | [offset()](#offset) | ✓ | | | | | | +| COUNT | [count()](#count) | ✓ | | | | | | +| SET | [set()](#set) | ✓ | | | | | | +| UPDATE | [update()](#update) | ✓ | | | | | | +| INSERT | [insert()](#insert) | ✓ | | | | | | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | +| DELETE | [delete()](#delete) | ✓ | | | | | | Library-Specific Methods ------------------------ -| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | -| [get()](#get) | x | | | | | | -| [get_where()](#get_where) | x | | | | | | -| [count_all()](#count_all) | x | | | | | | -| [where_not_in()](#where_not_in) | x | | | | | | -| [or_where()](#or_where) | x | | | | | | -| [or_where_in()](#or_where_in) | x | | | | | | -| [or_where_not_in()](#or_where_not_in) | x | | | | | | -| [or_like()](#or_like) | x | | | | | | -| [or_not_like()](#or_not_like) | x | | | | | | -| [not_like()](#not_like) | x | | | | | | -| [or_having()](#or_having) | x | | | | | | -| [count_all_results()](#count_all_results) | x | | | | | | -| [insert_batch()](#insert_batch) | x | | | | | | -| [update_batch()](#update_batch) | x | | | | | | -| [query()](#query) | x | | | | | | -| [last_query()](#last_query) | x | | | | | | +| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | +| [get()](#get) | ✓ | | | | | | +| [get_where()](#get_where) | ✓ | | | | | | +| [count_all()](#count_all) | ✓ | | | | | | +| [where_not_in()](#where_not_in) | ✓ | | | | | | +| [or_where()](#or_where) | ✓ | | | | | | +| [or_where_in()](#or_where_in) | ✓ | | | | | | +| [or_where_not_in()](#or_where_not_in) | ✓ | | | | | | +| [or_like()](#or_like) | ✓ | | | | | | +| [or_not_like()](#or_not_like) | ✓ | | | | | | +| [not_like()](#not_like) | ✓ | | | | | | +| [or_having()](#or_having) | ✓ | | | | | | +| [count_all_results()](#count_all_results) | ✓ | | | | | | +| [insert_batch()](#insert_batch) | ✓ | | | | | | +| [update_batch()](#update_batch) | ✓ | | | | | | +| [query()](#query) | ✓ | | | | | | +| [last_query()](#last_query) | ✓ | | | | | | ------------- @@ -572,7 +572,7 @@ If you just want to pass a single filter at a time: qb.select('galaxy').where('planet_name','Earth').get('universe',callback); ``` -If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: +If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 @@ -838,7 +838,7 @@ If you just want to add a single having clause: qb.group_by('id').having('num_planets',5).count('star_systems',callback); ``` -If you need more complex filtering using different operators (<, >, <=, =>, !-, <>, etc...), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: +If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: ```javascript // SELECT COUNT(*) AS `num_planets` FROM `star_systems` From 2223cacbb368862d1c1e73091d8d29df0fbfa2c5 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 8 Oct 2014 13:38:02 -0400 Subject: [PATCH 036/200] Fixed some formatting issues in the readme.md --- README.md | 208 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 108 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 749bb64..1beb556 100755 --- a/README.md +++ b/README.md @@ -98,14 +98,14 @@ Connecting to Your Database Quick Reference --------------- -| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | -| :-------- | :------ | :----- | :------- | :--- | :------ | :------------------------------------------------------- | -| mysql | X | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | -| mssql | | No | Yes | ??? | ??? | | -| sqlite | | No | Yes | ??? | ??? | | -| oracle | | No | Yes | ??? | ??? | | -| postgres | | No | Yes | ??? | ??? | | -| mongodb | | No | Yes | ??? | ??? | | +| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | +| :-------- | :------ | :----- | :------- | :--- | :------ | :---------------------------------------------------------- | +| mysql | ✓ | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | +| mssql | | No | Yes | ??? | ??? | | +| sqlite | | No | Yes | ??? | ??? | | +| oracle | | No | Yes | ??? | ??? | | +| postgres | | No | Yes | ??? | ??? | | +| mongodb | | No | Yes | ??? | ??? | | Standard Connection Settings @@ -114,12 +114,12 @@ Standard Connection Settings The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. | Option | Default | Description | -| :-------- | :----- | :-------------------------------------------- | -| host | localhost | The server you're connecting to | -| user | NULL | The database user | -| password | NULL | The database `user`'s password | -| database | NULL | The database to connect to | -| pool_size | 10 | Max connections for `pool` connection type | +| :-------- | :----- | :-------------------------------------------- | +| host | localhost | The server you're connecting to | +| user | NULL | The database user | +| password | NULL | The database `user`'s password | +| database | NULL | The database to connect to | +| pool_size | 10 | Max connections for `pool` connection type | The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. @@ -200,51 +200,51 @@ API Methods SQL Commands ------------- -| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :------------ | :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | -| SELECT | [select()](#select) | ✓ | | | | | | -| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | -| MIN | [select_min()](#min) | ✓ | | | | | | -| MAX | [select_max()](#max) | ✓ | | | | | | -| AVG | [select_avg()](#avg) | ✓ | | | | | | -| SUM | [select_sum()](#sum) | ✓ | | | | | | -| FROM | [from()](#from) | ✓ | | | | | | -| JOIN | [join()](#join) | ✓ | | | | | | -| WHERE | [where()](#where) | ✓ | | | | | | -| IN | [where_in()](#where_in) | ✓ | | | | | | -| GROUP BY | [group_by()](#group_by) | ✓ | | | | | | -| HAVING | [having()](#having) | ✓ | | | | | | -| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | -| LIMIT | [limit()](#limit) | ✓ | | | | | | -| OFFSET | [offset()](#offset) | ✓ | | | | | | -| COUNT | [count()](#count) | ✓ | | | | | | -| SET | [set()](#set) | ✓ | | | | | | -| UPDATE | [update()](#update) | ✓ | | | | | | -| INSERT | [insert()](#insert) | ✓ | | | | | | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | -| DELETE | [delete()](#delete) | ✓ | | | | | | +| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :------------ | :---------------- | :-------: | :-------: | :-------: | :-------: | :-------: | :---: | +| SELECT | [select()](#select) | ✓ | | | | | | +| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | +| MIN | [select_min()](#min) | ✓ | | | | | | +| MAX | [select_max()](#max) | ✓ | | | | | | +| AVG | [select_avg()](#avg) | ✓ | | | | | | +| SUM | [select_sum()](#sum) | ✓ | | | | | | +| FROM | [from()](#from) | ✓ | | | | | | +| JOIN | [join()](#join) | ✓ | | | | | | +| WHERE | [where()](#where) | ✓ | | | | | | +| IN | [where_in()](#or_where_infieldvaluesescape) | ✓ | | | | | | +| GROUP BY | [group_by()](#group-by) | ✓ | | | | | | +| HAVING | [having()](#having) | ✓ | | | | | | +| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | +| LIMIT | [limit()](#limit) | ✓ | | | | | | +| OFFSET | [offset()](#offset) | ✓ | | | | | | +| COUNT | [count()](#count) | ✓ | | | | | | +| SET | [set()](#set) | ✓ | | | | | | +| UPDATE | [update()](#update) | ✓ | | | | | | +| INSERT | [insert()](#insert) | ✓ | | | | | | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | +| DELETE | [delete()](#delete) | ✓ | | | | | | Library-Specific Methods ------------------------ -| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :---------------- | :---: | :---: | :----: | :-----:| :------: | :---: | -| [get()](#get) | ✓ | | | | | | -| [get_where()](#get_where) | ✓ | | | | | | -| [count_all()](#count_all) | ✓ | | | | | | -| [where_not_in()](#where_not_in) | ✓ | | | | | | -| [or_where()](#or_where) | ✓ | | | | | | -| [or_where_in()](#or_where_in) | ✓ | | | | | | -| [or_where_not_in()](#or_where_not_in) | ✓ | | | | | | -| [or_like()](#or_like) | ✓ | | | | | | -| [or_not_like()](#or_not_like) | ✓ | | | | | | -| [not_like()](#not_like) | ✓ | | | | | | -| [or_having()](#or_having) | ✓ | | | | | | -| [count_all_results()](#count_all_results) | ✓ | | | | | | -| [insert_batch()](#insert_batch) | ✓ | | | | | | -| [update_batch()](#update_batch) | ✓ | | | | | | -| [query()](#query) | ✓ | | | | | | -| [last_query()](#last_query) | ✓ | | | | | | +| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :-----------------------------------------| :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| [get()](#get) | ✓ | | | | | | +| [get_where()](#get_where) | ✓ | | | | | | +| [count_all()](#count_all) | ✓ | | | | | | +| [where_not_in()](#where_not_in) | ✓ | | | | | | +| [or_where()](#or_where) | ✓ | | | | | | +| [or_where_in()](#or_where_in) | ✓ | | | | | | +| [or_where_not_in()](#or_where_not_in) | ✓ | | | | | | +| [or_like()](#or_like) | ✓ | | | | | | +| [or_not_like()](#or_not_like) | ✓ | | | | | | +| [not_like()](#not_like) | ✓ | | | | | | +| [or_having()](#or_having) | ✓ | | | | | | +| [count_all_results()](#count_all_results) | ✓ | | | | | | +| [insert_batch()](#insert_batch) | ✓ | | | | | | +| [update_batch()](#update_batch) | ✓ | | | | | | +| [query()](#query) | ✓ | | | | | | +| [last_query()](#last_query) | ✓ | | | | | | ------------- @@ -253,10 +253,10 @@ Library-Specific Methods This method is used to specify the fields to pull into the resultset when running SELECT-like queries. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | -| fields | String/Array | N/A | The fields in which to grab from the database | -| escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------- | +| fields | String/Array | N/A | The fields in which to grab from the database | +| escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): @@ -267,7 +267,7 @@ The fields provided to this method will be automatically escaped by the database * `.select('foo, bar, baz')` * Array of field names * `.select(['foo','bar','baz'])` - + **Examples** `.select()` is not called ('*' assumed) @@ -330,8 +330,8 @@ qb.distinct().select('id,name,description').get('users',callback); This SQL command is used to find the minimum value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| | field | String | Required | The field to get the minimum value of | | alias | String | NULL | Optional alias to rename field | @@ -356,10 +356,10 @@ qb.select_min('age','min_age').get('users',callback); This SQL command is used to find the maximum value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the maximum value of | -| alias | String | NULL | Optional alias to rename field | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the maximum value of | +| alias | String | NULL | Optional alias to rename field | **Examples** @@ -382,10 +382,10 @@ qb.select_max('age','max_age').get('users',callback); This SQL command is used to find the average value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the average value of | -| alias | String | NULL | Optional alias to rename field | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------| +| field | String | Required | The field to get the average value of | +| alias | String | NULL | Optional alias to rename field | **Examples** @@ -408,10 +408,10 @@ qb.select_avg('age','avg_age').get('users',callback); This SQL command is used to find the minimum value for a specific field within a result set. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the minimum value of | -| alias | String | NULL | Optional alias to rename field | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------ | +| field | String | Required | The field to get the minimum value of | +| alias | String | NULL | Optional alias to rename field | **Examples** @@ -434,9 +434,9 @@ qb.select_sum('age','sum_age').get('users',callback); This SQL command is used to determine which sources, available to the active connection, to obtain data from. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | -| tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | +| Parameter | Type | Default | Description | +| :-------- | :------------ | :-------- | :-------------------------------------------- | +| tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). @@ -490,10 +490,10 @@ qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') This SQL command is used query multiple tables related and connected by keys and get a single resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------------------- | -| table | String | Required | The table or view to join to. | -| relation | String | Required | The "ON" statement that relates two tables together | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :-------- | :-------------------------------------------------------- | +| table | String | Required | The table or view to join to. | +| relation | String | Required | The "ON" statement that relates two tables together | | direction | String | "left" | Direction of the join (see join types list below) | **Join Types/Directions** @@ -551,10 +551,10 @@ qb.select(select).from('users u') This SQL command is used to limit the resultset based on filters. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| Parameter | Type | Default | Description | +| :------------ | :------------ | :-------- | :------------------------------------------------------------ | | field/filters | String/Object | Required | A field name, a WHERE clause, or an object of key/value pairs | -| value | Mixed | N/A | When the first parameter is a field name, this is the value | +| value | Mixed | N/A | When the first parameter is a field name, this is the value | | escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping | #### .where(field[,value[,escape]]) @@ -603,6 +603,7 @@ qb.select('star_system') .get('star_systems',callback); ``` + #### .or_where(field[,value[,escape]]) This method functions identically to [.where()](#where) except that it joins clauses with 'OR' instead of 'AND'. @@ -615,6 +616,7 @@ qb.select('star_system').where('star','Sun') .get('star_systems',callback); ``` + #### .where_in(field,values[,escape]) This will create a "WHERE IN" statement in traditional SQL which is useful when you're trying to find rows with fields matching many different values... It will be joined with existing "WHERE" statements with 'AND'. @@ -626,6 +628,7 @@ var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; qb.select('star_system').where_in('star',stars).get('star_systems',callback); ``` + #### .or_where_in(field,values[,escape]) Same as `.where_in()` except the clauses are joined by 'OR'. @@ -639,6 +642,7 @@ qb.select('star_system').where('planet_count',4) .get('star_systems',callback); ``` + #### .where_not_in(field,values[,escape]) Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clauses are joined with 'AND'. @@ -650,6 +654,7 @@ var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); ``` + #### .or_where_not_in(field,values[,escape]) Same as `.where_not_in()` except that clauses are joined with 'OR'. @@ -673,9 +678,9 @@ qb.select('star_system') This SQL command is used to find close matches where as the "WHERE" command is for precise matches. This is useful for doing searches. | Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :------------------------------------------------ | +| :------------ | :------------ | :-------- | :------------------------------------------------ | | field/filters | String/Object | Required | Field name or object of field/match pairs | -| value | String/Number | Required | The value you want the field to closely match | +| value | String/Number | Required | The value you want the field to closely match | | side | String | 'both' | before: '%value'; after: 'value%', both: '%value%'| #### .like(field,match[,side]) @@ -738,6 +743,7 @@ qb.select('first_name') .get('users',callback); ``` + #### .or_like(field,match[,side]) This is exactly the same as the `.like()` method except that the clauses are joined by 'OR' not 'AND'. @@ -756,7 +762,7 @@ qb.select('first_name') .get('users',callback); ``` - + #### .not_like(field,match[,side]) This is exactly the same as the `.like()` method except that it creates "NOT LIKE" statements. @@ -773,6 +779,7 @@ qb.select('first_name') .get('users',callback); ``` + #### .or_not_like(field,match[,side]) This is exactly the same as the `.not_like()` method except that the clauses are joined by 'OR' not 'AND'. @@ -794,9 +801,9 @@ qb.select('first_name') This SQL command allows you to get the first (depending on ORDER) result of a group of results related by a shared value or values. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :------------------------------------ | -| field(s) | String/Object | Required | Field name or array of field names | +| Parameter | Type | Default | Description | +| :-------- | :------------ | :------- | :------------------------------------ | +| field(s) | String/Object | Required | Field name or array of field names | **Examples** @@ -819,11 +826,11 @@ qb.group_by(['department_id','position_id']).get('users',callback); This SQL command is similar to the 'WHERE' command but is used when aggregate functions are used in the "SELECT" portion of the query. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :----------------------------------------------------- | -| field/filters | String/Object | Required | Field name or object of field/value pairs to filter on | -| value | Mixed | NULL | Value to filter by | -| escape | Boolean | true | TRUE: Escape fields and values; FALSE: Don't escape. | +| Parameter | Type | Default | Description | +| :-------- | :------------ | :-------- | :----------------------------------------------------- | +| field/filters | String/Object | Required | Field name or object of field/value pairs to filter on | +| value | Mixed | NULL | Value to filter by | +| escape | Boolean | true | TRUE: Escape fields and values; FALSE: Don't escape. | This method works exactly the same way as the `.where()` method works with the exception of the fact that there is no 'HAVING' equivalent to 'WHERE IN'. See the [.where()](#where) documentation if you need additional information. @@ -865,6 +872,7 @@ You can construct complex WHERE clauses manually and they will be escaped proper qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems',callback); ``` + #### .or_having(field[,value[,escape]]) This method functions identically to [.having()](#having) except that it joins clauses with 'OR' instead of 'AND'. @@ -885,7 +893,7 @@ qb.group_by('id') This SQL command is used to order the resultset by a field or fields in descending, ascending, or random order(s). | Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :------------------------------------------------------------------------ | +| :-------- | :------------ | :-------- | :------------------------------------------------------------------------ | | fields | String/Array | Required | Field name or an array of field names, possibly with directions as well | | direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. | @@ -948,7 +956,7 @@ qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies',callback); This SQL command is used to limit a result set to a maximum number of results, regardless of the actual number of results that might be returned by a non-limited query. | Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------- | +| :-------- | :-------- | :-------- | :---------------------------------------------------- | | limit_to | Integer | Required | The maximum number of results you want from the query | | offset | Integer | NULL | Optional offset value (where to start before limiting)| @@ -971,9 +979,9 @@ qb.limit(5,5).get('users',callback); This SQL command is tell the "LIMIT" where to start grabbing data. If cannot be used without a limit having been set first. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------- | -| offset | Integer | NULL | where to start before limiting | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :----------------------------- | +| offset | Integer | NULL | where to start before limiting | The practical uses of this method are probably miniscule since the `.limit()` method must be called in order to use it and the limit method provides a means by which to set the offset. In any case, the method is very simple: pass the result row index that you want to start from when limiting. This is most useful for pagination of search results and similar scenarios. From 207c1784f6dd862feb1713f11ddc564973bacf57 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 8 Oct 2014 13:42:39 -0400 Subject: [PATCH 037/200] Fixed some more formatting issues in the readme.md --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1beb556..71c1437 100755 --- a/README.md +++ b/README.md @@ -200,35 +200,35 @@ API Methods SQL Commands ------------- -| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :------------ | :---------------- | :-------: | :-------: | :-------: | :-------: | :-------: | :---: | -| SELECT | [select()](#select) | ✓ | | | | | | -| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | -| MIN | [select_min()](#min) | ✓ | | | | | | -| MAX | [select_max()](#max) | ✓ | | | | | | -| AVG | [select_avg()](#avg) | ✓ | | | | | | -| SUM | [select_sum()](#sum) | ✓ | | | | | | -| FROM | [from()](#from) | ✓ | | | | | | -| JOIN | [join()](#join) | ✓ | | | | | | -| WHERE | [where()](#where) | ✓ | | | | | | -| IN | [where_in()](#or_where_infieldvaluesescape) | ✓ | | | | | | -| GROUP BY | [group_by()](#group-by) | ✓ | | | | | | -| HAVING | [having()](#having) | ✓ | | | | | | -| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | -| LIMIT | [limit()](#limit) | ✓ | | | | | | -| OFFSET | [offset()](#offset) | ✓ | | | | | | -| COUNT | [count()](#count) | ✓ | | | | | | -| SET | [set()](#set) | ✓ | | | | | | -| UPDATE | [update()](#update) | ✓ | | | | | | -| INSERT | [insert()](#insert) | ✓ | | | | | | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | -| DELETE | [delete()](#delete) | ✓ | | | | | | +| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :---------------- | :-------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| SELECT | [select()](#select) | ✓ | | | | | | +| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | +| MIN | [select_min()](#min) | ✓ | | | | | | +| MAX | [select_max()](#max) | ✓ | | | | | | +| AVG | [select_avg()](#avg) | ✓ | | | | | | +| SUM | [select_sum()](#sum) | ✓ | | | | | | +| FROM | [from()](#from) | ✓ | | | | | | +| JOIN | [join()](#join) | ✓ | | | | | | +| WHERE | [where()](#where) | ✓ | | | | | | +| IN | [where_in()](#where_in) | ✓ | | | | | | +| GROUP BY | [group_by()](#group_by) | ✓ | | | | | | +| HAVING | [having()](#having) | ✓ | | | | | | +| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | +| LIMIT | [limit()](#limit) | ✓ | | | | | | +| OFFSET | [offset()](#offset) | ✓ | | | | | | +| COUNT | [count()](#count) | ✓ | | | | | | +| SET | [set()](#set) | ✓ | | | | | | +| UPDATE | [update()](#update) | ✓ | | | | | | +| INSERT | [insert()](#insert) | ✓ | | | | | | +| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | +| DELETE | [delete()](#delete) | ✓ | | | | | | Library-Specific Methods ------------------------ -| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :-----------------------------------------| :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :---------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | | [get()](#get) | ✓ | | | | | | | [get_where()](#get_where) | ✓ | | | | | | | [count_all()](#count_all) | ✓ | | | | | | From 68ae43e67a3364f5cafe547d9f037a55964e202e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 8 Oct 2014 16:05:52 -0400 Subject: [PATCH 038/200] Rearranged the documentation a little bit as well as added some new 'Execution Methods' documentation. --- README.md | 338 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 284 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 71c1437..b26946d 100755 --- a/README.md +++ b/README.md @@ -33,8 +33,9 @@ Table of Contents * [Choosing the Database Type](#choosing-the-database-type) * [Choosing the Connection Type](#choosing-the-connection-type) * [API Methods](#api-methods) - * [SQL Commands](#sql-commands) - * [Library-Specific Methods](#library-specific-methods) + * [Chainable Methods](#chainable-methods) + * [Execution Methods](#execution-methods) + * [Other Library-Specific Methods](#other-library-specific-methods) * [Contribute](#contribute) Database Drivers @@ -80,14 +81,14 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'standard' qb.select('name','position') .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', function(err,rows) { + .get('planets', function(err,response) { if (err) console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 console.log("Query Ran: " + qb.last_query()); // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] - console.dir(rows); + console.dir(response); } ); ``` @@ -98,8 +99,8 @@ Connecting to Your Database Quick Reference --------------- -| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | -| :-------- | :------ | :----- | :------- | :--- | :------ | :---------------------------------------------------------- | +| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | +| :-------- | :------ | :----- | :------- | :---- | :---- | :-------------------------------------------------------- | | mysql | ✓ | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | | mssql | | No | Yes | ??? | ??? | | | sqlite | | No | Yes | ??? | ??? | | @@ -197,54 +198,36 @@ API Methods ***NOTE:*** The compatibility portions of these tables are subject to change as features and drivers are written! -SQL Commands -------------- - -| SQL Command | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :---------------- | :-------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | -| SELECT | [select()](#select) | ✓ | | | | | | -| DISTINCT | [distinct()](#distinct) | ✓ | | | | | | -| MIN | [select_min()](#min) | ✓ | | | | | | -| MAX | [select_max()](#max) | ✓ | | | | | | -| AVG | [select_avg()](#avg) | ✓ | | | | | | -| SUM | [select_sum()](#sum) | ✓ | | | | | | -| FROM | [from()](#from) | ✓ | | | | | | -| JOIN | [join()](#join) | ✓ | | | | | | -| WHERE | [where()](#where) | ✓ | | | | | | -| IN | [where_in()](#where_in) | ✓ | | | | | | -| GROUP BY | [group_by()](#group_by) | ✓ | | | | | | -| HAVING | [having()](#having) | ✓ | | | | | | -| ORDER BY | [order_by()](#order_by) | ✓ | | | | | | -| LIMIT | [limit()](#limit) | ✓ | | | | | | -| OFFSET | [offset()](#offset) | ✓ | | | | | | -| COUNT | [count()](#count) | ✓ | | | | | | -| SET | [set()](#set) | ✓ | | | | | | -| UPDATE | [update()](#update) | ✓ | | | | | | -| INSERT | [insert()](#insert) | ✓ | | | | | | -| INSERT IGNORE | [insert_ignore()](#insert_ignore) | ✓ | | | | | | -| DELETE | [delete()](#delete) | ✓ | | | | | | - -Library-Specific Methods ------------------------- - -| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :---------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | -| [get()](#get) | ✓ | | | | | | -| [get_where()](#get_where) | ✓ | | | | | | -| [count_all()](#count_all) | ✓ | | | | | | -| [where_not_in()](#where_not_in) | ✓ | | | | | | -| [or_where()](#or_where) | ✓ | | | | | | -| [or_where_in()](#or_where_in) | ✓ | | | | | | -| [or_where_not_in()](#or_where_not_in) | ✓ | | | | | | -| [or_like()](#or_like) | ✓ | | | | | | -| [or_not_like()](#or_not_like) | ✓ | | | | | | -| [not_like()](#not_like) | ✓ | | | | | | -| [or_having()](#or_having) | ✓ | | | | | | -| [count_all_results()](#count_all_results) | ✓ | | | | | | -| [insert_batch()](#insert_batch) | ✓ | | | | | | -| [update_batch()](#update_batch) | ✓ | | | | | | -| [query()](#query) | ✓ | | | | | | -| [last_query()](#last_query) | ✓ | | | | | | +Chainable Methods +----------------- + +| API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :------------------------------------ | :------------ | :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| [select()](#select) | SELECT | ✓ | | | | | | +| [distinct()](#distinct) | DISTINCT | ✓ | | | | | | +| [select_min()](#min) | MIN | ✓ | | | | | | +| [select_max()](#max) | MAX | ✓ | | | | | | +| [select_avg()](#avg) | AVG | ✓ | | | | | | +| [select_sum()](#sum) | SUM | ✓ | | | | | | +| [from()](#from) | FROM | ✓ | | | | | | +| [join()](#join) | JOIN | ✓ | | | | | | +| [where()](#where) | WHERE | ✓ | | | | | | +| [where_not_in()](#where_not_in) | WHERE | ✓ | | | | | | +| [or_where()](#or_where) | WHERE | ✓ | | | | | | +| [or_where_in()](#or_where_in) | WHERE | ✓ | | | | | | +| [or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | | +| [like()](#like) | LIKE | ✓ | | | | | | +| [or_like()](#or_like) | LIKE | ✓ | | | | | | +| [or_not_like()](#or_not_like) | LIKE | ✓ | | | | | | +| [not_like()](#not_like) | LIKE | ✓ | | | | | | +| [where_in()](#where_in) | IN | ✓ | | | | | | +| [group_by()](#group-by) | GROUP BY | ✓ | | | | | | +| [having()](#having) | HAVING | ✓ | | | | | | +| [or_having()](#or_having) | HAVING | ✓ | | | | | | +| [order_by()](#order-by) | ORDER BY | ✓ | | | | | | +| [limit()](#limit) | LIMIT | ✓ | | | | | | +| [offset()](#offset) | OFFSET | ✓ | | | | | | +| [set()](#set) | SET | ✓ | | | | | | ------------- @@ -796,6 +779,8 @@ qb.select('first_name') .get('users',callback); ``` +------------- + ### GROUP BY #### .group_by(fields) @@ -821,6 +806,8 @@ Group by multiple fields: qb.group_by(['department_id','position_id']).get('users',callback); ``` +------------- + ### HAVING #### .having(field,value) @@ -887,6 +874,8 @@ qb.group_by('id') .count('star_systems',callback); ``` +------------- + ### ORDER BY #### .order_by(field[,direction]) @@ -950,6 +939,8 @@ Pass a raw comma-seperated string of field + directions in first parameter and o qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies',callback); ``` +------------- + ### LIMIT #### .limit(limit_to,offset) @@ -974,6 +965,8 @@ You can provide an option offset value instead of calling [.offset()](#offset) s qb.limit(5,5).get('users',callback); ``` +------------- + ### OFFSET #### .offset(offset) @@ -992,6 +985,243 @@ The practical uses of this method are probably miniscule since the `.limit()` me qb.limit(5).offset(25).get('users',callback); ``` +------------- + +Execution Methods +----------------- + +| API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :-------------------------------- | :------------ | :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| [query()](#query) | N/A | ✓ | | | | | | +| [get()](#get) | N/A | ✓ | | | | | | +| [get_where()](#get_where) | N/A | ✓ | | | | | | +| [count()](#count) | COUNT | ✓ | | | | | | +| [update()](#update) | UPDATE | ✓ | | | | | | +| [update_batch()](#update_batch) | N/A | ✓ | | | | | | +| [insert()](#insert) | INSERT | ✓ | | | | | | +| [insert_batch()](#insert_batch) | N/A | ✓ | | | | | | +| [insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | | +| [delete()](#delete) | DELETE | ✓ | | | | | | + +### What are "Execution Methods"?? + +Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond positively with resultset/boolean depending on the type of query being executed or negatively with an error message. Both (if provided) will be supplied to the callback function. + +### Handling Error Messages and Results + +The final parameter of every execution method will be a callback function. The parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected (all others) in any other scenario. + + +#### Callback Example + +```javascript +var callback = function(err, response) { + if (err) { + console.error(err); + } else { + for (var i in response) { + var row = response[i]; + /* + Do something with each row... + */ + } + } +}; +qb.get('foo',callback); +``` + +------------- + +### .query(query_string,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------ | +| query_string | String | Required | Query to send directly to your database driver | +| callback | Function | Required | What to do when the driver has responded | + +This method bypasses the entire QueryBuilder portion of this module is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. + +There are scenarios when using this method may be required; for instance, if you need to run a very specific type of command on your database that is not typical of a standard, CRUD-type query (ex. user permissions or creating a view). + +**Example** + +```javascript +var sql = qb.select(['f.foo','b.bar']).from('foo f').join('bar b','b.foo_id=f.id','left').get_compiled_select(); +qb.query("CREATE VIEW `foobar` AS " + sql, callback); +``` + +------------- + +### .get(table,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | +| callback | Function | Required | What to do when the driver has responded | + +This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. + +**Type of Response** + +Array of rows + +**Examples** + +If you want to provide a table name into the first parameter: + +```javascript +// SELECT * FROM `galaxies` +qb.get('galaxies', callback); +``` + +If you already have the table added to the query: + +```javascript +// SELECT * FROM `galaxies` +qb.from('galaxies').get(callback); +``` + +Just a more-complicated example for the sake of it: + +```javascript +/** + * SELECT + * `g`.`name`, + * `g`.`diameter`, + * `g`.`type_id`, + * `gt`.`name` AS `type`, + * COUNT(`s`.`id`) as `num_stars` + * FROM `galaxies` `g` + * LEFT JOIN `galaxy_types` `gt` ON `gt`.`id`=`g`.`type_id` + * LEFT JOIN `stars` `s` ON `s`.`galaxy_id`=`g`.`id` + * GROUP BY `g`.`id` + * ORDER BY `g`.`name` ASC + * LIMIT 10 + **/ +qb.limit(10) + .select(['g.name','g.diameter','gt.name as type']) + .select('COUNT(`s`.`id`) as `num_stars`',null,false) + .from('galaxies g') + .join('galaxy_types gt','gt.id=g.type_id','left') + .join('stars s','s.galaxy_id=g.id','left') + .group_by('g.id') + .order_by('g.name','asc') + .get(function(err, response { + if (err) return console.error(err); + + for (var i in response) { + var row = response[i]; + console.log("The " + row.name + " is a " + row.diameter + " lightyear-wide " + row.type + " galaxy with " + row.num_stars + " stars."); + } + }); +``` + +------------- + +### .get_where(table,where,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Required | (optional) Used to avoid having to call `.from()` seperately. | +| where | Object | Required | (optional) Used to avoid having to call `.where()` seperately | +| callback | Function | Required | What to do when the driver has responded. | + +This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. + +**Type of Response** + +Array of rows + +**Examples** + +Basic example: + +```javascript +// SELECT * FROM `galaxies` WHERE `num_stars` > 100000000 +qb.get('galaxies', {'num_stars >': 100000000}, callback); +``` + +You can still provide other where statements if you want—they'll all work hapilly together: + +```javascript +// SELECT * FROM `galaxies` WHERE `num_stars` > 100000000 AND `galaxy_type_id` = 3 +qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, callback); +``` + +------------- + +### .count(table,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Required | (optional) Used to avoid having to call `.from()` seperately. | +| callback | Function | Required | What to do when the driver has responded. | + +This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. + +**Type of Response** + +Integer + +**Examples** + +```javascript +// SELECT COUNT(*) AS `count` FROM `galaxies` WHERE `type` = 3 +var type = 3; +qb.where('type',type).count('galaxies', function(err, count) { + if (err) return console.error(err); + console.log("There are " + count + " Type " + type + " galaxies in the Universe."); +}); +``` + +------------- + +### .update(table,data,where,callback) + +------------- + +### .update_batch(table,dataset,where,callback) + +------------- + +### .insert(table,data,callback) + +------------- + +### .insert_batch(table,dataset,callback) + +------------- + +### .insert_ignore(table,data,callback) + +------------- + +### .delete(table,where,callback) + +------------- + +Other Library-Specifc Methods +----------------------------- + +These are methods that aren't part of the query-building chain, but, rather, methods you might call before, after, or during (but not as part of) building a query. + +| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | +| :-------------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| [last_query()](#last_query) | ✓ | | | | | | +| [escape()](#escape) | ✓ | | | | | | +| [get_compiled_select()](#get_compiled_select) | ✓ | | | | | | +| [get_compiled_insert()](#get_compiled_select) | ✓ | | | | | | +| [get_compiled_update()](#get_compiled_select) | ✓ | | | | | | +| [get_compiled_delete()](#get_compiled_select) | ✓ | | | | | | + + +### .last_query() + +------------- + +### .escape(value) + + Contribute ========== From 66aa42f72397a2521416f413f9bec2b06fdce592 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 8 Oct 2014 16:12:08 -0400 Subject: [PATCH 039/200] Added the scaffolding for the rest of the API documentation. --- README.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b26946d..20c2848 100755 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ The primary benefits of this module (currently) are: * Ability to write queries agnostically to the database you intend to query * Supports all basic database commands (insert, update, delete, select, etc...) -* Extend commands from the most popular native database drivers in NPM. +* Extend capabilities from the most popular native database drivers in NPM. * Supports method chaining * Automatically escapes field values * Is fully unit tested +* **Very thoroughly documented** * Allows for greater flexibility and more control over a full ORM * Ligher-weight than an ORM * Allows you to drop down to the native methods of your driver if you choose @@ -1046,7 +1047,10 @@ There are scenarios when using this method may be required; for instance, if you **Example** ```javascript -var sql = qb.select(['f.foo','b.bar']).from('foo f').join('bar b','b.foo_id=f.id','left').get_compiled_select(); +var sql = qb.select(['f.foo','b.bar']) + .from('foo f') + .join('bar b','b.foo_id=f.id','left') + .get_compiled_select(); qb.query("CREATE VIEW `foobar` AS " + sql, callback); ``` @@ -1111,7 +1115,9 @@ qb.limit(10) for (var i in response) { var row = response[i]; - console.log("The " + row.name + " is a " + row.diameter + " lightyear-wide " + row.type + " galaxy with " + row.num_stars + " stars."); + console.log("The " + row.name + " is a " + row.diameter + + " lightyear-wide " + row.type + " galaxy with " + + row.num_stars + " stars."); } }); ``` @@ -1210,9 +1216,9 @@ These are methods that aren't part of the query-building chain, but, rather, met | [last_query()](#last_query) | ✓ | | | | | | | [escape()](#escape) | ✓ | | | | | | | [get_compiled_select()](#get_compiled_select) | ✓ | | | | | | -| [get_compiled_insert()](#get_compiled_select) | ✓ | | | | | | -| [get_compiled_update()](#get_compiled_select) | ✓ | | | | | | -| [get_compiled_delete()](#get_compiled_select) | ✓ | | | | | | +| [get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | | +| [get_compiled_update()](#get_compiled_update) | ✓ | | | | | | +| [get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | | ### .last_query() @@ -1221,6 +1227,23 @@ These are methods that aren't part of the query-building chain, but, rather, met ### .escape(value) +------------- + +### .get_compiled_select() + +------------- + +### .get_compiled_insert() + +------------- + +### .get_compiled_update() + +------------- + +### .get_compiled_delete() + +------------- Contribute ========== From d8272ad8b08ee306aba2b8514f529bb66e624d61 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 9 Oct 2014 14:44:08 -0400 Subject: [PATCH 040/200] Added documentation for the update method. --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++----- index.js | 1 + 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20c2848..e109154 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Universal QueryBuilder for Node.js Node-QueryBuilder is an ambitious attempt to create a kind of "universal translator" which provides programmers a consistent API to connect to and query *any* database (traditional and NoSQL) supported by the module. The module is highly extensible and, in theory, can suppport any database provided that the driver has been written for it. -The API of this module very closely mimics Codeigniter's Active Record (now called "Query Builder") library and much of the code has been directly translated from the PHP libraries in Codeigniter to JavaScript. A lot of credit needs to go to he folks over at EllisLab (https://ellislab.com/codeigniter) and all the contributors to the Codeigniter project (of which I am one): https://github.com/EllisLab/CodeIgniter/ +The API of this module very closely mimics Codeigniter's Active Record (now called "Query Builder") library and much of the code has been directly translated from the PHP libraries in Codeigniter to JavaScript. A lot of credit needs to go to the folks over at EllisLab (https://ellislab.com/codeigniter) and all the contributors to the Codeigniter project (of which I am one): https://github.com/EllisLab/CodeIgniter/ The primary benefits of this module (currently) are: @@ -11,12 +11,12 @@ The primary benefits of this module (currently) are: * Supports all basic database commands (insert, update, delete, select, etc...) * Extend capabilities from the most popular native database drivers in NPM. * Supports method chaining -* Automatically escapes field values +* Automatically escapes field values and identifiers by default * Is fully unit tested * **Very thoroughly documented** * Allows for greater flexibility and more control over a full ORM * Ligher-weight than an ORM -* Allows you to drop down to the native methods of your driver if you choose +* Allows you to drop down to the native methods of your driver if you choose to * Allows for different drivers for different versions (SQLite 2 vs SQLite 3) * The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. * Can used as a learning tool/Rosetta stone @@ -44,7 +44,7 @@ Database Drivers Currently Written: ------------------ -* MySQL +* MySQL / MariaDB Coming Soon: ------------ @@ -83,7 +83,7 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'standard' qb.select('name','position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', function(err,response) { - if (err) console.error("Uh oh! Couldn't get results: " + err.msg); + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 console.log("Query Ran: " + qb.last_query()); @@ -194,6 +194,8 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); ``` + + API Methods =========== @@ -1184,6 +1186,51 @@ qb.where('type',type).count('galaxies', function(err, count) { ### .update(table,data,where,callback) +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Required | The table/collection you'd like to update | +| data | Object | Required | The data to update (ex. {field: value}) | +| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately | +| callback | Function | Required | What to do when the driver has responded. | + +This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... + +**Type of Response** + +Object containing infomration about the results of the query. + +**Examples** + +Here's a contrived example of how it might be used in an app made with the Express framework: + +```javascript +var express = require('express'); +var app = express(); +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); + +app.post('/update_account', function(req, res) { + var user_id = req.session.user_id; + var data = { + first_name: sanitize_name(req.body.first_name), + last_name: sanitize_name(req.body.last_name), + age: sanitize_age(req.body.last_name), + bio: sanitize_bio(req.body.bio), + }; + + pool.get_connection(function(err, qb) { + qb.update('users', data, {id:user_id}, function(err, res) { + if (err) return console.error(err); + + var page_data = { + prefill: data, + } + return res.render('/account_updated', page_data); + }); + }); +}); +``` + ------------- ### .update_batch(table,dataset,where,callback) @@ -1213,6 +1260,7 @@ These are methods that aren't part of the query-building chain, but, rather, met | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | | :-------------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | +| [get_connection()](#get_connection) | ✓ | | | | | | | [last_query()](#last_query) | ✓ | | | | | | | [escape()](#escape) | ✓ | | | | | | | [get_compiled_select()](#get_compiled_select) | ✓ | | | | | | @@ -1221,6 +1269,12 @@ These are methods that aren't part of the query-building chain, but, rather, met | [get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | | +### .get_connection(callback) + +Used to get a new connection from the connection pool or cluster pool. + +------------- + ### .last_query() ------------- diff --git a/index.js b/index.js index 1569cbb..51ec369 100755 --- a/index.js +++ b/index.js @@ -209,6 +209,7 @@ var QueryBuilder = function(settings,driver,type) { } get_adapter(this); + this.get_connection = this.adapter.get_connection; this.disconnect = this.adapter.disconnect; this.destroy = this.adapter.destroy; this.escape = this.adapter.escape; From 5afc3adc9d2e90ff3d5c49ed58d00cb3fb4f561e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:03:08 -0500 Subject: [PATCH 041/200] Clarified some of the documentation. Added tests for get(). Fixed the get() method in the MySQL QueryBuilder class. --- .gitignore | 0 README.md | 158 +++++++++++++++++---------------- drivers/drivers.json | 14 +-- drivers/mysql/connect.js | 31 +++---- drivers/mysql/query_builder.js | 3 +- drivers/mysql/query_exec.js | 72 ++++++++------- index.js | 9 +- test/mysql/tests-get.js | 133 +++++++++++++++++++++++++++ tests.md | 0 9 files changed, 273 insertions(+), 147 deletions(-) mode change 100644 => 100755 .gitignore create mode 100755 test/mysql/tests-get.js mode change 100644 => 100755 tests.md diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md index e109154..447ec6d 100755 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ Licensed under the GPL license and MIT: Quick Example ============= +This quick example shows how to connect to and asynchronously query a MySQL database using a single connection. + ```javascript var settings = { host: 'localhost', @@ -78,9 +80,9 @@ var settings = { user: 'myuser', password: 'MyP@ssw0rd' }; -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'standard'); +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); -qb.select('name','position') +qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', function(err,response) { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); @@ -100,8 +102,8 @@ Connecting to Your Database Quick Reference --------------- -| Driver | Default | Active | standard | pool | cluster | Additional Connection Options | -| :-------- | :------ | :----- | :------- | :---- | :---- | :-------------------------------------------------------- | +| Driver | Default | Ready | single | pool | cluster | Additional Connection Options | +| :-------- | :------ | :----- | :-------- | :---- | :---- | :-------------------------------------------------------- | | mysql | ✓ | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | | mssql | | No | Yes | ??? | ??? | | | sqlite | | No | Yes | ??? | ??? | | @@ -115,13 +117,13 @@ Standard Connection Settings The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. -| Option | Default | Description | -| :-------- | :----- | :-------------------------------------------- | -| host | localhost | The server you're connecting to | -| user | NULL | The database user | -| password | NULL | The database `user`'s password | -| database | NULL | The database to connect to | -| pool_size | 10 | Max connections for `pool` connection type | +| Option | Default | Optional | Description | +| :-------- | :-------- | :-------- | :-------------------------------------------- | +| host | localhost | No | The server you're connecting to | +| user | NULL | No | The database user | +| password | NULL | Yes | The database `user`'s password | +| database | NULL | Yes | The database to connect to | +| pool_size | 10 | Yes | Max connections for `pool` connection type | The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. @@ -143,6 +145,7 @@ We'll call this `db.json`. ```javascript var settings = require('db.json'); +// Second and third parameters of the QueryBuilder method default to 'mysql' and 'standard', respectively var qb = require('node-querybuilder').QueryBuilder(settings); ``` @@ -176,7 +179,7 @@ Choosing the Connection Type This library currently supports 3 connection methods: -* standard (default) +* single (default) * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. * **All drivers must have this connection type**. * pool @@ -204,6 +207,8 @@ API Methods Chainable Methods ----------------- +Chainable methods can be called as many times as you'd like in any order you like. The final query will not be built and executed until one of the [execution methods](#execution-methods), like `get()`, are callled. As the name implies, the methods can be chained together indefinitely but this is not required. You definitely call them individually with the same effect at execution time. + | API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | | :------------------------------------ | :------------ | :-------: | :---: | :-------: | :-------: | :-------: | :---: | | [select()](#select) | SELECT | ✓ | | | | | | @@ -252,36 +257,37 @@ The fields provided to this method will be automatically escaped by the database * String with fields seperated by a comma: * `.select('foo, bar, baz')` * Array of field names - * `.select(['foo','bar','baz'])` + * `.select(['foo', 'bar', 'baz'])` **Examples** -`.select()` is not called ('*' assumed) - ```javascript // SELECT * FROM galaxies -qb.get('galaxies',callback); +qb.select('*').get('foo',callback); + +// Easier and same result: +qb.get('foo',callback); ``` An array of field names: ```javascript // SELECT `foo`, `bar`, `baz` -qb.select(['foo','bar','baz']); +qb.select(['foo', 'bar', 'baz']); ``` You can chain the method together using different patterns if you want: ```javascript // SELECT `foo`, `bar`, `baz`, `this`, `that`, `the_other` -qb.select(['foo','bar','baz']).select('this,that,the_other'); +qb.select(['foo', 'bar', 'baz']).select('this,that,the_other'); ``` You can alias your field names and they will be escaped properly as well: ```javascript // SELECT `foo` as `f`, `bar` as `b`, `baz` as `z` -qb.select(['foo as f','bar as b','baz as z']); +qb.select(['foo as f', 'bar as b', 'baz as z']); ``` You can optionally choose not to have the driver auto-escape the fieldnames (dangerous, but useful if you a utilize function in your select statement, for instance): @@ -332,7 +338,7 @@ You can optionally include a second parameter to rename the resulting field ```javascript // SELECT MIN(`age`) AS `min_age` FROM `users` -qb.select_min('age','min_age').get('users',callback); +qb.select_min('age', 'min_age').get('users',callback); ``` ------------- @@ -358,7 +364,7 @@ You can optionally include a second parameter to rename the resulting field ```javascript // SELECT MAX(`age`) AS `max_age` FROM `users` -qb.select_max('age','max_age').get('users',callback); +qb.select_max('age', 'max_age').get('users',callback); ``` ------------- @@ -384,7 +390,7 @@ You can optionally include a second parameter to rename the resulting field ```javascript // SELECT AVG(`age`) AS `avg_age` FROM `users` -qb.select_avg('age','avg_age').get('users',callback); +qb.select_avg('age', 'avg_age').get('users',callback); ``` ------------- @@ -410,7 +416,7 @@ You can optionally include a second parameter to rename the resulting field ```javascript // SELECT SUM(`age`) AS `sum_age` FROM `users` -qb.select_sum('age','sum_age').get('users',callback); +qb.select_sum('age', 'sum_age').get('users',callback); ``` ------------- @@ -502,7 +508,7 @@ If no direction is specified, "left" will be used: // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') - .join('types t','t.id=u.type_id') + .join('types t', 't.id=u.type_id') .get(callback); ``` @@ -513,7 +519,7 @@ You may specify a direction: // FROM `users` `u` // RIGHT OUTER JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') - .join('types t','t.id=u.type_id','right outer') + .join('types t', 't.id=u.type_id', 'right outer') .get(callback); ``` @@ -524,10 +530,10 @@ Multiple function calls can be made if you need several joins in one query: // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` // LEFT JOIN `locations` `l` ON `l`.`id`=`u`.`location_id` -var select = ['u.id','u.name','t.name as type','l.name as location']; +var select = ['u.id', 'u.name', 't.name as type', 'l.name as location']; qb.select(select).from('users u') - .join('types t','t.id=u.type_id','right outer') - .join('locations l','l.id=u.location_id','left') + .join('types t', 't.id=u.type_id', 'right outer') + .join('locations l', 'l.id=u.location_id', 'left') .get(callback); ``` @@ -555,7 +561,7 @@ If you just want to pass a single filter at a time: ```javascript // SELECT `galaxy` FROM `universe` WHERE `planet_name` = 'Earth' -qb.select('galaxy').where('planet_name','Earth').get('universe',callback); +qb.select('galaxy').where('planet_name', 'Earth').get('universe',callback); ``` If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: @@ -583,9 +589,9 @@ You can pass a non-empty array as a value and that portion will be treated as a ```javascript // SELECT `star_system` FROM `star_systems` -// WHERE `planet_count` >= 4, `star` IN('Sun','Betelgeuse') +// WHERE `planet_count` >= 4, `star` IN('Sun', 'Betelgeuse') qb.select('star_system') - .where({'planet_count >=': 4, star: ['Sun','Betelgeuse']) + .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']) .get('star_systems',callback); ``` @@ -597,8 +603,8 @@ This method functions identically to [.where()](#where) except that it joins cla ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` = 'Sun' OR `star` = 'Betelgeuse' -qb.select('star_system').where('star','Sun') - .or_where('star','Betelgeuse') +qb.select('star_system').where('star', 'Sun') + .or_where('star', 'Betelgeuse') .get('star_systems',callback); ``` @@ -609,8 +615,8 @@ This will create a "WHERE IN" statement in traditional SQL which is useful when ```javascript // SELECT `star_system` FROM `star_systems` -// WHERE `star` IN('Sun','Betelgeuse','Sirius','Vega','Alpha Centauri') -var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; +// WHERE `star` IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') +var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_in('star',stars).get('star_systems',callback); ``` @@ -621,8 +627,8 @@ Same as `.where_in()` except the clauses are joined by 'OR'. ```javascript // SELECT `star_system` FROM `star_systems` -// WHERE `planet_count` = 4 OR `star` IN('Sun','Betelgeuse') -var stars = ['Sun','Betelgeuse']; +// WHERE `planet_count` = 4 OR `star` IN('Sun', 'Betelgeuse') +var stars = ['Sun', 'Betelgeuse']; qb.select('star_system').where('planet_count',4) .or_where_in('star',stars) .get('star_systems',callback); @@ -635,8 +641,8 @@ Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clau ```javascript // SELECT `star_system` FROM `star_systems` -// WHERE `star` NOT IN('Sun','Betelgeuse','Sirius','Vega','Alpha Centauri') -var stars = ['Sun','Betelgeuse','Sirius','Vega','Alpha Centauri']; +// WHERE `star` NOT IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') +var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); ``` @@ -647,9 +653,9 @@ Same as `.where_not_in()` except that clauses are joined with 'OR'. ```javascript // SELECT `star_system` FROM `star_systems` -// WHERE `star` NOT IN('Sun','Betelgeuse') +// WHERE `star` NOT IN('Sun', 'Betelgeuse') // OR `planet_count` NOT IN [2,4,6,8] -var stars = ['Sun','Betelgeuse']; +var stars = ['Sun', 'Betelgeuse']; var planet_sizes = [2,4,6,8]; qb.select('star_system') .where_not_in('star',stars) @@ -680,7 +686,7 @@ By default, the match string will be wrapped on both sides with the wildcard (%) ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber%' // Potential results: [{first_name: 'Kimberly'},{first_name: 'Amber'}] -qb.select('first_name').like('first_name','mber').get('users',callback); +qb.select('first_name').like('first_name', 'mber').get('users',callback); ``` You can specify a side to place the wildcard (%) on if you'd like (before/left, after/right, both): @@ -688,11 +694,11 @@ You can specify a side to place the wildcard (%) on if you'd like (before/left, ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber' // Potential results: [{first_name: 'Amber'}] -qb.select('first_name').like('first_name','mber','before').get('users',callback); +qb.select('first_name').like('first_name', 'mber', 'before').get('users',callback); // SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'Kim%' // Potential results: [{first_name: 'Kim'},{first_name: 'Kimberly'}] -qb.select('first_name').like('first_name','Kim','right').get('users',callback); +qb.select('first_name').like('first_name', 'Kim', 'right').get('users',callback); ``` You can also pass 'none' if you don't want to use the wildcard (%) @@ -700,7 +706,7 @@ You can also pass 'none' if you don't want to use the wildcard (%) ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'kim' // Potential results: [{first_name: 'Kim'}] -qb.select('first_name').like('first_name','kim','none').get('users',callback); +qb.select('first_name').like('first_name', 'kim', 'none').get('users',callback); ``` If you'd like to have multiple like clauses, you can do that by calling like multiple times: @@ -711,9 +717,9 @@ If you'd like to have multiple like clauses, you can do that by calling like mul // AND `middle_name` LIKE '%lyt%' // AND `last_name` LIKE '%arris' qb.select('first_name') - .like('first_name','Kim','right') - .like('middle_name','lyt') - .like('last_name','arris','left') + .like('first_name', 'Kim', 'right') + .like('middle_name', 'lyt') + .like('last_name', 'arris', 'left') .get('users',callback); ``` @@ -742,9 +748,9 @@ This is exactly the same as the `.like()` method except that the clauses are joi // OR `middle_name` LIKE '%lyt%' // OR `last_name` LIKE '%arris' qb.select('first_name') - .or_like('first_name','Kim','right') - .or_like('middle_name','lyt') - .or_like('last_name','arris','left') + .or_like('first_name', 'Kim', 'right') + .or_like('middle_name', 'lyt') + .or_like('last_name', 'arris', 'left') .get('users',callback); ``` @@ -806,7 +812,7 @@ Group by multiple fields: ```javascript // SELECT * FROM `users` GROUP BY `department_id`, `position_id` -qb.group_by(['department_id','position_id']).get('users',callback); +qb.group_by(['department_id', 'position_id']).get('users',callback); ``` ------------- @@ -911,28 +917,28 @@ Pass the field name and the direction as the first and second parameters, respec ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC -qb.order_by('galaxy_name','desc').get('galaxies',callback); +qb.order_by('galaxy_name', 'desc').get('galaxies',callback); ``` Pass an array of fields to first paramter, direction to second parameter ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` DESC -qb.order_by(['galaxy_name','galaxy_size'],'desc').get('galaxies',callback); +qb.order_by(['galaxy_name', 'galaxy_size'],'desc').get('galaxies',callback); ``` Pass an array of fields + directions in first parameter and ommit the second one. ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC -qb.order_by(['galaxy_name desc','galaxy_size asc']).get('galaxies',callback); +qb.order_by(['galaxy_name desc', 'galaxy_size asc']).get('galaxies',callback); ``` Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC -qb.order_by(['galaxy_name desc','galaxy_size'],'asc').get('galaxies',callback); +qb.order_by(['galaxy_name desc', 'galaxy_size'],'asc').get('galaxies',callback); ``` Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. @@ -1012,13 +1018,14 @@ Execution methods are the end-of-chain methods in the QueryBuilder library. Once ### Handling Error Messages and Results -The final parameter of every execution method will be a callback function. The parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected (all others) in any other scenario. +The final parameter of every execution method will be a callback function. The parameters for the callback are in the `node.js` standard `(err, response)` format. When you working with `pool` and `cluster` type connections, a third paramter will be passed containing the `connection` object—you would use this to release the connection when you're done with it. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected (all others) in any other scenario. #### Callback Example ```javascript -var callback = function(err, response) { +var callback = function(err, response, connection) { + connection.release(); // if you're working with a pool or cluster... if (err) { console.error(err); } else { @@ -1042,16 +1049,16 @@ qb.get('foo',callback); | query_string | String | Required | Query to send directly to your database driver | | callback | Function | Required | What to do when the driver has responded | -This method bypasses the entire QueryBuilder portion of this module is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. +*****This method bypasses the entire QueryBuilder portion of this module***** is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. There are scenarios when using this method may be required; for instance, if you need to run a very specific type of command on your database that is not typical of a standard, CRUD-type query (ex. user permissions or creating a view). **Example** ```javascript -var sql = qb.select(['f.foo','b.bar']) +var sql = qb.select(['f.foo', 'b.bar']) .from('foo f') - .join('bar b','b.foo_id=f.id','left') + .join('bar b', 'b.foo_id=f.id', 'left') .get_compiled_select(); qb.query("CREATE VIEW `foobar` AS " + sql, callback); ``` @@ -1105,14 +1112,14 @@ Just a more-complicated example for the sake of it: * LIMIT 10 **/ qb.limit(10) - .select(['g.name','g.diameter','gt.name as type']) + .select(['g.name', 'g.diameter', 'gt.name as type']) .select('COUNT(`s`.`id`) as `num_stars`',null,false) .from('galaxies g') - .join('galaxy_types gt','gt.id=g.type_id','left') - .join('stars s','s.galaxy_id=g.id','left') + .join('galaxy_types gt', 'gt.id=g.type_id', 'left') + .join('stars s', 's.galaxy_id=g.id', 'left') .group_by('g.id') - .order_by('g.name','asc') - .get(function(err, response { + .order_by('g.name', 'asc') + .get(function(err, response) { if (err) return console.error(err); for (var i in response) { @@ -1207,7 +1214,7 @@ Here's a contrived example of how it might be used in an app made with the Expre var express = require('express'); var app = express(); var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); +var qb = require('node-querybuilder').QueryBuilder(settings,'mysql', 'pool'); app.post('/update_account', function(req, res) { var user_id = req.session.user_id; @@ -1218,15 +1225,14 @@ app.post('/update_account', function(req, res) { bio: sanitize_bio(req.body.bio), }; - pool.get_connection(function(err, qb) { - qb.update('users', data, {id:user_id}, function(err, res) { - if (err) return console.error(err); - - var page_data = { - prefill: data, - } - return res.render('/account_updated', page_data); - }); + qb.update('users', data, {id:user_id}, function(err, res, connection) { + connection.release(); + if (err) return console.error(err); + + var page_data = { + prefill: data, + } + return res.render('/account_updated', page_data); }); }); ``` diff --git a/drivers/drivers.json b/drivers/drivers.json index ce574c1..9093d98 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -1,6 +1,6 @@ { "mysql": { - "connection_types": { "standard": true, "pool": true, "cluster": true }, + "connection_types": { "single": true, "pool": true, "cluster": true }, "versions": { "default": { "path": "./drivers/mysql/", @@ -13,7 +13,7 @@ } }, "mssql": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/mssql/", @@ -22,7 +22,7 @@ } }, "oracle": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/mssql/", @@ -31,7 +31,7 @@ } }, "postgres": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/postgres/", @@ -40,7 +40,7 @@ } }, "sqlite": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/sqlite/", @@ -49,7 +49,7 @@ } }, "sqlite3": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/sqlite3/", @@ -58,7 +58,7 @@ } }, "mongo": { - "connection_types": { "standard": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { "default": { "path": "./drivers/mongo/", diff --git a/drivers/mysql/connect.js b/drivers/mysql/connect.js index 14e721e..f0aa15f 100755 --- a/drivers/mysql/connect.js +++ b/drivers/mysql/connect.js @@ -19,7 +19,7 @@ var Adapter = function(settings,type) { this.debugging = false; this.connection = null; - this.pool = null; + this.connection_pool = null; this.connection_type = 'single'; // Enable debugging if necessary @@ -95,7 +95,12 @@ var Adapter = function(settings,type) { case 'cluster': break; case 'pool': - that.pool.getConnection(function (err, connection) { + if (null === that.connection_pool) { + var error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + that.connection_pool.getConnection(function (err, connection) { if (err) { throw err; } @@ -116,9 +121,9 @@ var Adapter = function(settings,type) { // @return VOID // **************************************************************************** this.pool = function() { - that.pool = mysql.createPool(this.settings); + that.connection_pool = mysql.createPool(this.settings); - that.pool.query('SELECT 1 + 1 AS solution', function(err) { + that.connection_pool.query('SELECT 1 + 1 AS solution', function(err) { if (err) throw err; if (that.debugging === true) { console.log('mysql connection pool created'); @@ -127,25 +132,11 @@ var Adapter = function(settings,type) { return { disconnect: function() { - that.pool().end(responseCallback); + that.connection_pool.end(responseCallback); } } }; - // **************************************************************************** - // Actually executes a query. This is probably the single-most essential method - // in this class! - // ----- - // @param String sql The SQL to execute - // @param Function What to do after the query is executed - // @return VOID - // **************************************************************************** - this.query = function(sql,callback) { - that.get_connection(function(connection) { - connection.query(sql,callback); - }); - }; - // **************************************************************************** // Sets up a standard one-time connection (no pooling). This one is used by the // other two methods to stay DRY. @@ -153,7 +144,7 @@ var Adapter = function(settings,type) { // @param Object settings Connection settings // @return VOID // **************************************************************************** - this.standard = function() { + this.single = function() { that.connection = mysql.createConnection(this.settings); that.connection.connect(function(err) { if (err) { diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index f54fe8f..e6c60d7 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1287,8 +1287,7 @@ var QueryBuilder = function() { }, get: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); + if (typeof table !== 'undefined') { this.from(table); } else { diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index fdc2c27..0ded262 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -5,18 +5,27 @@ // @param Object adapter The connection adapter object // **************************************************************************** var QueryExec = function(qb, adapter) { + var exec = function(sql, callback) { + adapter.get_connection(function(connection) { + connection.query(sql, function(err, rows) { + if (adapter.connection_type === 'standard') { + callback(err, rows); + } else { + callback(err, rows, connection); + } + }); + }); + }; + return { + query: function(sql, callback) { + exec(sql,callback); + }, + count: function(table, callback) { var sql = qb.count(table); - - adapter.get_connection(function(connection) { - connection.query(sql, function(err, res) { - if (err) - callback(err, null); - else - callback(null, res[0]['count']); - }); - }); + qb.reset_query(sql); + exec(sql,callback); }, get: function(table,callback) { @@ -30,41 +39,31 @@ var QueryExec = function(qb, adapter) { var sql = qb.get(table); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, get_where: function(table,where,callback) { var sql = qb.get_where(table,where); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, insert: function(table,set,callback,ignore,suffix) { var sql = qb.insert(table,set,ignore,suffix); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql,callback); - }); + exec(sql,callback); }, insert_ignore: function(table,set,callback) { var sql = qb.insert_ignore(table,set); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, insert_batch: function(table,set,callback) { var sql = qb.insert_batch(table,set); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, update: function(table,set,where,callback) { @@ -81,14 +80,19 @@ var QueryExec = function(qb, adapter) { var sql = qb.update(table,set,where); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, // TODO: Write this complicated-ass function update_batch: function(table,set,where,callback) { - callback(new Error("This function is not currently available!"),null); + adapter.get_connection(function(connection) { + if (adapter.connection_type === 'standard') { + callback(new Error("This function is not currently available!"),null); + } else { + callback(new Error("This function is not currently available!"),null, connection); + } + + }); }, delete: function(table, where, callback) { @@ -110,25 +114,19 @@ var QueryExec = function(qb, adapter) { var sql = qb.delete(table, where); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql, callback); - }); + exec(sql,callback); }, empty_table: function(table, callback) { var sql = qb.empty_table(table,callback); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql,callback); - }); + exec(sql,callback); }, truncate: function(table, callback) { var sql = qb.truncate(table,callback); qb.reset_query(sql); - adapter.get_connection(function(connection) { - connection.query(sql,callback); - }); + exec(sql,callback); }, } } diff --git a/index.js b/index.js index 51ec369..e761d8a 100755 --- a/index.js +++ b/index.js @@ -42,7 +42,7 @@ var QueryBuilder = function(settings,driver,type) { this.settings = settings || {}; this.driver = driver || 'mysql'; - this.connection_type = type || 'standard'; + this.connection_type = type || 'single'; this.drivers = require('./drivers/drivers.json'); this.driver_version = 'default'; this.driver_info = null; @@ -174,7 +174,7 @@ var QueryBuilder = function(settings,driver,type) { // **************************************************************************** var get_connection_type = function(qb) { if (Object.keys(qb.drivers[qb.driver].connection_types).indexOf(qb.connection_type) === -1) { - throw new Error("You have specified a invalid database connection method."); + throw new Error("You have specified a invalid database connection method: " + qb.connection_type); } if (qb.drivers[qb.driver].connection_types[qb.connection_type] !== true) { throw new Error("You cannot connect to a " + qb.driver + " database using the " + qb.connection_type + " connection type using this library."); @@ -209,12 +209,10 @@ var QueryBuilder = function(settings,driver,type) { } get_adapter(this); - this.get_connection = this.adapter.get_connection; this.disconnect = this.adapter.disconnect; this.destroy = this.adapter.destroy; this.escape = this.adapter.escape; this.get_connection_id = this.adapter.get_connection_id; - this.query = this.adapter.query; // **************************************************************************** // Get the the driver's QueryExec object so that queries can actually be @@ -234,6 +232,7 @@ var QueryBuilder = function(settings,driver,type) { get_query_exec(this); // QueryExecute method mappings: + this.query = this.qe.query; this.count = this.qe.count; this.get = this.qe.get; this.get_where = this.qe.get_where; @@ -244,7 +243,7 @@ var QueryBuilder = function(settings,driver,type) { this.update_batch = this.qe.update_batch; this.delete = this.qe.delete; this.empty_table = this.qe.empty_table; - this.truncate = this.qe.truncate; + this.truncate = this.qe.truncate; var that = this; return this; diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js new file mode 100755 index 0000000..9280819 --- /dev/null +++ b/test/mysql/tests-get.js @@ -0,0 +1,133 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +describe('get()', function() { + it('should exist', function() { + should.exist(qb.get); + }); + it('should be a function', function() { + qb.get.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.get('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', function() { + qb.reset_query(); + qb.get(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + }); + it('should return a string', function() { + qb.reset_query(); + var sql = qb.get('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', function() { + qb.reset_query(); + var sql = qb.get('galaxies'); + sql.should.eql('SELECT * FROM (`galaxies`)'); + }); + it('should properly handle alias if provided in table string', function() { + qb.reset_query(); + var sql = qb.get('galaxies g'); + sql.should.eql('SELECT * FROM (`galaxies` `g`)'); + }); + it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', function() { + qb.reset_query(); + var sql = qb.select(['id','name']).get('galaxies'); + sql.should.eql("SELECT `id`, `name` FROM (`galaxies`)"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed conditions', function() { + qb.reset_query(); + var sql = qb.where('class','M').get('galaxies'); + sql.should.eql("SELECT * FROM (`galaxies`) WHERE `class` = 'M'"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', function() { + qb.reset_query(); + var sql = qb.get(['galaxies','star_systems','planets']); + sql.should.eql('SELECT * FROM (`galaxies`, `star_systems`, `planets`)'); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', function() { + qb.reset_query(); + var sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); + sql.should.eql("SELECT * FROM (`galaxies`, `star_systems`, `planets`) WHERE `class` = 'M'"); + }); + it('should use tables added previously via the from() method', function() { + qb.reset_query(); + qb.from('galaxies'); + var sql = qb.get(); + sql.should.eql('SELECT * FROM (`galaxies`)'); + + qb.reset_query(); + var sql = qb.from(['galaxies','star_systems','planets']).get(); + sql.should.eql('SELECT * FROM (`galaxies`, `star_systems`, `planets`)'); + }); + it('should accept where conditions added previously via the where() method', function() { + qb.reset_query(); + var sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); + sql.should.eql("SELECT * FROM (`galaxies`) WHERE `created` >= 4600000000 AND `classification` = 'M'"); + }); + it('should accept a limit on the number of rows selected', function() { + qb.reset_query(); + var sql = qb.limit(20).get('galaxies'); + sql.should.eql("SELECT * FROM (`galaxies`) LIMIT 20"); + }); + it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', function() { + qb.reset_query(); + var sql = qb.limit(20,10).get('galaxies'); + sql.should.eql("SELECT * FROM (`galaxies`) LIMIT 10, 20"); + }); + it('should include the DISTINCT keyword if the distinct() method is called', function() { + qb.reset_query(); + var sql = qb.distinct().select(['id','name']).get('galaxies'); + sql.should.eql("SELECT DISTINCT `id`, `name` FROM (`galaxies`)"); + }); + it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', function() { + qb.reset_query(); + + // MIN + var sql = qb.select_min('size','min_size').get('galaxies'); + sql.should.eql("SELECT MIN(`size`) AS min_size FROM (`galaxies`)"); + qb.reset_query(); + + // MAX + var sql = qb.select_max('size','max_size').get('galaxies'); + sql.should.eql("SELECT MAX(`size`) AS max_size FROM (`galaxies`)"); + qb.reset_query(); + + // AVG + var sql = qb.select_avg('size','avg_size').get('galaxies'); + sql.should.eql("SELECT AVG(`size`) AS avg_size FROM (`galaxies`)"); + qb.reset_query(); + + // SUM + var sql = qb.select_sum('size','total_size').get('galaxies'); + sql.should.eql("SELECT SUM(`size`) AS total_size FROM (`galaxies`)"); + }); + it('should include any joins that were added in the chain', function() { + qb.reset_query(); + var sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) + .join('galaxies g','g.id=s.galaxy_id','left') + .get('star_systems s'); + sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM (`star_systems` `s`) LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + }); + it('should include any GROUP BY statements added using the group_by() method.', function() { + qb.reset_query(); + var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM (`galaxies`) GROUP BY `size`"); + }); + it('should add the ORDER BY clause of the order_by() method was called in the chain', function() { + qb.reset_query(); + var sql = qb.order_by('size').get('galaxies'); + sql.should.eql("SELECT * FROM (`galaxies`) ORDER BY `size` ASC"); + }); + it('should include any HAVING clauses added using the having() method', function() { + qb.reset_query(); + var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM (`galaxies`) GROUP BY `size` HAVING `num_of_size` >= 456034960"); + }); +}); \ No newline at end of file diff --git a/tests.md b/tests.md old mode 100644 new mode 100755 From 41b3ecf9170571794b490fd6df18359ee21f210f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:10:59 -0500 Subject: [PATCH 042/200] Added travis-ci functionality. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..69a491d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" + - "0.8" + - "0.6" \ No newline at end of file From 84b3bcc94578dded51c4654d12dbbe8363ecd8ce Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:12:32 -0500 Subject: [PATCH 043/200] Updated history to reflect addition of travis-ci. --- HISTORY.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 6348e4a..ab38869 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2014-09-24) +## v0.9.0 (2014-11-14) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -31,4 +31,5 @@ you spot any mistakes. * Name officially changed to node-mysql-querybuilder. * Author officially changed to Kyle Farris due to the substantial changes to this fork and Martin Tajur demoted to primary contributor (thanks for the great starting place Martin!!) * Name of internal methods and properties have been normalized to use the "lower_case" syntax. -* Dependency for node-mysql upgraded to 2.5.*. \ No newline at end of file +* Dependency for node-mysql upgraded to 2.5. +* travis-ci functionality added to repository From 185ef7bd3c763d8b02f2e1e4273cfa44de7ed7fe Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:17:33 -0500 Subject: [PATCH 044/200] Fixed travis yaml file. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 69a491d..feb40ba 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js node_js: - - "0.10" - - "0.8" - - "0.6" \ No newline at end of file + - "0.10" + - "0.8" + - "0.6" \ No newline at end of file From a65c51fe245e0900b500988ceaf6750053170bd7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:21:21 -0500 Subject: [PATCH 045/200] Removed compatibility for Node < 0.10 in travis YML. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index feb40ba..fdb4a91 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ language: node_js node_js: - - "0.10" - - "0.8" - - "0.6" \ No newline at end of file + - "0.10" \ No newline at end of file From 57bcd156a93c24d45b339368895574d820c39a6b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 14 Nov 2014 17:27:57 -0500 Subject: [PATCH 046/200] Added travis build tag to README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 447ec6d..a917565 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/kylefarris/node-querybuilder.svg?branch=master)](https://travis-ci.org/kylefarris/node-querybuilder) + Universal QueryBuilder for Node.js ====================================== From 684720a1ee6badad330dbcda0eb609fd8ad07dfa Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 17 Nov 2014 12:09:22 -0500 Subject: [PATCH 047/200] Clarified some documentation and added get_where() to to the unit tests. --- README.md | 24 +++++++++--------- drivers/mysql/query_builder.js | 17 ++++++++----- drivers/mysql/query_exec.js | 6 +++++ test/mysql/tests-from.js | 17 +++++++++++-- test/mysql/tests-get.js | 46 ++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a917565..5a77e87 100755 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ This method is used to specify the fields to pull into the resultset when runnin | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :-------------------------------------------- | -| fields | String/Array | N/A | The fields in which to grab from the database | +| fields | String/Array | Required | The fields in which to grab from the database | | escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | @@ -430,7 +430,7 @@ This SQL command is used to determine which sources, available to the active con | Parameter | Type | Default | Description | | :-------- | :------------ | :-------- | :-------------------------------------------- | -| tables | String/Array | N/A | Table(s), view(s), etc... to grab data from | +| tables | String/Array | Required | Table(s), view(s), etc... to grab data from | You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). @@ -1076,7 +1076,7 @@ qb.query("CREATE VIEW `foobar` AS " + sql, callback); This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. -**Type of Response** +**Type of Response Sent to Callback** Array of rows @@ -1137,15 +1137,15 @@ qb.limit(10) ### .get_where(table,where,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Required | (optional) Used to avoid having to call `.from()` seperately. | -| where | Object | Required | (optional) Used to avoid having to call `.where()` seperately | -| callback | Function | Required | What to do when the driver has responded. | +| Parameter | Type | Default | Description | +| :-------- | :---------------- | :-------- | :------------------------------------------------------------ | +| table | String or Array | Required | Used to avoid having to call `.from()` seperately. | +| where | Object | Required | Used to avoid having to call `.where()` seperately | +| callback | Function | Required | What to do when the driver has responded. | -This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. +This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. -**Type of Response** +**Type of Response Sent to Callback** Array of rows @@ -1176,7 +1176,7 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. -**Type of Response** +**Type of Response Sent to Callback** Integer @@ -1204,7 +1204,7 @@ qb.where('type',type).count('galaxies', function(err, count) { This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... -**Type of Response** +**Type of Response Sent to Callback** Object containing infomration about the results of the query. diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index e6c60d7..3645be2 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -736,6 +736,8 @@ var QueryBuilder = function() { for (var i in from) { var val = from[i]; + if (val.trim() === '') continue; + if (val.indexOf(',') !== -1) { var objects = val.split(','); for (var j in objects) { @@ -1299,17 +1301,20 @@ var QueryBuilder = function() { }, get_where: function(table, where) { + table = table || null; where = where || null; - if (typeof table !== 'string') { - throw new Error('You must specify a table as the first parameter when using the get_where method!'); - return this; - } else { - track_aliases(this,table); + if (table === null || (typeof table === 'string' && table.trim().length === 0)) { + throw new Error('You must specify a table or tables in the first parameter of get_where()'); + } + else { this.from(table); } - if (where !== null) { + if (where === null || Object.keys(where).length === 0) { + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + } + else { this.where(where); } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 0ded262..a7df0c7 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -43,6 +43,12 @@ var QueryExec = function(qb, adapter) { }, get_where: function(table,where,callback) { + if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } var sql = qb.get_where(table,where); qb.reset_query(sql); exec(sql,callback); diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js index 56da175..6de989c 100755 --- a/test/mysql/tests-from.js +++ b/test/mysql/tests-from.js @@ -17,7 +17,7 @@ describe('from()', function() { it('should add an item to an array and escape it properly', function() { qb.from('universe'); qb.from_array.should.eql(['`universe`']); - }); + }) it('should accept a comma-delimited string of items and trim and escape each properly', function() { qb.reset_query(); qb.from('universe,galaxy , star_system, planet'); @@ -105,5 +105,18 @@ describe('from()', function() { qb.reset_query(); qb.from('universe.galaxy.star_system as planet'); qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); - }) + }); + it('should accept but ignore empty strings and empty strings within arrays', function() { + qb.reset_query(); + qb.from(''); + qb.from_array.should.be.empty; + + qb.reset_query(); + qb.from(['','']); + qb.from_array.should.be.empty; + + qb.reset_query(); + qb.from(['','foobar']); + qb.from_array.should.eql(['`foobar`']); + }); }); \ No newline at end of file diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index 9280819..c2625c8 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -130,4 +130,50 @@ describe('get()', function() { var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM (`galaxies`) GROUP BY `size` HAVING `num_of_size` >= 456034960"); }); +}); + +describe('get()', function() { + it('should exist', function() { + should.exist(qb.get_where); + }); + it('should be a function', function() { + qb.get_where.should.be.a('function'); + }); + it('should require the first parameter to be a table in string format or tables array format', function() { + qb.reset_query(); + expect(function() { qb.get_where(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.get_where(''); }, 'empty string for table').to.throw(Error); + expect(function() { qb.get_where([]); }, 'empty array for tables').to.throw(Error); + expect(function() { qb.get_where(['']); }, 'array of empty strings for tables').to.throw(Error); + expect(function() { qb.get_where(1); }, 'integer for table').to.throw(Error); + expect(function() { qb.get_where(5.5); }, 'float for table').to.throw(Error); + expect(function() { qb.get_where(true); }, 'TRUE for table').to.throw(Error); + expect(function() { qb.get_where(false); }, 'FALSE for table').to.throw(Error); + expect(function() { qb.get_where(null); }, 'NULL for table').to.throw(Error); + expect(function() { qb.get_where({}); }, 'Standard object for table').to.throw(Error); + expect(function() { qb.get_where(Infinite); }, 'Infinite for table').to.throw(Error); + expect(function() { qb.get_where('galaxies'); }, 'valid table, no where').to.throw(Error); + expect(function() { qb.get_where('galaxies',{}); }, 'valid table, empty where').to.throw(Error); + expect(function() { qb.get_where('galaxies',[]); }, 'valid table, array for where').to.throw(Error); + expect(function() { qb.get_where('galaxies',3); }, 'valid table, integer for where').to.throw(Error); + expect(function() { qb.get_where('galaxies',33.3); }, 'valid table, float for where').to.throw(Error); + expect(function() { qb.get_where('galaxies','foo'); }, 'valid table, string for where').to.throw(Error); + expect(function() { qb.get_where('galaxies',Infinite); }, 'valid table, Infinite where').to.throw(Error); + expect(function() { qb.get_where('galaxies',null); }, 'valid table, NULL where').to.throw(Error); + expect(function() { qb.get_where('galaxies',{id: 3}); }, 'valid table, valid where').to.not.throw(Error); + }); + it('should return a string', function() { + qb.reset_query(); + var sql = qb.get('galaxies', {type: 'spiral'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should add table(s) to from_array and where items to where_array', function() { + qb.reset_query(); + var sql = qb.get_where('galaxies', {type: 'spiral'}); + qb.from_array.should.eql(['`galaxies`']); + qb.where_array.should.eql(["`type` = 'spiral'"]); + sql.should.eql("SELECT * FROM (`galaxies`) WHERE `type` = 'spiral'"); + }); }); \ No newline at end of file From 7abf493f5cf2be29cdb561f21ec86c44e2bec5db Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 17 Nov 2014 12:10:17 -0500 Subject: [PATCH 048/200] Fixed a typo in the test-get unit tests. --- test/mysql/tests-get.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index c2625c8..d64b78a 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -132,7 +132,7 @@ describe('get()', function() { }); }); -describe('get()', function() { +describe('get_where()', function() { it('should exist', function() { should.exist(qb.get_where); }); From f2f1cef01ede7d571aa534cc0936bce62839b58a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 17 Nov 2014 15:31:51 -0500 Subject: [PATCH 049/200] Updated/fixed documentation, fixed count() methods in builder and exec for MySQL, wrote tests count(). --- README.md | 12 ++++---- drivers/mysql/query_builder.js | 27 ++++++++--------- drivers/mysql/query_exec.js | 16 ++++++++-- test/mysql/tests-count.js | 55 ++++++++++++++++++++++++++++++++++ test/mysql/tests-get.js | 4 ++- 5 files changed, 92 insertions(+), 22 deletions(-) create mode 100755 test/mysql/tests-count.js diff --git a/README.md b/README.md index 5a77e87..14038a3 100755 --- a/README.md +++ b/README.md @@ -1147,7 +1147,7 @@ This method is basically the same as the `.get()` method except that if offers a **Type of Response Sent to Callback** -Array of rows +Array of objects representing the result rows. **Examples** @@ -1155,7 +1155,7 @@ Basic example: ```javascript // SELECT * FROM `galaxies` WHERE `num_stars` > 100000000 -qb.get('galaxies', {'num_stars >': 100000000}, callback); +qb.get_where('galaxies', {'num_stars >': 100000000}, callback); ``` You can still provide other where statements if you want—they'll all work hapilly together: @@ -1171,11 +1171,13 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Required | (optional) Used to avoid having to call `.from()` seperately. | +| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | | callback | Function | Required | What to do when the driver has responded. | This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. +The field in the resultset will always labeled be 'numrows'. + **Type of Response Sent to Callback** Integer @@ -1183,11 +1185,11 @@ Integer **Examples** ```javascript -// SELECT COUNT(*) AS `count` FROM `galaxies` WHERE `type` = 3 +// SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 3 var type = 3; qb.where('type',type).count('galaxies', function(err, count) { if (err) return console.error(err); - console.log("There are " + count + " Type " + type + " galaxies in the Universe."); + console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); }); ``` diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 3645be2..d351d0f 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -715,20 +715,6 @@ var QueryBuilder = function() { return this; }, - count: function(table) { - if (typeof table === 'string') { - track_aliases(this,table); - this.from(table); - } - var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'count') - + build_from_clause(this) - + build_join_string(this) - + build_where_clause(this) - + build_having_clause(this) - - return sql; - }, - from: function(from) { if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { from = [from]; @@ -1321,6 +1307,19 @@ var QueryBuilder = function() { return compile_select(this); }, + count: function(table) { + if (typeof table === 'string') { + this.from(table); + } + + var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + + build_from_clause(this) + + build_join_string(this) + + build_where_clause(this); + + return sql; + }, + update: function(table, set, where) { set = set || null; diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index a7df0c7..c950b19 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -19,13 +19,25 @@ var QueryExec = function(qb, adapter) { return { query: function(sql, callback) { - exec(sql,callback); + exec(sql, callback); }, count: function(table, callback) { + if (typeof table === 'function' && typeof callback !== 'function') { + table = null; + callback = table; + } + var sql = qb.count(table); qb.reset_query(sql); - exec(sql,callback); + exec(sql, function(err, row) { + if (!err) { + callback(err, row.numrows); + } + else { + callback(err, row); + } + }); }, get: function(table,callback) { diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js new file mode 100755 index 0000000..25c8ea8 --- /dev/null +++ b/test/mysql/tests-count.js @@ -0,0 +1,55 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +describe('count()', function() { + it('should exist', function() { + should.exist(qb.count); + }); + it('should be a function', function() { + qb.count.should.be.a('function'); + }); + it('should require that an item already exists in the from_array if one is not provided as the first parameter', function() { + qb.reset_query(); + expect(function() { qb.count(); }, 'no tables supplied in chain').to.throw(Error); + expect(function() { qb.from('galaxies').count(); }, 'table supplied by from()').to.not.throw(Error); + expect(function() { qb.count('galaxies'); }, 'table supplied as first parameter').to.not.throw(Error); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + var sql = qb.count('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should return a string', function() { + qb.reset_query(); + var sql = qb.count('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should create a properly-escaped SELECT query', function() { + qb.reset_query(); + var sql = qb.count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`)"); + }); + it('should include WHERE statements', function() { + qb.reset_query(); + var sql = qb.where({type:'spiral'}).count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`) WHERE `type` = 'spiral'"); + }); + it('should work when table/view/procedure is provided earlier in chain but not in count() method', function() { + qb.reset_query(); + var sql = qb.from('galaxies').count(); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`)"); + }); + it('should work with multiple tables/views/stored procedures', function() { + qb.reset_query(); + var sql = qb.from(['planets','galaxies']).count(); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`planets`, `galaxies`)"); + }); + it('should include any joins that were added in the chain', function() { + qb.reset_query(); + var sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`star_systems` `s`) LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + }); +}); \ No newline at end of file diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index d64b78a..ba97ebd 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -41,7 +41,7 @@ describe('get()', function() { var sql = qb.select(['id','name']).get('galaxies'); sql.should.eql("SELECT `id`, `name` FROM (`galaxies`)"); }); - it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed conditions', function() { + it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', function() { qb.reset_query(); var sql = qb.where('class','M').get('galaxies'); sql.should.eql("SELECT * FROM (`galaxies`) WHERE `class` = 'M'"); @@ -158,6 +158,8 @@ describe('get_where()', function() { expect(function() { qb.get_where('galaxies',3); }, 'valid table, integer for where').to.throw(Error); expect(function() { qb.get_where('galaxies',33.3); }, 'valid table, float for where').to.throw(Error); expect(function() { qb.get_where('galaxies','foo'); }, 'valid table, string for where').to.throw(Error); + expect(function() { qb.get_where('galaxies',true); }, 'valid table, TRUE for where').to.throw(Error); + expect(function() { qb.get_where('galaxies',false); }, 'valid table, FALSE for where').to.throw(Error); expect(function() { qb.get_where('galaxies',Infinite); }, 'valid table, Infinite where').to.throw(Error); expect(function() { qb.get_where('galaxies',null); }, 'valid table, NULL where').to.throw(Error); expect(function() { qb.get_where('galaxies',{id: 3}); }, 'valid table, valid where').to.not.throw(Error); From b1dc2e02d6511aaed11cd964f2c0ec202566648f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 18 Nov 2014 14:09:48 -0500 Subject: [PATCH 050/200] Made some changes to how pool connections can be passed through iterative queries. --- README.md | 62 +++++++++++++---- drivers/mysql/connect.js | 16 +++-- drivers/mysql/query_builder.js | 6 +- drivers/mysql/query_exec.js | 99 +++++++++++++++++--------- index.js | 2 +- test/mysql/tests-update.js | 123 +++++++++++++++++++++++++++++++++ 6 files changed, 255 insertions(+), 53 deletions(-) create mode 100755 test/mysql/tests-update.js diff --git a/README.md b/README.md index 14038a3..1a0844a 100755 --- a/README.md +++ b/README.md @@ -1020,7 +1020,16 @@ Execution methods are the end-of-chain methods in the QueryBuilder library. Once ### Handling Error Messages and Results -The final parameter of every execution method will be a callback function. The parameters for the callback are in the `node.js` standard `(err, response)` format. When you working with `pool` and `cluster` type connections, a third paramter will be passed containing the `connection` object—you would use this to release the connection when you're done with it. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected (all others) in any other scenario. +The third parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. When you are working with `pool` and `cluster` type connection setups, a third paramter will be passed containing the `conn` (connection) object used to make the query. You would use this connection object to pass on to a successive query (to avoid having to get release and re-get another connection) or release the connection back to the pool(s). If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. + +### Response Format Examples + +| API Method(s) | Response Format | +| :-------------------------------- | :---------------------------------------------------------------------------------------- | +| get(), get_where() | [{field:value,field2:value2},{field:value, field2:value2}] | +| count() | Integer (ex. 578) | +| insert(), update(), delete() | Example: {insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]} | +| insert_batch(), update_batch() | Example: {insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]} | #### Callback Example @@ -1030,7 +1039,8 @@ var callback = function(err, response, connection) { connection.release(); // if you're working with a pool or cluster... if (err) { console.error(err); - } else { + } + else { for (var i in response) { var row = response[i]; /* @@ -1042,6 +1052,30 @@ var callback = function(err, response, connection) { qb.get('foo',callback); ``` +#### Using the Same Connection Pool Connection for Successive Calls + +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); +var data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; +qb.insert('employees', data, function(err, res, conn) { + if (err) { + console.error(err); + } + else { + if (res.affected_rows > 0) { + var insert_id = res.insert_id; + qb.get_where('employees', {id: insert_id}, function(err, res, conn) { + conn.release(); + console.dir(res); + }, conn); + } + else { + console.error("New user was not added to database!"); + } + } +}); +``` + ------------- ### .query(query_string,callback) @@ -1096,7 +1130,7 @@ If you already have the table added to the query: qb.from('galaxies').get(callback); ``` -Just a more-complicated example for the sake of it: +Just a more-complicated example for the sake of it (note: using connection pool): ```javascript /** @@ -1121,7 +1155,8 @@ qb.limit(10) .join('stars s', 's.galaxy_id=g.id', 'left') .group_by('g.id') .order_by('g.name', 'asc') - .get(function(err, response) { + .get(function(err, response, conn) { + conn.release(); if (err) return console.error(err); for (var i in response) { @@ -1197,12 +1232,12 @@ qb.where('type',type).count('galaxies', function(err, count) { ### .update(table,data,where,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Required | The table/collection you'd like to update | -| data | Object | Required | The data to update (ex. {field: value}) | -| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately | -| callback | Function | Required | What to do when the driver has responded. | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to update | +| data | Object | Required | The data to update (ex. {field: value}) | +| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | +| callback | Function | Required | What to do when the driver has responded. | This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... @@ -1218,10 +1253,13 @@ Here's a contrived example of how it might be used in an app made with the Expre var express = require('express'); var app = express(); var settings = require('db.json'); -var qb = require('node-querybuilder').QueryBuilder(settings,'mysql', 'pool'); +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/update_account', function(req, res) { var user_id = req.session.user_id; + var sanitize_name = function(name) { + return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); + }; var data = { first_name: sanitize_name(req.body.first_name), last_name: sanitize_name(req.body.last_name), @@ -1229,7 +1267,7 @@ app.post('/update_account', function(req, res) { bio: sanitize_bio(req.body.bio), }; - qb.update('users', data, {id:user_id}, function(err, res, connection) { + qb.update('users', data, {id:user_id}, function(err, res, conn) { connection.release(); if (err) return console.error(err); diff --git a/drivers/mysql/connect.js b/drivers/mysql/connect.js index f0aa15f..e858139 100755 --- a/drivers/mysql/connect.js +++ b/drivers/mysql/connect.js @@ -20,7 +20,7 @@ var Adapter = function(settings,type) { this.debugging = false; this.connection = null; this.connection_pool = null; - this.connection_type = 'single'; + this.connection_type = type || 'single'; // Enable debugging if necessary if (settings.hasOwnProperty('qb_debug') && settings.debug === true) { @@ -91,6 +91,7 @@ var Adapter = function(settings,type) { // @return VOID // **************************************************************************** this.get_connection = function(callback) { + switch(that.connection_type) { case 'cluster': break; @@ -123,11 +124,14 @@ var Adapter = function(settings,type) { this.pool = function() { that.connection_pool = mysql.createPool(this.settings); - that.connection_pool.query('SELECT 1 + 1 AS solution', function(err) { - if (err) throw err; - if (that.debugging === true) { - console.log('mysql connection pool created'); - } + that.connection_pool.getConnection(function(err, connection) { + connection.query('SELECT 1 + 1 AS solution', function(err) { + connection.release(); + if (err) throw err; + if (that.debugging === true) { + console.log('mysql connection pool created'); + } + }); }); return { diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d351d0f..2dbb312 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -412,7 +412,11 @@ var QueryBuilder = function() { if (qb.from_array.length === 1) { var table = qb.from_array.toString(); } else { - throw new Error("You haven't provided any tables to build UPDATE query with!"); + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); return ''; } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index c950b19..fd2d69d 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -4,25 +4,57 @@ // @param Object qb The QueryBuilder object // @param Object adapter The connection adapter object // **************************************************************************** -var QueryExec = function(qb, adapter) { - var exec = function(sql, callback) { - adapter.get_connection(function(connection) { - connection.query(sql, function(err, rows) { - if (adapter.connection_type === 'standard') { - callback(err, rows); - } else { - callback(err, rows, connection); +var QueryExec = function(qb, adapter) { + + var do_query = function(conn, sql, callback) { + conn.query(sql, function(err, results) { + // Standardize some important properties + if (!err && results.length > 0) { + + // Insert ID + if (results.hasOwnProperty('insertId')) { + results.insert_id = results.insertId; } - }); + + // Affected Rows + if (results.hasOwnProperty('affectedRows')) { + results.affected_rows = results.affectedRows; + } + + // Changed Rows + if (results.hasOwnProperty('changedRows')) { + results.changed_rows = results.changedRows; + } + } + + if (adapter.connection_type === 'standard') { + callback(err, results); + } else { + callback(err, results, conn); + } }); }; + var exec = function(sql, callback, conn) { + if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { + do_query(conn, sql, callback); + } + else { + adapter.get_connection(function(connection) { + if (connection === null) { + throw Error("A connection to the database could not be established!"); + } + do_query(connection, sql, callback); + }); + } + }; + return { - query: function(sql, callback) { - exec(sql, callback); + query: function(sql, callback, conn) { + exec(sql, callback, conn); }, - count: function(table, callback) { + count: function(table, callback, conn) { if (typeof table === 'function' && typeof callback !== 'function') { table = null; callback = table; @@ -37,10 +69,10 @@ var QueryExec = function(qb, adapter) { else { callback(err, row); } - }); + }, conn); }, - get: function(table,callback) { + get: function(table,callback,conn) { // The table parameter is optional, it could be the callback... if (typeof table === 'function' && typeof callback !== 'function') { callback = table; @@ -51,10 +83,10 @@ var QueryExec = function(qb, adapter) { var sql = qb.get(table); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - get_where: function(table,where,callback) { + get_where: function(table,where,callback,conn) { if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { throw new Error("First parameter of get_where() must be a string or an array of strings."); } @@ -63,31 +95,32 @@ var QueryExec = function(qb, adapter) { } var sql = qb.get_where(table,where); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - insert: function(table,set,callback,ignore,suffix) { + insert: function(table,set,callback,ignore,suffix,conn) { var sql = qb.insert(table,set,ignore,suffix); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - insert_ignore: function(table,set,callback) { + insert_ignore: function(table,set,callback,conn) { var sql = qb.insert_ignore(table,set); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - insert_batch: function(table,set,callback) { + insert_batch: function(table,set,callback,conn) { var sql = qb.insert_batch(table,set); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - update: function(table,set,where,callback) { + update: function(table,set,where,callback,conn) { // The where parameter is optional, it could be the callback... if (typeof where === 'function' && typeof callback !== 'function') { callback = where; + where = null; } else if (typeof where === 'undefined' && typeof callback !== 'function') { throw new Error("No callback function has been provided in your update call!"); @@ -96,13 +129,13 @@ var QueryExec = function(qb, adapter) { where = null; } - var sql = qb.update(table,set,where); + var sql = qb.update(table,set,where,conn); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, // TODO: Write this complicated-ass function - update_batch: function(table,set,where,callback) { + update_batch: function(table,set,where,callback,conn) { adapter.get_connection(function(connection) { if (adapter.connection_type === 'standard') { callback(new Error("This function is not currently available!"),null); @@ -113,7 +146,7 @@ var QueryExec = function(qb, adapter) { }); }, - delete: function(table, where, callback) { + delete: function(table, where, callback,conn) { if (typeof where === 'function' && typeof callback !== 'function') { callback = where; where = undefined; @@ -132,19 +165,19 @@ var QueryExec = function(qb, adapter) { var sql = qb.delete(table, where); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - empty_table: function(table, callback) { + empty_table: function(table, callback,conn) { var sql = qb.empty_table(table,callback); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, - truncate: function(table, callback) { + truncate: function(table, callback,conn) { var sql = qb.truncate(table,callback); qb.reset_query(sql); - exec(sql,callback); + exec(sql,callback,conn); }, } } diff --git a/index.js b/index.js index e761d8a..8fff630 100755 --- a/index.js +++ b/index.js @@ -191,7 +191,7 @@ var QueryBuilder = function(settings,driver,type) { // **************************************************************************** var get_adapter = function(qb) { try { - qb.adapter = require(qb.driver_info.path + 'connect.js').Adapter(qb.settings); + qb.adapter = require(qb.driver_info.path + 'connect.js').Adapter(qb.settings, qb.connection_type); } catch(e) { throw new Error("Could not connect to database: " + e); } diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js new file mode 100755 index 0000000..6be2e25 --- /dev/null +++ b/test/mysql/tests-update.js @@ -0,0 +1,123 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +var test_where = {id:3}; +var test_data = {id:3, name:'Milky Way', type: 'spiral'}; +var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +// table, data, callback, ignore, suffix + +describe('update()', function() { + it('should exist', function() { + should.exist(qb.update); + }); + it('should be a function', function() { + qb.update.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.update('galaxies', test_data, test_where); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a string for the table (first) parameter', function() { + qb.reset_query(); + + // Doing these to prevent other errors + qb.from('galaxies'); + + expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); + expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); + expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); + expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); + expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); + + expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); + expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { + qb.reset_query(); + + expect(function() { qb.insert('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); + + expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); + expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); + expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); + expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', function() { + qb.reset_query(); + qb.from('galaxies'); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in in setArray', function() { + qb.reset_query(); + qb.set(test_data); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should utilize pre-existing tables and values from setArray and from_array, respectively', function() { + qb.reset_query(); + qb.from('galaxies').set(test_data); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should accept a non-empty object for the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data_set); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should fail if any invalid values are passed in the data object.', function() { + qb.reset_query(); + var func = function() { console.log("foo"); }; + var regex = /foobar/; + var arr = [1,2,3]; + var obj = {foo: 'bar'}; + + expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + + }); + it('should support insert ignore statements', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); +}); \ No newline at end of file From 64fb900770d90522ec3809c4bf6c5cb8badb684d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 18 Nov 2014 14:28:52 -0500 Subject: [PATCH 051/200] Minor clarification in execution method docs. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a0844a..c95d95e 100755 --- a/README.md +++ b/README.md @@ -1020,7 +1020,7 @@ Execution methods are the end-of-chain methods in the QueryBuilder library. Once ### Handling Error Messages and Results -The third parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. When you are working with `pool` and `cluster` type connection setups, a third paramter will be passed containing the `conn` (connection) object used to make the query. You would use this connection object to pass on to a successive query (to avoid having to get release and re-get another connection) or release the connection back to the pool(s). If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. +The third parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. When you are working with `pool` and `cluster` type connection setups, a third paramter will be passed containing the `conn` (connection) object used to make the query. You would use this connection object to pass on to a successive query (final paramter of all exec methods)&mdashto avoid having to get release and re-get another connection—or release the connection back to the pool(s). If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. ### Response Format Examples @@ -1067,7 +1067,7 @@ qb.insert('employees', data, function(err, res, conn) { qb.get_where('employees', {id: insert_id}, function(err, res, conn) { conn.release(); console.dir(res); - }, conn); + }, conn); // <----- Notice connection is passed as 4th parameter } else { console.error("New user was not added to database!"); From f59339e04793e3be54cf45543da6aca562f9e61f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 18 Nov 2014 14:37:29 -0500 Subject: [PATCH 052/200] Added some more documentation for how to re-use connections from pools. --- README.md | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c95d95e..3db25b5 100755 --- a/README.md +++ b/README.md @@ -1024,12 +1024,12 @@ The third parameter of every execution method will be a callback function. For ` ### Response Format Examples -| API Method(s) | Response Format | -| :-------------------------------- | :---------------------------------------------------------------------------------------- | -| get(), get_where() | [{field:value,field2:value2},{field:value, field2:value2}] | -| count() | Integer (ex. 578) | -| insert(), update(), delete() | Example: {insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]} | -| insert_batch(), update_batch() | Example: {insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]} | +| API Method(s) | Response Format | +| :-------------------------------- | :-------------------------------------------------------------------------------------------- | +| get(), get_where() | `[{field:value,field2:value2},{field:value, field2:value2}]` | +| count() | Integer (ex. `578`) | +| insert(), update(), delete() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | +| insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | #### Callback Example @@ -1078,12 +1078,13 @@ qb.insert('employees', data, function(err, res, conn) { ------------- -### .query(query_string,callback) +### .query(query_string,callback[,connection]) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------ | -| query_string | String | Required | Query to send directly to your database driver | -| callback | Function | Required | What to do when the driver has responded | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| query_string | String | Required | Query to send directly to your database driver | +| callback | Function | Required | What to do when the driver has responded | +| connection | Object | undefined | (optional) Pass if you want to re-use a connection from a pool| *****This method bypasses the entire QueryBuilder portion of this module***** is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. @@ -1101,12 +1102,13 @@ qb.query("CREATE VIEW `foobar` AS " + sql, callback); ------------- -### .get(table,callback) +### .get([table,]callback[,connection]) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | -| callback | Function | Required | What to do when the driver has responded | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------- | +| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | +| callback | Function | Required | What to do when the driver has responded | +| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool | This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. @@ -1170,13 +1172,14 @@ qb.limit(10) ------------- -### .get_where(table,where,callback) +### .get_where(table,where,callback[,connection]) | Parameter | Type | Default | Description | | :-------- | :---------------- | :-------- | :------------------------------------------------------------ | -| table | String or Array | Required | Used to avoid having to call `.from()` seperately. | -| where | Object | Required | Used to avoid having to call `.where()` seperately | +| table | String or Array | Required | Used to avoid having to call `.from()` seperately. | +| where | Object | Required | Used to avoid having to call `.where()` seperately | | callback | Function | Required | What to do when the driver has responded. | +| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool| This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. @@ -1202,12 +1205,13 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca ------------- -### .count(table,callback) +### .count([table,]callback[,connection]) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | | callback | Function | Required | What to do when the driver has responded. | +| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool| This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. @@ -1230,7 +1234,7 @@ qb.where('type',type).count('galaxies', function(err, count) { ------------- -### .update(table,data,where,callback) +### .update(table,data[,where],callback[,connection]) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | @@ -1238,6 +1242,7 @@ qb.where('type',type).count('galaxies', function(err, count) { | data | Object | Required | The data to update (ex. {field: value}) | | where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | | callback | Function | Required | What to do when the driver has responded. | +| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool | This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... From aa16a420449238e7204c3f97ade991b52c6db953 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 21 Nov 2014 17:48:32 -0500 Subject: [PATCH 053/200] Major overhaul to fix QueryBuilder race condition issues when working with connection pools. --- README.md | 78 ++++----- drivers/mysql/adapters.js | 224 ++++++++++++++++++++++++++ drivers/mysql/connect.js | 172 -------------------- drivers/mysql/query_builder.js | 2 +- drivers/mysql/query_exec.js | 113 ++++++------- examples/connection_pool_example.js | 23 +++ examples/connection_single_example.js | 21 +++ index.js | 140 +--------------- package.json | 3 +- 9 files changed, 364 insertions(+), 412 deletions(-) create mode 100755 drivers/mysql/adapters.js delete mode 100755 drivers/mysql/connect.js create mode 100755 examples/connection_pool_example.js create mode 100755 examples/connection_single_example.js diff --git a/README.md b/README.md index 3db25b5..bd4b91e 100755 --- a/README.md +++ b/README.md @@ -1016,11 +1016,11 @@ Execution Methods ### What are "Execution Methods"?? -Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond positively with resultset/boolean depending on the type of query being executed or negatively with an error message. Both (if provided) will be supplied to the callback function. +Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. Both (if provided) will be supplied to the callback function. ### Handling Error Messages and Results -The third parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. When you are working with `pool` and `cluster` type connection setups, a third paramter will be passed containing the `conn` (connection) object used to make the query. You would use this connection object to pass on to a successive query (final paramter of all exec methods)&mdashto avoid having to get release and re-get another connection—or release the connection back to the pool(s). If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. +The final parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. ### Response Format Examples @@ -1035,8 +1035,8 @@ The third parameter of every execution method will be a callback function. For ` #### Callback Example ```javascript -var callback = function(err, response, connection) { - connection.release(); // if you're working with a pool or cluster... +var callback = function(err, response) { + qb.release(); if (err) { console.error(err); } @@ -1049,42 +1049,46 @@ var callback = function(err, response, connection) { } } }; -qb.get('foo',callback); +pool.get_connection(function(qb) { + qb.get('foo',callback); +}); ``` #### Using the Same Connection Pool Connection for Successive Calls ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); +var pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); var data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; -qb.insert('employees', data, function(err, res, conn) { - if (err) { - console.error(err); - } - else { - if (res.affected_rows > 0) { - var insert_id = res.insert_id; - qb.get_where('employees', {id: insert_id}, function(err, res, conn) { - conn.release(); - console.dir(res); - }, conn); // <----- Notice connection is passed as 4th parameter + +pool.get_connection(function(qb) { + qb.insert('employees', data, function(err, res) { + if (err) { + console.error(err); } else { - console.error("New user was not added to database!"); + if (res.affected_rows > 0) { + var insert_id = res.insert_id; + qb.get_where('employees', {id: insert_id}, function(err, res) { + qb.release(); + console.dir(res); + }); + } + else { + console.error("New user was not added to database!"); + } } - } + }); }); ``` ------------- -### .query(query_string,callback[,connection]) +### .query(query_string,callback) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | query_string | String | Required | Query to send directly to your database driver | | callback | Function | Required | What to do when the driver has responded | -| connection | Object | undefined | (optional) Pass if you want to re-use a connection from a pool| *****This method bypasses the entire QueryBuilder portion of this module***** is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. @@ -1102,13 +1106,12 @@ qb.query("CREATE VIEW `foobar` AS " + sql, callback); ------------- -### .get([table,]callback[,connection]) +### .get([table,]callback) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------- | | table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | | callback | Function | Required | What to do when the driver has responded | -| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool | This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. @@ -1172,14 +1175,13 @@ qb.limit(10) ------------- -### .get_where(table,where,callback[,connection]) +### .get_where(table,where,callback) | Parameter | Type | Default | Description | | :-------- | :---------------- | :-------- | :------------------------------------------------------------ | | table | String or Array | Required | Used to avoid having to call `.from()` seperately. | | where | Object | Required | Used to avoid having to call `.where()` seperately | | callback | Function | Required | What to do when the driver has responded. | -| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool| This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. @@ -1205,13 +1207,12 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca ------------- -### .count([table,]callback[,connection]) +### .count([table,]callback) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | | callback | Function | Required | What to do when the driver has responded. | -| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool| This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. @@ -1234,7 +1235,7 @@ qb.where('type',type).count('galaxies', function(err, count) { ------------- -### .update(table,data[,where],callback[,connection]) +### .update(table,data[,where],callback) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | @@ -1242,7 +1243,6 @@ qb.where('type',type).count('galaxies', function(err, count) { | data | Object | Required | The data to update (ex. {field: value}) | | where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | | callback | Function | Required | What to do when the driver has responded. | -| connection| Object | undefined | (optional) Pass if you want to re-use a connection from a pool | This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... @@ -1258,7 +1258,7 @@ Here's a contrived example of how it might be used in an app made with the Expre var express = require('express'); var app = express(); var settings = require('db.json'); -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/update_account', function(req, res) { var user_id = req.session.user_id; @@ -1272,14 +1272,16 @@ app.post('/update_account', function(req, res) { bio: sanitize_bio(req.body.bio), }; - qb.update('users', data, {id:user_id}, function(err, res, conn) { - connection.release(); - if (err) return console.error(err); - - var page_data = { - prefill: data, - } - return res.render('/account_updated', page_data); + pool.get_connection(function(qb) { + qb.update('users', data, {id:user_id}, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + prefill: data, + } + return res.render('/account_updated', page_data); + }); }); }); ``` diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js new file mode 100755 index 0000000..df44bdb --- /dev/null +++ b/drivers/mysql/adapters.js @@ -0,0 +1,224 @@ +var Adapters = function(nqb) { + + var _ = require('underscore'); + + // Load MySQL Driver + var mysql = require('mysql'); + + // Verify setting property exists + if (!nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + // Enable debugging if necessary + this.debugging = false; + if (nqb.settings.hasOwnProperty('qb_debug') && settings.qb_debug === true) { + this.debugging = true; + delete settings.qb_debug; + } + + // Verify that required fields are provided... + if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; + if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + if (!nqb.settings.hasOwnProperty('password')) throw new Error("No user property provided. Hint: It can be NULL"); + + this.connection_settings = {}; + var that = this; + + // **************************************************************************** + // Map generic NQB connection settings to node-mysql's format + // ---- + // NOTE: MySQL connection settings names are the same as Node Querybuilder, + // it's just good practice to go ahead and do this in case things change. + // **************************************************************************** + var map_connection_settings = function() { + that.connection_settings = { + host: nqb.settings.host, + user: nqb.settings.user, + password: nqb.settings.password + } + if (nqb.settings.hasOwnProperty('database')) { + that.connection_settings.database = nqb.settings.database; + delete nqb.settings.database + } + if (nqb.settings.hasOwnProperty('port')) { + that.connection_settings.port = nqb.settings.port; + delete nqb.settings.port + } + + // Remove mapped settings: + delete nqb.settings.host + delete nqb.settings.user + delete nqb.settings.password + + // Merge any driver-specific settings into connection settings + that.connection_settings = _.extend(that.connection_settings, nqb.settings); + } + + map_connection_settings(); + + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object + // **************************************************************************** + var get_query_builder = function() { + try { + return require('./query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object + // **************************************************************************** + var get_query_exec = function(qb, conn) { + try { + return require('./query_exec.js').QueryExec(qb, conn); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Generic Single Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Adapter = function(settings) { + var pool, connection; + + // If the Pool object is instatiating this Adapter, use it's connection + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } + // Otherwise, let's create a new connection + else { + connection = new mysql.createConnection(that.connection_settings); + } + + var qb = get_query_builder(); + var qe = get_query_exec(qb, connection); + + var adapter = _.extend({ + connection_settings: function() { + return that.connection_settings; + }, + + connection: function() { + return connection; + }, + + escape: function(str) { + return connection.escape(str); + }, + + disconnect: function(str) { + return connection.end(); + }, + + release: function() { + pool.releaseConnection(connection); + } + }, qb, qe); + + return adapter; + }; + + // **************************************************************************** + // Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Pool = function() { + if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { + // Create connection Pool + nqb.pool = mysql.createPool(that.connection_settings); + + // Test connection pool (asynchronous) + nqb.pool.getConnection(function(err, connection) { + connection.query('SELECT 1 + 1 AS solution', function(err) { + connection.release(); + if (err) throw err; + if (that.debugging === true) { + console.log('mysql connection pool created'); + } + }); + }); + } + + // Return Pool Object + return { + pool: function() { + return nqb.pool; + }, + get_connection: function(callback) { + if (null === nqb.pool) { + var error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + + nqb.pool.getConnection(function (err, connection) { + if (err) throw err; + var adapter = new Adapter({ + pool: { + pool: nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + nqb.pool.end(callback); + } + } + }; + + // **************************************************************************** + // Clustered Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Cluster = function() { + + }; + + // **************************************************************************** + // Basic Description + // ----- + // @param + // @return + // **************************************************************************** + var determine_adapter = function() { + switch(nqb.connection_type) { + case 'cluster': + return new Cluster(); + break; + case 'pool': + return new Pool(); + break; + case 'single': + default: + return new Adapter({}); + break; + } + } + + var adapter = determine_adapter(); + //console.dir(adapter); process.exit(1); + return adapter; +}; + +exports.Adapters = Adapters; diff --git a/drivers/mysql/connect.js b/drivers/mysql/connect.js deleted file mode 100755 index e858139..0000000 --- a/drivers/mysql/connect.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * MySQL Connection Library - * Version 0.0.1 - * - * Fundamentally, this is a wrapper to the awesome `node-mysql` project that - * simply allows it to work generically within the Query Builder module. - * - * Supported connection types: - * - Single - * - Pool - * - PoolCluster - * - * Dependencies: node-mysql - * -**/ - -var Adapter = function(settings,type) { - var mysql = require('mysql'); - - this.debugging = false; - this.connection = null; - this.connection_pool = null; - this.connection_type = type || 'single'; - - // Enable debugging if necessary - if (settings.hasOwnProperty('qb_debug') && settings.debug === true) { - this.debugging = true; - delete settings.qb_debug; - } - - this.settings = settings; - if (this.settings.hasOwnProperty('connection_type')) { - this.connection_type = this.settings.connection_type; - delete settings.connection_type; - } - - // Verify that required fields are provided... - if (!this.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - if (!this.settings.hasOwnProperty('password')) throw new Error("No user property provided. Hint: It can be NULL"); - - // **************************************************************************** - // Sets up a cluster of pooled connections to different servers for load - // balancing and failover - // ----- - // @param Object settings Connection settings - // @return VOID - // **************************************************************************** - this.cluster = function() { - throw new Error("Not implemented yet!"); - }; - - // **************************************************************************** - // Method for immediately destoying all open connections to MySQL--even if there - // are active queries being processed. - // ----- - // @return VOID - // **************************************************************************** - this.destroy = function() { - return that.connection.destroy(); - }; - - // **************************************************************************** - // Method for gracefully closing all connections to MySQL after all active - // queries have completed. - // ----- - // @param Function callback What to do once connection has been closed. - // @return VOID - // **************************************************************************** - this.disconnect = function(callback) { - return that.connection.end(callback); - }; - - // **************************************************************************** - // Escape values for MySQL - // ----- - // @param Mixed item String to escape - // @return String Escaped value - // **************************************************************************** - this.escape = function(item) { - that.get_connection(function(connection) { - return connection.escape(item); - }); - } - - // **************************************************************************** - // Returns a connection hangle (this will mostly be used for cluster and pool - // connection types, but, nonetheless can be used as a getter for the single - // type as well. - // ----- - // @param Function callback What to do once the connection is obtained - // @return VOID - // **************************************************************************** - this.get_connection = function(callback) { - - switch(that.connection_type) { - case 'cluster': - break; - case 'pool': - if (null === that.connection_pool) { - var error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - that.connection_pool.getConnection(function (err, connection) { - if (err) { - throw err; - } - callback(connection); - }); - break; - case 'single': - default: - callback(this.connection); - break; - } - }; - - // **************************************************************************** - // Sets up a connection pool - // ----- - // @param Object settings Connection settings - // @return VOID - // **************************************************************************** - this.pool = function() { - that.connection_pool = mysql.createPool(this.settings); - - that.connection_pool.getConnection(function(err, connection) { - connection.query('SELECT 1 + 1 AS solution', function(err) { - connection.release(); - if (err) throw err; - if (that.debugging === true) { - console.log('mysql connection pool created'); - } - }); - }); - - return { - disconnect: function() { - that.connection_pool.end(responseCallback); - } - } - }; - - // **************************************************************************** - // Sets up a standard one-time connection (no pooling). This one is used by the - // other two methods to stay DRY. - // ----- - // @param Object settings Connection settings - // @return VOID - // **************************************************************************** - this.single = function() { - that.connection = mysql.createConnection(this.settings); - that.connection.connect(function(err) { - if (err) { - console.error('error connecting to mysql: ' + err.stack); - return; - } - if (this.debugging === true) { - console.log('connected to mysql as id ' + connection.threadId); - } - }); - - that.connection.on('error', function(err) { - console.error(err.code); - }); - }; - - var that = this; - return that; -} - -exports.Adapter = Adapter; \ No newline at end of file diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 2dbb312..b156871 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1409,7 +1409,7 @@ var QueryBuilder = function() { throw new Error("This function has not yet been implemented!"); }, - _last_query: function() { + last_query: function() { return this.last_query_string[0] || ''; } } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index fd2d69d..75290ac 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -4,57 +4,43 @@ // @param Object qb The QueryBuilder object // @param Object adapter The connection adapter object // **************************************************************************** -var QueryExec = function(qb, adapter) { +var QueryExec = function(qb, conn) { - var do_query = function(conn, sql, callback) { - conn.query(sql, function(err, results) { - // Standardize some important properties - if (!err && results.length > 0) { - - // Insert ID - if (results.hasOwnProperty('insertId')) { - results.insert_id = results.insertId; - } - - // Affected Rows - if (results.hasOwnProperty('affectedRows')) { - results.affected_rows = results.affectedRows; - } + var exec = function(sql, callback) { + if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { + conn.query(sql, function(err, results) { + // Standardize some important properties + if (!err && results.length > 0) { - // Changed Rows - if (results.hasOwnProperty('changedRows')) { - results.changed_rows = results.changedRows; + // Insert ID + if (results.hasOwnProperty('insertId')) { + results.insert_id = results.insertId; + } + + // Affected Rows + if (results.hasOwnProperty('affectedRows')) { + results.affected_rows = results.affectedRows; + } + + // Changed Rows + if (results.hasOwnProperty('changedRows')) { + results.changed_rows = results.changedRows; + } } - } - - if (adapter.connection_type === 'standard') { + callback(err, results); - } else { - callback(err, results, conn); - } - }); - }; - - var exec = function(sql, callback, conn) { - if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - do_query(conn, sql, callback); - } - else { - adapter.get_connection(function(connection) { - if (connection === null) { - throw Error("A connection to the database could not be established!"); - } - do_query(connection, sql, callback); }); + } else { + throw new Error("No connection object supplied to the Query Exec Library!"); } }; return { - query: function(sql, callback, conn) { - exec(sql, callback, conn); + query: function(sql, callback) { + exec(sql, callback); }, - count: function(table, callback, conn) { + count: function(table, callback) { if (typeof table === 'function' && typeof callback !== 'function') { table = null; callback = table; @@ -69,7 +55,7 @@ var QueryExec = function(qb, adapter) { else { callback(err, row); } - }, conn); + }); }, get: function(table,callback,conn) { @@ -83,10 +69,10 @@ var QueryExec = function(qb, adapter) { var sql = qb.get(table); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - get_where: function(table,where,callback,conn) { + get_where: function(table,where,callback) { if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { throw new Error("First parameter of get_where() must be a string or an array of strings."); } @@ -95,28 +81,28 @@ var QueryExec = function(qb, adapter) { } var sql = qb.get_where(table,where); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - insert: function(table,set,callback,ignore,suffix,conn) { + insert: function(table,set,callback,ignore,suffix) { var sql = qb.insert(table,set,ignore,suffix); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - insert_ignore: function(table,set,callback,conn) { + insert_ignore: function(table,set,callback) { var sql = qb.insert_ignore(table,set); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - insert_batch: function(table,set,callback,conn) { + insert_batch: function(table,set,callback) { var sql = qb.insert_batch(table,set); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - update: function(table,set,where,callback,conn) { + update: function(table,set,where,callback) { // The where parameter is optional, it could be the callback... if (typeof where === 'function' && typeof callback !== 'function') { callback = where; @@ -129,24 +115,17 @@ var QueryExec = function(qb, adapter) { where = null; } - var sql = qb.update(table,set,where,conn); + var sql = qb.update(table,set,where); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, // TODO: Write this complicated-ass function update_batch: function(table,set,where,callback,conn) { - adapter.get_connection(function(connection) { - if (adapter.connection_type === 'standard') { - callback(new Error("This function is not currently available!"),null); - } else { - callback(new Error("This function is not currently available!"),null, connection); - } - - }); + callback(new Error("This function is not currently available!"),null); }, - delete: function(table, where, callback,conn) { + delete: function(table, where, callback) { if (typeof where === 'function' && typeof callback !== 'function') { callback = where; where = undefined; @@ -165,19 +144,19 @@ var QueryExec = function(qb, adapter) { var sql = qb.delete(table, where); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - empty_table: function(table, callback,conn) { + empty_table: function(table, callback) { var sql = qb.empty_table(table,callback); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, - truncate: function(table, callback,conn) { + truncate: function(table, callback) { var sql = qb.truncate(table,callback); qb.reset_query(sql); - exec(sql,callback,conn); + exec(sql,callback); }, } } diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js new file mode 100755 index 0000000..5fe6589 --- /dev/null +++ b/examples/connection_pool_example.js @@ -0,0 +1,23 @@ +var settings = { + host: 'localhost', + database: 'mydatabase', + user: 'myuser', + password: 'MyP@ssw0rd' +}; +var nqb = require('node-querybuilder'); +var pool = new QueryBuilder(settings, 'mysql', 'pool'); + +pool.get_connection(function(qb) { + qb.select('name', 'position') + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets', function(err,response) { + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); + + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); + + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.dir(response); + } + ); +}); \ No newline at end of file diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js new file mode 100755 index 0000000..50198fd --- /dev/null +++ b/examples/connection_single_example.js @@ -0,0 +1,21 @@ +var settings = { + host: 'localhost', + database: 'mydatabase', + user: 'myuser', + password: 'MyP@ssw0rd' +}; +var nqb = require('node-querybuilder'); +var qb = new QueryBuilder(settings, 'mysql', 'single'); + +qb.select('name', 'position') + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets', function(err,response) { + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); + + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); + + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.dir(response); + } +); \ No newline at end of file diff --git a/index.js b/index.js index 8fff630..ef88a9d 100755 --- a/index.js +++ b/index.js @@ -46,10 +46,7 @@ var QueryBuilder = function(settings,driver,type) { this.drivers = require('./drivers/drivers.json'); this.driver_version = 'default'; this.driver_info = null; - this.connection = null; - this.qb = null; - this.qe = null; - this.adapter = null; + this.pool = []; // **************************************************************************** // Get information about the driver the user wants to use and modify QB object @@ -92,80 +89,6 @@ var QueryBuilder = function(settings,driver,type) { }; get_driver_info(this); - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object - // **************************************************************************** - var get_query_builder = function(qb) { - try { - qb.qb = require(qb.driver_info.path + 'query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + qb.driver + ": " + e); - } - return qb; - }; - get_query_builder(this); - - // Non-Public QueryBuilder APIs - this._where = this.qb._where; - this._where_in = this.qb._where_in; - this._like = this.qb._like; - this._min_max_avg_sum = this.qb._min_max_avg_sum; - this._having = this.qb._having; - this._update = this.qb._update; - this.reset_query = this.qb.reset_query; - - // QueryBuilder Properties - this.where_array = this.qb.where_array; - this.where_in_array = this.qb.where_in_array; - this.from_array = this.qb.from_array; - this.join_array = this.qb.join_array; - this.select_array = this.qb.select_array; - this.set_array = this.qb.set_array; - this.order_by_array = this.qb.order_by_array; - this.group_by_array = this.qb.group_by_array; - this.having_array = this.qb.having_array; - this.limit_to = this.qb.limit_to; - this.offset_val = this.qb.offset_val; - this.join_clause = this.qb.join_clause; - this.last_query_string = this.qb.last_query_string; - this.distinct_clause = this.qb.distinct_clause; - this.aliased_tables = this.qb.aliased_tables; - - // QueryBuilder method mappings - this.where = this.qb.where; - this.or_where = this.qb.or_where; - this.where_in = this.qb.where_in; - this.or_where_in = this.qb.or_where_in; - this.where_not_in = this.qb.where_not_in; - this.or_where_not_in = this.qb.or_where_not_in; - this.like = this.qb.like; - this.not_like = this.qb.not_like; - this.or_like = this.qb.or_like; - this.or_not_like = this.qb.or_not_like; - this.from = this.qb.from; - this.join = this.qb.join; - this.select = this.qb.select; - this.select_min = this.qb.select_min; - this.select_max = this.qb.select_max; - this.select_avg = this.qb.select_avg; - this.select_sum = this.qb.select_sum; - this.distinct = this.qb.distinct; - this.group_by = this.qb.group_by; - this.having = this.qb.having; - this.or_having = this.qb.or_having; - this.order_by = this.qb.order_by; - this.limit = this.qb.limit; - this.offset = this.qb.offset; - this.set = this.qb.set; - this.get_compiled_select = this.qb.get_compiled_select; - this.get_compiled_insert = this.qb.get_compiled_insert; - this.get_compiled_update = this.qb.get_compiled_update; - this.get_compiled_delete = this.qb.get_compiled_delete; - this.last_query = this.qb._last_query; - // **************************************************************************** // Determine the type of connection (single, pool, cluster, etc...) // ----- @@ -184,69 +107,20 @@ var QueryBuilder = function(settings,driver,type) { get_connection_type(this); // **************************************************************************** - // Try to create a connection to the database using the driver's connection library + // Returns the single, pool, or cluster adapter // ----- - // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object + // @return VOID This method responds asychronously via a callback // **************************************************************************** var get_adapter = function(qb) { try { - qb.adapter = require(qb.driver_info.path + 'connect.js').Adapter(qb.settings, qb.connection_type); + var adapter = adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); + return adapter; } catch(e) { - throw new Error("Could not connect to database: " + e); + throw new Error("Couldn't load the Connection library for " + qb.driver + ": " + e); } - - if (!qb.adapter.hasOwnProperty(qb.connection_type)) { - throw new Error('"' + qb.connection_type + '" is an invalid connection type for ' + qb.driver + '!'); - } - - // Create connection - qb.adapter[qb.connection_type](); - qb.adapter.get_connection(function(connection) { - this.connection = connection; - return qb; - }); - } - get_adapter(this); - - this.disconnect = this.adapter.disconnect; - this.destroy = this.adapter.destroy; - this.escape = this.adapter.escape; - this.get_connection_id = this.adapter.get_connection_id; - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object - // **************************************************************************** - var get_query_exec = function(qb) { - try { - qb.qe = require(qb.driver_info.path + 'query_exec.js').QueryExec(qb.qb, qb.adapter); - } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + qb.driver + ": " + e); - } - return qb; }; - get_query_exec(this); - - // QueryExecute method mappings: - this.query = this.qe.query; - this.count = this.qe.count; - this.get = this.qe.get; - this.get_where = this.qe.get_where; - this.insert = this.qe.insert; - this.insert_ignore = this.qe.insert_ignore; - this.insert_batch = this.qe.insert_batch; - this.update = this.qe.insert_batch; - this.update_batch = this.qe.update_batch; - this.delete = this.qe.delete; - this.empty_table = this.qe.empty_table; - this.truncate = this.qe.truncate; - var that = this; - return this; + return get_adapter(this); }; exports.QueryBuilder = QueryBuilder; \ No newline at end of file diff --git a/package.json b/package.json index 572226e..6016a23 100755 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "dependencies": { "mysql": "2.5.*", "chai": "~1.4.2", - "mocha": "*" + "mocha": "*", + "underscore": "*" }, "main" : "./", "engines": { From 8408a21b5ae6184f8d73ce4827c6293286c7d449 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 21 Nov 2014 17:52:40 -0500 Subject: [PATCH 054/200] Removed invalid tests that I'm working on so CI will work. --- test/mysql/tests-update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 6be2e25..48d09da 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -20,7 +20,7 @@ describe('update()', function() { qb.update('galaxies', test_data, test_where); qb.from_array.should.eql(['`galaxies`']); }); - it('should only accept nothing or a string for the table (first) parameter', function() { + /* it('should only accept nothing or a string for the table (first) parameter', function() { qb.reset_query(); // Doing these to prevent other errors @@ -119,5 +119,5 @@ describe('update()', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); - }); + }); */ }); \ No newline at end of file From d838818617fb6faec7d2068fba8e7d7e31191ad3 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 24 Nov 2014 10:24:05 -0500 Subject: [PATCH 055/200] Fixed broken tests. --- drivers/mysql/query_builder.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index b156871..dce420f 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -406,7 +406,7 @@ var QueryBuilder = function() { var compile_update = function(qb) { var valstr = []; for (var i in qb.set_array) { - valstr.push(key + ' = ' + qb.set_array[i]); + valstr.push(i + ' = ' + qb.set_array[i]); } if (qb.from_array.length === 1) { @@ -423,7 +423,7 @@ var QueryBuilder = function() { var limit_to = qb.limit_to[0] || false; var limit = (!limit_to ? '' : ' LIMIT ' + limit_to); - sql = 'UPDATE ' + table + " SET " + valstr.join(', '); + var sql = 'UPDATE ' + table + " SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); sql += build_limit_clause(qb); @@ -1352,8 +1352,7 @@ var QueryBuilder = function() { this.where(where); } - var sql = this._update(this); - return sql; + return compile_update(this); }, delete: function(table, where) { From f4a1728f8dfd65ec25985afd0dbaec2cb0463f59 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 24 Nov 2014 10:29:51 -0500 Subject: [PATCH 056/200] Changed test reporter to look better on Travis CI. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 66adf16..bb1afe9 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mysql/*.js test: - mocha --timeout 5000 --check-leaks --reporter nyan $(TESTS) + mocha --timeout 5000 --check-leaks --reporter spec $(TESTS) .PHONY: test From 133a841fd8cee33abe29c14ba3d7a8a9b4b6f2dd Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 30 Jan 2015 17:42:27 -0500 Subject: [PATCH 057/200] Documentation for the 'Other Library-Specifc Methods' section. Update copyright on index.js file. Adjustment of how the Pool object is created as to prevent a possible issues caused by the asynchronous nature of the connection check. Added public escape method to the MySQL querybuilder library. --- README.md | 271 +++++++++++++++++++++++++++++++-- drivers/mysql/adapters.js | 64 ++++---- drivers/mysql/query_builder.js | 47 ++++-- index.js | 2 +- test/mysql/tests-update.js | 70 ++++----- 5 files changed, 365 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index bd4b91e..4fc2429 100755 --- a/README.md +++ b/README.md @@ -677,6 +677,8 @@ This SQL command is used to find close matches where as the "WHERE" command is f | value | String/Number | Required | The value you want the field to closely match | | side | String | 'both' | before: '%value'; after: 'value%', both: '%value%'| +**NOTE:** You can, alternatively, use `'right'` and `'left'` in place of `'before'` and '`after`' if you prefer. + #### .like(field,match[,side]) All fields are escaped automatically, no exceptions. Multiple calls will be joined together with 'AND'. You can also pass an object of field/match pairs. Wildcard sides are interchangeable between before/left and after/right--choose the one that makes the most sense to you (there are examples of each below). @@ -1240,7 +1242,7 @@ qb.where('type',type).count('galaxies', function(err, count) { | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | | table | String | Required | The table/collection you'd like to update | -| data | Object | Required | The data to update (ex. {field: value}) | +| data | Object | Required | The data to update (ex. `{field: value}`) | | where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | | callback | Function | Required | What to do when the driver has responded. | @@ -1248,7 +1250,7 @@ This method is used to update a table (SQL) or collection (NoSQL) with new data. **Type of Response Sent to Callback** -Object containing infomration about the results of the query. +Object containing information about the results of the query. **Examples** @@ -1262,14 +1264,14 @@ var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/update_account', function(req, res) { var user_id = req.session.user_id; - var sanitize_name = function(name) { - return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); - }; + var sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; + var sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; + var data = { first_name: sanitize_name(req.body.first_name), last_name: sanitize_name(req.body.last_name), age: sanitize_age(req.body.last_name), - bio: sanitize_bio(req.body.bio), + bio: req.body.bio, }; pool.get_connection(function(qb) { @@ -1290,22 +1292,117 @@ app.post('/update_account', function(req, res) { ### .update_batch(table,dataset,where,callback) +Documentation for this method coming soon! + ------------- ### .insert(table,data,callback) +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to insert into | +| data | Object | Required | The data to insert (ex. `{field: value}`) | +| callback | Function | Required | What to do when the driver has responded. | + +This method is used to insert new data into a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the ID of the newly inserted item, the affected rows (should be 1), etc... + +**Type of Response Sent to Callback** + +Object containing information about the result of the query. + +**Examples** + +Here's a contrived example of how it might be used in an app made with the Express framework: + +```javascript +var express = require('express'); +var app = express(); +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +app.post('/add_article', function(req, res) { + var user_id = req.session.user_id; + + var data = { + title: req.body.first_name, + body: req.body.last_name, + author: user_id, + publish_date: sanitize_age(req.body.last_name) + }; + + pool.get_connection(function(qb) { + qb.insert('articles', data, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + article_id: res.insert_id, + } + return res.render('/article_manager', page_data); + }); + }); +}); +``` + ------------- ### .insert_batch(table,dataset,callback) +Documentation for this method coming soon! + ------------- ### .insert_ignore(table,data,callback) +Documentation for this method coming soon! + ------------- ### .delete(table,where,callback) +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to delete records from. | +| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | +| callback | Function | Required | What to do when the driver has responded. | + +This method is used to delete records from a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with the number of affected rows. + +**Type of Response Sent to Callback** + +Object containing information about the result of the query. + +**Examples** + +Here's a contrived example of how it might be used in an app made with the Express framework (NOTE: you should do better with error handling): + +```javascript +var express = require('express'); +var app = express(); +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +app.post('/delete_comment/:id', function(req, res) { + var comment_id = req.params.id; + + pool.get_connection(function(qb) { + qb.get('comments', {id: id}, function(err, res) { + if (err) return console.error(err); + var article_id = res.article_id; + + qb.delete('comments', {id: id}, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + num_removed: res.affected_rows, + } + return res.render('/article/' + article_id, page_data); + }); + }); +}); +``` + ------------- Other Library-Specifc Methods @@ -1332,25 +1429,179 @@ Used to get a new connection from the connection pool or cluster pool. ### .last_query() +This is used to ascertain the query string that was most-recently executed. This MUST be called before closing the connection or releasing a connection back to the pool. This is useful for debugging what the `node-querybuilder` library is executing (or trying to execute). + +**Examples** + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +pool.get_connection(function(qb) { + var id = 4531; + qb.get('comments', {id: id}, function(err, res) { + // SELECT * FROM `comments` WHERE `id` = 4531 + console.log(qb.last_query()); + qb.release(); + }); +}); +``` + ------------- ### .escape(value) +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| value | Mixed | Required | The value to escape based on your database driver | + +This can be used to excape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). + +**What should happen:** +*Examples given are for MySQL* + +| Input Type | Output Type | Ex. Input | Ex. Output | +| :------------ | :-----------: | :---------------: | :---------------------------: | +| String | String | "\n\s\x1a" | "\\n\\s\\x1a" | +| Integer | String | 76 | '76' | +| Array | String | [1,2,3] | '1','2',3' | +| Date | String | new Date() | '2015-01-30 16:54:23.1856' | +| Buffer | String | new Buffer(1) | 'X\'00\'' | +| Object | String | {foo: 'bar', i: 3}| "`foo` = 'bar', `i` = 3" | + + +**Example** + +```javascript +var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +var sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; +qb.query(sql, function(err, res) { + console.dir(res); +}); +``` + ------------- -### .get_compiled_select() +### SQL Compilation Methods + +These methods can be used to build a query string without having to execute it. This is a fantastic option if you want to use the querybuilder to simply build queries and display the resulting string or to send the compiled query string off to a driver/engine other than the one offered by `node-querybuilder`. + +These are excellent educational tools and can be used like a SQL/NoSQL language rosetta stone of sorts. + +The callback will respond with the standard `(err, res)` format. The `res` parameter will contain the compiled query string. + +#### .get_compiled_select(table, callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +| callback | Function | Required | What to do when the string has been compiled | + +Compiles a SELECT-like query into a properly-escaped string. + +**Example:** + +Get certain details of a user account + +```javascript +var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +qb.select(['id','username','first_name','last_name']) + .from('users') + .like('username','k','after') + .get_compiled_select(function(err, res) { + if (err) return console.error(err); + + // SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' + console.log(res); + }); +``` ------------- -### .get_compiled_insert() +#### .get_compiled_insert(table, callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +| callback | Function | Required | What to do when the string has been compiled | + +Compiles a INSERT-like query into a properly-escaped string. + +**Example:** + +Add a new user to a `users` table. + +```javascript +var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +var crypto = require('crypto'); +var data = { + username: 'foobar', + password: crypto.createHash('sha1').update('password').digest('hex'), + first_name: 'Foo', + last_name: 'Bar' +}; +qb.set(data).get_compiled_insert('users', function(err, res) { + if (err) return console.error(err); + + // INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES ('foobar', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Foo', 'Bar') + console.log(res); +}); +``` ------------- -### .get_compiled_update() +#### .get_compiled_update(table, callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +| callback | Function | Required | What to do when the string has been compiled | + +Compiles an UPDATE-like query into a properly-escaped string. + +**Example:** + +Update the password of a user + +```javascript +var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +var crypto = require('crypto'); +var data = { + password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), +}; +qb.where('id',4321) + .set(data) + .get_compiled_update('users', function(err, res) { + if (err) return console.error(err); + + // UPDATE `users` SET `password` = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' WHERE `id` = 4321 + console.log(res); + }); +``` ------------- -### .get_compiled_delete() +#### .get_compiled_delete(table, callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +| callback | Function | Required | What to do when the string has been compiled | + +Compiles a SELECT-like query into a properly-escaped string. + +**Example:** + +Delete a user + +```javascript +var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +qb.where('id',4321).get_compiled_delete('users', function(err, res) { + if (err) return console.error(err); + + // DELETE FROM `users` WHERE `id` = 4321 + console.log(res); +}); +``` ------------- diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index df44bdb..8a0841e 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -140,6 +140,38 @@ var Adapters = function(nqb) { // @return Object Adapter object // **************************************************************************** var Pool = function() { + // Return Pool Object + var return_pool = function() { + return { + pool: function() { + return nqb.pool; + }, + get_connection: function(callback) { + if (null === nqb.pool) { + var error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + + nqb.pool.getConnection(function (err, connection) { + if (err) throw err; + var adapter = new Adapter({ + pool: { + pool: nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + nqb.pool.end(callback); + } + } + } + + // Create pool for node-querybuild object if it doesn't already have one. if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { // Create connection Pool nqb.pool = mysql.createPool(that.connection_settings); @@ -152,37 +184,11 @@ var Adapters = function(nqb) { if (that.debugging === true) { console.log('mysql connection pool created'); } + return return_pool(); }); }); - } - - // Return Pool Object - return { - pool: function() { - return nqb.pool; - }, - get_connection: function(callback) { - if (null === nqb.pool) { - var error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - - nqb.pool.getConnection(function (err, connection) { - if (err) throw err; - var adapter = new Adapter({ - pool: { - pool: nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - nqb.pool.end(callback); - } + } else { + return return_pool(); } }; diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index dce420f..e8a254c 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -279,12 +279,8 @@ var QueryBuilder = function() { }; var qb_escape = function(qb,str) { - if(qb.hasOwnProperty('escape')) { - var do_escape = qb.escape; - } else { - var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); - var do_escape = SqlString.escape; - } + var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); + var do_escape = SqlString.escape; if (typeof str === 'string' && !str.match(/^\d+$/)) { str = do_escape(str); @@ -1189,7 +1185,7 @@ var QueryBuilder = function() { table = table.trim(); - if (table !== '' && (!table.match(/[a-zA-Z]/) || !table.match(/^[a-zA-Z0-9\$_]+/))) { + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { throw new Error("insert(): Invalid table name provided!"); } @@ -1325,27 +1321,46 @@ var QueryBuilder = function() { }, update: function(table, set, where) { + table = table || ''; set = set || null; + where = where || null; + + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to update database!"); + } + + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + return this.update_batch(table, set, where); + } if (set !== null) { - this.set(set); + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } } if (this.set_array.length == 0) { throw new Error("You must set a some field value pairs to update using the set method or in object for in the second parameter of the update method!"); } + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + if (table == '') { - if (this.from_array.length === 0) { - throw new Error("You have not set any tables to update!"); + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); } - table = this.from_array[0]; } else { clear_array(this.from_array); this.from(table); - - table = this.from_array[0]; } if (where != null) { @@ -1410,7 +1425,11 @@ var QueryBuilder = function() { last_query: function() { return this.last_query_string[0] || ''; - } + }, + + escape: function(val) { + return qb_escape(this, val); + }, } }; diff --git a/index.js b/index.js index ef88a9d..687dbe9 100755 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ /** * QueryBuilder for Node.js - * (C) Kyle Farris 2014 + * (C) Kyle Farris 2014-2015 * kyle@chomponllc.com * * A generic Query Builder for any SQL or NOSQL database adapter. diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 48d09da..d2d39f0 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -20,53 +20,53 @@ describe('update()', function() { qb.update('galaxies', test_data, test_where); qb.from_array.should.eql(['`galaxies`']); }); - /* it('should only accept nothing or a string for the table (first) parameter', function() { + it('should accept a string or falsy value for the table (first) parameter', function() { qb.reset_query(); // Doing these to prevent other errors qb.from('galaxies'); - expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); - expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); - expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); - expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); - expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); + expect(function() { qb.update([], test_data); }, 'empty array provided').to.throw(Error); + expect(function() { qb.update({}, test_data); }, 'empty object provided').to.throw(Error); + expect(function() { qb.update(3, test_data); }, 'integer provided').to.throw(Error); + expect(function() { qb.update(3.5, test_data); }, 'float provided').to.throw(Error); + expect(function() { qb.update(true, test_data); }, 'true provided').to.throw(Error); + expect(function() { qb.update(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.update([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.update(/foobar/, test_data); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); - expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); + expect(function() { qb.update(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + expect(function() { qb.update(false, test_data); }, 'false provided').to.not.throw(Error); + expect(function() { qb.update('', test_data); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.update(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.update(null, test_data); }, 'null provided').to.not.throw(Error); + expect(function() { qb.update(undefined, test_data);},'undefined provided').to.not.throw(Error); }); it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { qb.reset_query(); - expect(function() { qb.insert('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',{}); }, 'empty object provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.update('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); + expect(function() { qb.update('galaxies'); }, 'nothing provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); - expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); - expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); - expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + expect(function() { qb.update('galaxies',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.update('galaxies',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.update('galaxies',true); }, 'true provided').to.throw(Error); + expect(function() { qb.update('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.update('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + expect(function() { qb.update('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.update('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); + expect(function() { qb.update('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.update('galaxies',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.update('galaxies',false); }, 'false provided').to.throw(Error); + expect(function() { qb.update('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); }); - it('should allow for an empty data parameter', function() { + /*it('should allow for an empty data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); From e8e8b10ab4b7ea9da866e90de9eab002e1df7463 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 30 Jan 2015 17:44:15 -0500 Subject: [PATCH 058/200] Added line item to the HISTORY file. --- HISTORY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index ab38869..c32a9fd 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2014-11-14) +## v0.9.0 (2015-01-30) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -33,3 +33,4 @@ you spot any mistakes. * Name of internal methods and properties have been normalized to use the "lower_case" syntax. * Dependency for node-mysql upgraded to 2.5. * travis-ci functionality added to repository +* Added public `escape()` method From dd37e166a7899fe156f01aa8b2eabd73f81c39c9 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 30 Jan 2015 18:11:35 -0500 Subject: [PATCH 059/200] Fixed bug where set value was being converted to null when falsy values were being passed. --- drivers/mysql/query_builder.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index e8a254c..10bb26b 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1322,17 +1322,18 @@ var QueryBuilder = function() { update: function(table, set, where) { table = table || ''; - set = set || null; where = where || null; - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to update database!"); - } - + // Send to batch_update if the data param is an array if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { return this.update_batch(table, set, where); } + // If set is a number, boolean, a non-empty string, or regex, fail + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } + if (set !== null) { if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { this.set(set); @@ -1344,7 +1345,7 @@ var QueryBuilder = function() { } if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); + throw new Error("update(): Table parameter must be a string!"); } table = table.trim(); @@ -1370,6 +1371,17 @@ var QueryBuilder = function() { return compile_update(this); }, + update_batch: function(table, set, where) { + for (var i in set) { + if (Object.prototype.toString.call(set[i]) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set[i]); + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + } + } + return ''; + }, + delete: function(table, where) { if ((typeof table == 'string' && table.trim().length > 0) || Object.prototype.toString.call(table) === Object.prototype.toString.call([])) { this.from(table); From 6939112fac60dd428901c9ed971b2b1203a89e9a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 4 Feb 2015 18:14:20 -0500 Subject: [PATCH 060/200] A bunch of new documentation and a working update_batch method. --- HISTORY.md | 3 +- README.md | 89 +++++++++++- drivers/mysql/query_builder.js | 249 ++++++++++++++++++++++++++++---- drivers/mysql/query_exec.js | 43 +++++- test/mysql/test-update_batch.js | 22 +++ test/mysql/tests-insert.js | 4 +- test/mysql/tests-set.js | 16 +- test/mysql/tests-update.js | 127 ++++++++-------- 8 files changed, 446 insertions(+), 107 deletions(-) create mode 100755 test/mysql/test-update_batch.js diff --git a/HISTORY.md b/HISTORY.md index c32a9fd..9bf4fbf 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2015-01-30) +## v0.9.0 (2015-02-04) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -34,3 +34,4 @@ you spot any mistakes. * Dependency for node-mysql upgraded to 2.5. * travis-ci functionality added to repository * Added public `escape()` method +* Added funtional `update_batch()` method. diff --git a/README.md b/README.md index 4fc2429..5febad0 100755 --- a/README.md +++ b/README.md @@ -1241,13 +1241,17 @@ qb.where('type',type).count('galaxies', function(err, count) { | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to update | -| data | Object | Required | The data to update (ex. `{field: value}`) | -| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | +| table | String | null | (suggested) The table/collection you'd like to update | +| data | Object | null | (suggested) The data to update (ex. `{field: value}`) | +| where | Object | null | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | | callback | Function | Required | What to do when the driver has responded. | This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... +**NOTE:** + +The first and second parameters are not required but I do suggest you use them as your code will be much easier to read. If you choose not to use them, you will need to pass a falsy value to each... you can't simply skip them. My recommendation is to use `null`. The way you would supply these values without using this method would be through the `from()` method for the first paramater and the `set()` method for the second paramter. + **Type of Response Sent to Callback** Object containing information about the results of the query. @@ -1288,11 +1292,80 @@ app.post('/update_account', function(req, res) { }); ``` +Here's another (more-direct) example where one decided to supply the table, data, and filters through alternative methods: + +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +qb.where('id', 42) + .from('users') + .set('email', 'email@domain.net') + .update(null, null, null, function(err, res) { + if (err) return console.error(err); + console.log("Updated: " + res.affected_rows + " rows); + }); +``` + ------------- -### .update_batch(table,dataset,where,callback) +### .update_batch(table,dataset,index,where,callback) -Documentation for this method coming soon! +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to insert into | +| dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) | +| index | String | Required | Name of the key in each data object that represents a `where` clause. | +| where | Object | NULL | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | +| callback | Function | Required | What to do when the driver has responded. | + +This method is a somewhat-complex one and, when using transactional databases, a bit pointless. Nevertheless, this will allow you to update a batch of rows with one query which, in theory, should be faster than running multiple update queries. + +The important thing to understand is that there are, essentially, *two* `where` clause portions with this method: a local one, and a global one. The `index` you specify in the 3rd parameter represents the name of the key in each data object of the dataset that will act as the local `where` clause for that particular row to be updated. That row, however, will only be updated if the global where clause(s) (4th param) have been satisfied as well. + +**NOTE:** This method will create batches of up to 100 rows at a time. So, if you have 250 rows to update, this will make 3 queries to your database. + +**Example:** + +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); + +// The key to use as the local where clause +var key = 'id'; + +// All objects in this dataset must have an id key +var dataset = [ + {id: 4569, name: 'Cartwheel', constellation: 'Sculptor'}, + {id: 5631, name: 'Black Eye', constellation: 'Coma Berenices'}, + {id: 1238, name: 'Sombrero', constellation: 'Virgo'} +]; + +var where = {'last_updated <' : '2015-01-01'} + +qb.update_batch('galaxies', dataset, key, where, function(err, res) { + if (err) return console.error(err); + + /* + * UPDATE `galaxies` + * SET + * `name` = CASE + * WHEN `id` = 4569 THEN 'Cartwheel' + * WHEN `id` = 5631 THEN 'Black Eye' + * WHEN `id` = 1238 THEN 'Sombrero' + * ELSE `name` + * END, + * `constellation` = CASE + * WHEN `id` = 4569 THEN 'Sculptor' + * WHEN `id` = 5631 THEN 'Coma Berenices' + * WHEN `id` = 1238 THEN 'Virgo' + * ELSE `constellation` + * END + * WHERE `id` IN(4569, 5631, 1238) + * AND `last_updated` < '2015-01-01' + */ + var last_query = qb.last_query(); +}); +``` + +As you can see, in each `CASE` statement, the `key` and it's value are being used to determine what to set the other items to. It's important to know that the `key` and it's `value` will not be updated in the batch update... they are just there to make sure we set the right values in the right place. ------------- @@ -1405,6 +1478,12 @@ app.post('/delete_comment/:id', function(req, res) { ------------- +### .set(key[, value[, escape]]) + +stuff + +------------- + Other Library-Specifc Methods ----------------------------- diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 10bb26b..8c54f0e 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -402,7 +402,9 @@ var QueryBuilder = function() { var compile_update = function(qb) { var valstr = []; for (var i in qb.set_array) { - valstr.push(i + ' = ' + qb.set_array[i]); + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + valstr.push(key + ' = ' + val); } if (qb.from_array.length === 1) { @@ -417,15 +419,42 @@ var QueryBuilder = function() { } var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; - var limit = (!limit_to ? '' : ' LIMIT ' + limit_to); var sql = 'UPDATE ' + table + " SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); - sql += build_limit_clause(qb); - return sql; + return build_limit_clause(sql, limit_to, offset_val); + }; + + var compile_insert = function(qb, ignore, suffix) { + var keys = []; + var values = []; + + for (var i in qb.set_array) { + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + + + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; }; - // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// return { @@ -434,7 +463,7 @@ var QueryBuilder = function() { from_array: [], join_array: [], select_array: [], - set_array: {}, + set_array: [], // has to be array to work as reference order_by_array: [], group_by_array: [], having_array: [], @@ -802,7 +831,6 @@ var QueryBuilder = function() { relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); } else if (!has_operator(relation)) { - console.log("Has operator: " + relation); relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; } else { @@ -1114,10 +1142,11 @@ var QueryBuilder = function() { return this; }, - set: function(key, value, escape) { + set: function(key, value, escape) { escape = (typeof escape === 'boolean' ? escape : true); if (typeof key === 'string') { + // Convert key and value params to {key: value} key = key.trim(); if (key.length == 0) { throw new Error("set(): Invalid field name provided!"); @@ -1127,8 +1156,8 @@ var QueryBuilder = function() { key_array[key] = value; key = key_array; } - else if(Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length == 0) { + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { throw new Error("set(): The object you provided is empty."); } @@ -1140,23 +1169,48 @@ var QueryBuilder = function() { throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); } + + // Add each key:value pair to the set_array for (var i in key) { var v = key[i]; if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided!"); } - else if(typeof v === 'number' && (v === Infinity || v !== +v)) { + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); } + // Escape the key to be DRY + var escaped_key = protect_identifiers(this,i,escape); + + // Build a temporary object with escaped key and val + var temp = {}; if (escape === false) { - this.set_array[protect_identifiers(this,i,escape)] = v; + temp[escaped_key] = v; + } else { + temp[escaped_key] = qb_escape(this,v); + } + + // Determine if this key has already been set + var found_index = null; + for (var j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; + } + } + + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; } else { - this.set_array[protect_identifiers(this,i,escape)] = qb_escape(this,v); + this.set_array.push(temp); } } + //console.dir(this.set_array); + return this; }, @@ -1199,10 +1253,7 @@ var QueryBuilder = function() { this.from(table); } - var keys = Object.keys(this.set_array); - var values = array_values(this.set_array); - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO ' + this.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + return compile_insert(this, ignore, suffix); }, insert_ignore: function(table, set, suffix) { @@ -1322,11 +1373,22 @@ var QueryBuilder = function() { update: function(table, set, where) { table = table || ''; + set = set || null; where = where || null; // Send to batch_update if the data param is an array if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - return this.update_batch(table, set, where); + var index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; + } + } + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + } } // If set is a number, boolean, a non-empty string, or regex, fail @@ -1334,29 +1396,36 @@ var QueryBuilder = function() { throw new Error("update(): Invalid data provided to update database!"); } + // If data object was provided, set it if (set !== null) { if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); } } + // Fail if, at this point, nothing has been set if (this.set_array.length == 0) { - throw new Error("You must set a some field value pairs to update using the set method or in object for in the second parameter of the update method!"); + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); } + // NOTE: If falsy table provided, table will have been converted to an empty string... if (typeof table !== 'string') { throw new Error("update(): Table parameter must be a string!"); } table = table.trim(); + // Table name must be in a legitimate format if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { throw new Error("update(): You have not set any tables to update!"); } + // If table not supplied, it must have been supplied already if (table == '') { if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); + throw new Error('update(): No tables set to update!'); } table = this.from_array[0]; } else { @@ -1364,22 +1433,144 @@ var QueryBuilder = function() { this.from(table); } - if (where != null) { + // Set where array if a where statement was provided + if (where !== null) { this.where(where); } - + return compile_update(this); }, - update_batch: function(table, set, where) { + update_batch: function(table, set, index, where) { + table = table || ''; + set = set || null; + index = index || null; + where = where || null; + + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } + + // Check to make sure we have a dataset + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } + + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } + + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); for (var i in set) { - if (Object.prototype.toString.call(set[i]) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set[i]); + var clean = {}; + var row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + var keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (var j in row) { + clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); + } + this.set_array.push(clean); + } } else { throw new Error("update_batch(): You have supplied an invalid object to batch update!"); } } - return ''; + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } + + // Verify there is a table in the from_array + if (this.from_array.length === 1) { + var table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); + } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } + + + // Limit to 100 rows per batch + var batches = []; + for (var i = 0, total = this.set_array.length; i < total; i += 100) { + var when_then = {}; + var ids = []; + var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); + var chunk = this.set_array.slice(i,100); + + // Escape the index + index = protect_identifiers(this, index); + + for (var j in chunk) { + ids.push(chunk[j][index]); + + var keys = Object.keys(chunk[j]); + for (var k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; + } + when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + } + } + } + + // Build the actual SQL statement + var sql = 'UPDATE ' + table + ' SET '; + var cases = ''; + + for (var l in when_then) { + cases += l + ' = CASE '; + + for (var m in when_then[l]) { + cases += when_then[l][m]; + } + + cases += 'ELSE ' + l + ' END, '; + } + + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; + + // Add query to batch + batches.push(sql); + } + + return batches; }, delete: function(table, where) { @@ -1432,7 +1623,11 @@ var QueryBuilder = function() { }, get_compiled_insert: function(table) { - throw new Error("This function has not yet been implemented!"); + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_insert(this); }, last_query: function() { diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 75290ac..4e26633 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -121,8 +121,47 @@ var QueryExec = function(qb, conn) { }, // TODO: Write this complicated-ass function - update_batch: function(table,set,where,callback,conn) { - callback(new Error("This function is not currently available!"),null); + update_batch: function(table,set,index,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sqls = qb.update_batch(table,set,index,where); + var results = null; + var errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + var sql = sqls.shift(); + qb.reset_query(sql); + + exec(sql, function(err, res) { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } + } else { + errors.push(err); + } + + if (sql.length > 0) { + setTimeout(next_batch,0); + } else { + return callback(errors, results); + } + }); + })(); }, delete: function(table, where, callback) { diff --git a/test/mysql/test-update_batch.js b/test/mysql/test-update_batch.js new file mode 100755 index 0000000..2367e19 --- /dev/null +++ b/test/mysql/test-update_batch.js @@ -0,0 +1,22 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +var test_where = {id:3}; +var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +// table, data, callback, ignore, suffix + +describe('update_batch()', function() { + it('should exist', function() { + should.exist(qb.update_batch); + }); + it('should be a function', function() { + qb.update_batch.should.be.a('function'); + }); + it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + qb.reset_query(); + var sql = qb.update_batch('galaxies', test_data, 'id'); + sql.should.eql(["UPDATE `galaxies` SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); + }); +}); \ No newline at end of file diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 163fba1..f937dcb 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -76,13 +76,13 @@ describe('insert()', function() { var sql = qb.insert(); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); - it('should utilize pre-existing tables set in in setArray', function() { + it('should utilize pre-existing values set in in set_array', function() { qb.reset_query(); qb.set(test_data); var sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); - it('should utilize pre-existing tables and values from setArray and from_array, respectively', function() { + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); var sql = qb.insert(); diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index 7224289..63793b8 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -57,46 +57,46 @@ describe('set()', function() { it('should add first param (key) and second param (value) to hash and escape them properly', function() { qb.reset_query(); qb.set('galaxy_name','Milky Way'); - qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'"}); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); }); it('should merge passed object into set_array and escape items properly', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}); - qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'"}); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); - qb.set_array.should.eql({"`galaxy_name`": "'Milky Way'", "`galaxy_class`": "'C'"}); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}, {"`galaxy_class`": "'C'"}]); }); it('should not escape items if asked not to', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.set_array.should.eql({galaxy_name: 'Milky Way'}); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}]); }); it('should append more items to set_array as set() is called', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}, null, false); qb.set('galaxy_size','D'); - qb.set_array.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); }); it('should be chainable', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); - qb.set_array.should.eql({galaxy_name: 'Milky Way', galaxy_class: 'C', "`galaxy_size`": "'D'"}); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); }); it('should overwrite values of keys that have been set already', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D'); - qb.set_array.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'D'"}); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'D'"}]); }); it('should NOT overwrite values of keys that are the same but have different escape flags', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D', false); - qb.set_array.should.eql({galaxy_name: 'Milky Way', "`galaxy_class`": "'C'", galaxy_class: 'D'}); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'C'"}, {galaxy_class: 'D'}]); }); }); \ No newline at end of file diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index d2d39f0..4c97e42 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -3,7 +3,7 @@ var expect = require('chai').expect; var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_where = {id:3}; -var test_data = {id:3, name:'Milky Way', type: 'spiral'}; +var test_data = {name:'Milky Way', type: 'spiral'}; var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; // table, data, callback, ignore, suffix @@ -23,81 +23,83 @@ describe('update()', function() { it('should accept a string or falsy value for the table (first) parameter', function() { qb.reset_query(); - // Doing these to prevent other errors - qb.from('galaxies'); + // One could choose to pass a falsy value to the first param because they have or will + // supply it with the from() method instead. - expect(function() { qb.update([], test_data); }, 'empty array provided').to.throw(Error); - expect(function() { qb.update({}, test_data); }, 'empty object provided').to.throw(Error); - expect(function() { qb.update(3, test_data); }, 'integer provided').to.throw(Error); - expect(function() { qb.update(3.5, test_data); }, 'float provided').to.throw(Error); - expect(function() { qb.update(true, test_data); }, 'true provided').to.throw(Error); - expect(function() { qb.update(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.update([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.update(/foobar/, test_data); }, 'regex provided').to.throw(Error); - - expect(function() { qb.update(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - expect(function() { qb.update(false, test_data); }, 'false provided').to.not.throw(Error); - expect(function() { qb.update('', test_data); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.update(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.update(null, test_data); }, 'null provided').to.not.throw(Error); - expect(function() { qb.update(undefined, test_data);},'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update([], test_data); }, 'empty array provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update({}, test_data); }, 'empty object provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(3, test_data); }, 'integer provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(3.5, test_data); }, 'float provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(true, test_data); }, 'true provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(/foobar/, test_data); }, 'regex provided').to.throw(Error); + + qb.reset_query(); expect(function() { qb.from('galaxies').update(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(false, test_data); }, 'false provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update('', test_data); }, 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(null, test_data); }, 'null provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.from('galaxies').update(undefined, test_data); },'undefined provided').to.not.throw(Error); }); it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { - qb.reset_query(); + // One could choose to pass a falsy value to the second param because they have or will + // supply data with the set() method instead. + + qb.reset_query(); expect(function() { qb.update('galaxies',test_data); }, 'non-empty array provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',test_data_set); }, 'array of non-empty standard objects provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',[]); }, 'empty array provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',{}); }, 'empty object provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); - expect(function() { qb.update('galaxies'); }, 'nothing provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',NaN); }, 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',false); }, 'false provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',''); }, 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',null); }, 'null provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',undefined); }, 'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies'); }, 'nothing provided').to.not.throw(Error); - expect(function() { qb.update('galaxies',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.update('galaxies',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.update('galaxies',true); }, 'true provided').to.throw(Error); - expect(function() { qb.update('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.update('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - expect(function() { qb.update('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.update('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); - expect(function() { qb.update('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.update('galaxies',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.update('galaxies',false); }, 'false provided').to.throw(Error); - expect(function() { qb.update('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3); }, 'integer provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3.5); }, 'float provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',true); }, 'true provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('foobar',{}); }, 'empty object provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[]); }, 'empty array provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); }); - /*it('should allow for an empty data parameter', function() { - qb.reset_query(); - var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + it('should require that there is at least something being updated', function() { + // @todo }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + var sql = qb.update(null, test_data, test_where); + sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); }); - it('should utilize pre-existing tables set in in setArray', function() { + it('should utilize pre-existing value set in in set_array', function() { qb.reset_query(); qb.set(test_data); - var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + var sql = qb.update('galaxies'); + sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); }); - it('should utilize pre-existing tables and values from setArray and from_array, respectively', function() { + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); - var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + var sql = qb.update(); + sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); - var sql = qb.insert('galaxies', test_data); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + var sql = qb.update('galaxies', test_data); + sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); }); - it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + qb.reset_query(); + var sql = qb.update('galaxies', test_data_set); qb.reset_query(); - var sql = qb.insert('galaxies', test_data_set); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + var sql_b = qb.update_batch('galaxies', test_data_set, 'id'); + sql.should.eql(sql_b); }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); @@ -106,15 +108,16 @@ describe('update()', function() { var arr = [1,2,3]; var obj = {foo: 'bar'}; - expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); }); + /* it('should support insert ignore statements', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); From e0cc0c043efb30b5f234d94706440cb3a7b7cd75 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 4 Feb 2015 18:29:52 -0500 Subject: [PATCH 061/200] Added documentation for the set() method. --- README.md | 35 ++++++++++++++++++++++++++++------ drivers/mysql/query_builder.js | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5febad0..2a42241 100755 --- a/README.md +++ b/README.md @@ -1000,6 +1000,35 @@ qb.limit(5).offset(25).get('users',callback); ------------- +### SET +#### .set(key[, value[, escape]]) + +This SQL is used to set values to fields when utilizing the `update`, and `insert` methods. More than likely, you will choose use the shorthand notation provided by the aforementioned methods, but, this can be handy in some cases. + +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :-------------------------------------------------------------------------------- | +| key | String/Object | Required | The key of field to be set or an object of key:value pairs | +| value | Mixed | NULL | Required if `key` is a string. Pass NULL if you'd like to use the 3rd parameter | +| escape | String/Object | true | If false, keys and values will not be escaped. | + +**Examples** + +Basic single seting of a value + +```javascript +// UPDATE `users` SET `birthday` = '2015-02-04' +qb.set('birthday','2015-02-04').update('users', callback); +``` + +Set multiple keys and values at once + +```javascript +// UPDATE `users` SET `birthday` = '2015-02-04', `anniversary` = '2010-05-15' +qb.set({birthday: '1986-02-04', anniversary: '2010-05-15'}).update('users', callback); +``` + +------------- + Execution Methods ----------------- @@ -1478,12 +1507,6 @@ app.post('/delete_comment/:id', function(req, res) { ------------- -### .set(key[, value[, escape]]) - -stuff - -------------- - Other Library-Specifc Methods ----------------------------- diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 8c54f0e..5b582d4 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1142,7 +1142,7 @@ var QueryBuilder = function() { return this; }, - set: function(key, value, escape) { + set: function(key, value, escape) { escape = (typeof escape === 'boolean' ? escape : true); if (typeof key === 'string') { From 718eb3c60ac18c3db8e7b9afe9f7a174a3eb59b0 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 4 Feb 2015 18:31:37 -0500 Subject: [PATCH 062/200] Minor tweak to update_batch documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a42241..e26dff9 100755 --- a/README.md +++ b/README.md @@ -1336,7 +1336,7 @@ qb.where('id', 42) ------------- -### .update_batch(table,dataset,index,where,callback) +### .update_batch(table,dataset,index[,where],callback) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | From 5288f35ef46cabcaa374398311116f364f486498 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 5 Feb 2015 14:29:04 -0500 Subject: [PATCH 063/200] Updated documentation for the query compile methods and fixed a bug with the Pool Adapter. --- README.md | 72 ++++++++++++++++++--------------------- drivers/mysql/adapters.js | 35 +++++++++---------- index.js | 2 +- 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index e26dff9..72980de 100755 --- a/README.md +++ b/README.md @@ -1005,11 +1005,11 @@ qb.limit(5).offset(25).get('users',callback); This SQL is used to set values to fields when utilizing the `update`, and `insert` methods. More than likely, you will choose use the shorthand notation provided by the aforementioned methods, but, this can be handy in some cases. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :-------------------------------------------------------------------------------- | -| key | String/Object | Required | The key of field to be set or an object of key:value pairs | -| value | Mixed | NULL | Required if `key` is a string. Pass NULL if you'd like to use the 3rd parameter | -| escape | String/Object | true | If false, keys and values will not be escaped. | +| Parameter | Type | Default | Description | +| :-------- | :------------ | :----- | :-------------------------------------------------------------------------------------------------------- | +| key | String/Object | Required | The key of field to be set or an object of key:value pairs | +| value | Mixed | NULL | Required if `key` is a string. Pass NULL if `key` is an object and you'd like to use the 3rd parameter | +| escape | String/Object | true | If false, keys and values will not be escaped. | **Examples** @@ -1023,8 +1023,9 @@ qb.set('birthday','2015-02-04').update('users', callback); Set multiple keys and values at once ```javascript +var birthday = new Date(1986, 7, 5, 8, 15, 23); // UPDATE `users` SET `birthday` = '2015-02-04', `anniversary` = '2010-05-15' -qb.set({birthday: '1986-02-04', anniversary: '2010-05-15'}).update('users', callback); +qb.set({birthday: birthday, anniversary: '2010-05-15'}).update('users', callback); ``` ------------- @@ -1589,14 +1590,13 @@ These methods can be used to build a query string without having to execute it. These are excellent educational tools and can be used like a SQL/NoSQL language rosetta stone of sorts. -The callback will respond with the standard `(err, res)` format. The `res` parameter will contain the compiled query string. +These methods are not asynchronous and, therefore, just return the compiled query string. -#### .get_compiled_select(table, callback) +#### .get_compiled_select(table) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | -| callback | Function | Required | What to do when the string has been compiled | Compiles a SELECT-like query into a properly-escaped string. @@ -1606,25 +1606,24 @@ Get certain details of a user account ```javascript var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -qb.select(['id','username','first_name','last_name']) + +var sql = qb + .select(['id','username','first_name','last_name']) .from('users') .like('username','k','after') - .get_compiled_select(function(err, res) { - if (err) return console.error(err); - - // SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' - console.log(res); - }); + .get_compiled_select(); + +// SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' +console.log(sql); ``` ------------- -#### .get_compiled_insert(table, callback) +#### .get_compiled_insert(table) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | -| callback | Function | Required | What to do when the string has been compiled | Compiles a INSERT-like query into a properly-escaped string. @@ -1641,22 +1640,20 @@ var data = { first_name: 'Foo', last_name: 'Bar' }; -qb.set(data).get_compiled_insert('users', function(err, res) { - if (err) return console.error(err); - - // INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES ('foobar', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Foo', 'Bar') - console.log(res); +var sql = qb.set(data).get_compiled_insert('users'); + +// INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES ('foobar', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Foo', 'Bar') +console.log(sql); }); ``` ------------- -#### .get_compiled_update(table, callback) +#### .get_compiled_update(table) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | -| callback | Function | Required | What to do when the string has been compiled | Compiles an UPDATE-like query into a properly-escaped string. @@ -1670,24 +1667,22 @@ var crypto = require('crypto'); var data = { password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), }; -qb.where('id',4321) +var sql = qb + .where('id',4321) .set(data) - .get_compiled_update('users', function(err, res) { - if (err) return console.error(err); - - // UPDATE `users` SET `password` = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' WHERE `id` = 4321 - console.log(res); - }); + .get_compiled_update('users'); + +// UPDATE `users` SET `password` = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' WHERE `id` = 4321 +console.log(sql); ``` ------------- -#### .get_compiled_delete(table, callback) +#### .get_compiled_delete(table) | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | -| callback | Function | Required | What to do when the string has been compiled | Compiles a SELECT-like query into a properly-escaped string. @@ -1697,11 +1692,10 @@ Delete a user ```javascript var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -qb.where('id',4321).get_compiled_delete('users', function(err, res) { - if (err) return console.error(err); - - // DELETE FROM `users` WHERE `id` = 4321 - console.log(res); +var sql = qb.where('id',4321).get_compiled_delete('users'); + +// DELETE FROM `users` WHERE `id` = 4321 +console.log(sql); }); ``` diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 8a0841e..ddee8f7 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -12,9 +12,9 @@ var Adapters = function(nqb) { // Enable debugging if necessary this.debugging = false; - if (nqb.settings.hasOwnProperty('qb_debug') && settings.qb_debug === true) { + if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { this.debugging = true; - delete settings.qb_debug; + delete nqb.settings.qb_debug; } // Verify that required fields are provided... @@ -169,27 +169,28 @@ var Adapters = function(nqb) { nqb.pool.end(callback); } } - } + }; // Create pool for node-querybuild object if it doesn't already have one. if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { // Create connection Pool nqb.pool = mysql.createPool(that.connection_settings); - // Test connection pool (asynchronous) - nqb.pool.getConnection(function(err, connection) { - connection.query('SELECT 1 + 1 AS solution', function(err) { - connection.release(); - if (err) throw err; - if (that.debugging === true) { - console.log('mysql connection pool created'); - } - return return_pool(); + // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) + if (that.debugging === true) { + nqb.pool.getConnection(function(err, connection) { + connection.query('SELECT 1 + 1 AS solution', function(err) { + connection.release(); + if (err) { + console.error(err); + } else { + console.log('mysql connection pool created'); + } + }); }); - }); - } else { - return return_pool(); + } } + return return_pool(); }; // **************************************************************************** @@ -222,9 +223,7 @@ var Adapters = function(nqb) { } } - var adapter = determine_adapter(); - //console.dir(adapter); process.exit(1); - return adapter; + return determine_adapter(); }; exports.Adapters = Adapters; diff --git a/index.js b/index.js index 687dbe9..8758ab3 100755 --- a/index.js +++ b/index.js @@ -113,7 +113,7 @@ var QueryBuilder = function(settings,driver,type) { // **************************************************************************** var get_adapter = function(qb) { try { - var adapter = adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); + var adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); return adapter; } catch(e) { throw new Error("Couldn't load the Connection library for " + qb.driver + ": " + e); From c233c530ad39aaec0c284109bebd14fd46d91f2f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 5 Feb 2015 14:35:24 -0500 Subject: [PATCH 064/200] Added aliases to query compilation methods. --- README.md | 8 ++++++++ drivers/mysql/query_builder.js | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 72980de..f5a8e56 100755 --- a/README.md +++ b/README.md @@ -1594,6 +1594,8 @@ These methods are not asynchronous and, therefore, just return the compiled quer #### .get_compiled_select(table) +***Alias:*** **compile_select(table)** + | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | @@ -1621,6 +1623,8 @@ console.log(sql); #### .get_compiled_insert(table) +***Alias:*** **compile_insert(table)** + | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | @@ -1651,6 +1655,8 @@ console.log(sql); #### .get_compiled_update(table) +***Alias:*** **compile_update(table)** + | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | @@ -1680,6 +1686,8 @@ console.log(sql); #### .get_compiled_delete(table) +***Alias:*** **compile_delete(table)** + | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | | table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 5b582d4..7f81457 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1630,6 +1630,22 @@ var QueryBuilder = function() { return compile_insert(this); }, + compile_select: function(table) { + return this.get_compiled_select(table); + }, + + compile_delete: function(table) { + return this.get_compiled_delete(table); + }, + + compile_update: function(table) { + return this.get_compiled_update(table); + }, + + compile_insert: function(table) { + return this.get_compiled_insert(table); + }, + last_query: function() { return this.last_query_string[0] || ''; }, From a3eb44ab1cf6386d268b295a8c2695cfecceea0f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 5 Feb 2015 17:12:27 -0500 Subject: [PATCH 065/200] Added truncate() and empty_table() methods, fixed a potential issue with the delete() method and added/enhanced some documentation. --- HISTORY.md | 3 +- README.md | 184 ++++++++++++++++++++++++++++++++- drivers/mysql/query_builder.js | 39 ++++++- test/mysql/test-empty_table.js | 50 +++++++++ test/mysql/test-truncate.js | 24 +++++ test/mysql/tests-delete.js | 49 +++++---- 6 files changed, 317 insertions(+), 32 deletions(-) create mode 100755 test/mysql/test-empty_table.js create mode 100755 test/mysql/test-truncate.js diff --git a/HISTORY.md b/HISTORY.md index 9bf4fbf..cd90eda 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v0.9.0 (2015-02-04) +## v0.9.0 (2015-02-05) * Added this history file * Added the ability to do `SELECT DISTINCT` queries @@ -35,3 +35,4 @@ you spot any mistakes. * travis-ci functionality added to repository * Added public `escape()` method * Added funtional `update_batch()` method. +* Added `truncate()` and `empty_table()` methods. diff --git a/README.md b/README.md index f5a8e56..7980cb2 100755 --- a/README.md +++ b/README.md @@ -1045,6 +1045,8 @@ Execution Methods | [insert_batch()](#insert_batch) | N/A | ✓ | | | | | | | [insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | | | [delete()](#delete) | DELETE | ✓ | | | | | | +| [truncate()](#truncate) | TRUNCATE | ✓ | | | | | | +| [empty_table()](#empty_table) | DELETE | ✓ | | | | | | ### What are "Execution Methods"?? @@ -1115,6 +1117,7 @@ pool.get_connection(function(qb) { ------------- + ### .query(query_string,callback) | Parameter | Type | Default | Description | @@ -1138,6 +1141,7 @@ qb.query("CREATE VIEW `foobar` AS " + sql, callback); ------------- + ### .get([table,]callback) | Parameter | Type | Default | Description | @@ -1207,6 +1211,7 @@ qb.limit(10) ------------- + ### .get_where(table,where,callback) | Parameter | Type | Default | Description | @@ -1239,6 +1244,7 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca ------------- + ### .count([table,]callback) | Parameter | Type | Default | Description | @@ -1267,6 +1273,7 @@ qb.where('type',type).count('galaxies', function(err, count) { ------------- + ### .update(table,data[,where],callback) | Parameter | Type | Default | Description | @@ -1337,6 +1344,7 @@ qb.where('id', 42) ------------- + ### .update_batch(table,dataset,index[,where],callback) | Parameter | Type | Default | Description | @@ -1399,6 +1407,7 @@ As you can see, in each `CASE` statement, the `key` and it's value are being use ------------- + ### .insert(table,data,callback) | Parameter | Type | Default | Description | @@ -1449,18 +1458,21 @@ app.post('/add_article', function(req, res) { ------------- + ### .insert_batch(table,dataset,callback) Documentation for this method coming soon! ------------- + ### .insert_ignore(table,data,callback) Documentation for this method coming soon! ------------- + ### .delete(table,where,callback) | Parameter | Type | Default | Description | @@ -1471,6 +1483,8 @@ Documentation for this method coming soon! This method is used to delete records from a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with the number of affected rows. +**NOTE:** If tables are added to the querybuilder query cache via the `from()` method, only first table in the array (the first added) will be used for this method. + **Type of Response Sent to Callback** Object containing information about the result of the query. @@ -1502,6 +1516,103 @@ app.post('/delete_comment/:id', function(req, res) { } return res.render('/article/' + article_id, page_data); }); + }); + }); +}); +``` + +------------- + + +### .truncate(table,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------- | +| table | String | Required | The table/collection you'd like to truncate. | +| callback | Function | Required | What to do when the driver has responded. | + +For drivers that support it (MySQL), this method will utilize the `TRUNCATE` directive to empty a table of all it's data. The main difference between the `truncate()` method and the `empty_table()` method is that, when available, and when possible, truncating a table will reset your AUTO_INCREMENT counter back to zero. If you simply delete every row from a table, the next item inserted will just continue with the next highest ID from the deleted records. + +For drivers that don't support the truncate method, this will simply act as a wrapper to the [.empty_table()](#empty_table) method. + +**Type of Response Sent to Callback** + +Object containing information about the result of the query. + +**Examples** + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +/* + * Assume we have a table like this to start with... + * [ + * { id: 1, name: 'Mary' }, + * { id: 2, name: 'Jane' }, + * { id: 3, name: 'Joe' } + * ]; + */ + +pool.get_connection(function(qb) { + qb.truncate('users', function(err, res) { + if (err) throw err; + qb.insert('users', {name: 'Bob'}, function(err, res) { + if (err) throw err; + qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.release(); + if (err) throw err; + // { id: 1, name: 'Bob' } (notice ID is 1) + console.dir(res); + }); + }); + }); +}); +``` + +------------- + + +### .empty_table(table,callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------- | +| table | String | Required | The table/collection you'd like to truncate. | +| callback | Function | Required | What to do when the driver has responded. | + +This method will allow you to delete all records from a table/collection. + +**Type of Response Sent to Callback** + +Object containing information about the result of the query. + +**Examples** + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +/* + * Assume we have a table like this to start with... + * [ + * { id: 1, name: 'Mary' }, + * { id: 2, name: 'Jane' }, + * { id: 3, name: 'Joe' } + * ]; + */ + +pool.get_connection(function(qb) { + qb.empty_table('users', function(err, res) { + if (err) throw err; + qb.insert('users', {name: 'Bob'}, function(err, res) { + if (err) throw err; + qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.release(); + if (err) throw err; + // { id: 4, name: 'Bob' } (notice ID is 4) + console.dir(res); + }); + }); }); }); ``` @@ -1516,6 +1627,7 @@ These are methods that aren't part of the query-building chain, but, rather, met | API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | | :-------------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | | [get_connection()](#get_connection) | ✓ | | | | | | +| [release()](#release) | ✓ | | | | | | | [last_query()](#last_query) | ✓ | | | | | | | [escape()](#escape) | ✓ | | | | | | | [get_compiled_select()](#get_compiled_select) | ✓ | | | | | | @@ -1523,13 +1635,68 @@ These are methods that aren't part of the query-building chain, but, rather, met | [get_compiled_update()](#get_compiled_update) | ✓ | | | | | | | [get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | | +------------- + ### .get_connection(callback) -Used to get a new connection from the connection pool or cluster pool. +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :------------------------------------------------------------ | +| callback | Function | Required | What to do when the connection is retrieved from the pool. | + +Used to get a new connection from the connection pool or cluster pool. An instances of the QueryBuilder adapter for your specific connection will be passed to the callback. Make sure that your connection is [release](#release)d when you are done with it! + +**Example** + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +pool.get_connection(function(qb) { + qb.limit(10).get('users', function(err, res) { + qb.release(); + // Do stuff with results or err + }); +}); +``` + +------------- + + +### .release() + +Releases a connection back to the pool when you are done with it. Calling this is *super* important! + +**Example** + +Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It them loops over each one and removed the `|` from the username and reinserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +pool.get_connection(function(qb) { + qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { + var users = users; + (function update_user() { + var user = users.shift(); + user.username = user.username.replace(/\^|/,''); + + qb.update('users', user, {id: user.id}, function(err, res) { + if (user.length > 0) { + setTimeout(update_user,0); + } else { + qb.release(); + } + }); + })(); + }); +}); +``` ------------- + ### .last_query() This is used to ascertain the query string that was most-recently executed. This MUST be called before closing the connection or releasing a connection back to the pool. This is useful for debugging what the `node-querybuilder` library is executing (or trying to execute). @@ -1551,6 +1718,7 @@ pool.get_connection(function(qb) { ------------- + ### .escape(value) | Parameter | Type | Default | Description | @@ -1592,9 +1760,12 @@ These are excellent educational tools and can be used like a SQL/NoSQL language These methods are not asynchronous and, therefore, just return the compiled query string. +------------- + + #### .get_compiled_select(table) -***Alias:*** **compile_select(table)** +***Alias:*** *compile_select(table)* | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | @@ -1621,9 +1792,10 @@ console.log(sql); ------------- + #### .get_compiled_insert(table) -***Alias:*** **compile_insert(table)** +***Alias:*** *compile_insert(table)* | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | @@ -1653,9 +1825,10 @@ console.log(sql); ------------- + #### .get_compiled_update(table) -***Alias:*** **compile_update(table)** +***Alias:*** *compile_update(table)* | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | @@ -1684,9 +1857,10 @@ console.log(sql); ------------- + #### .get_compiled_delete(table) -***Alias:*** **compile_delete(table)** +***Alias:*** *compile_delete(table)* | Parameter | Type | Default | Description | | :-------- | :-------- | :----- | :------------------------------------------------------------ | diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 7f81457..441348c 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -387,11 +387,13 @@ var QueryBuilder = function() { }; var compile_delete = function(qb) { - if (qb.from_array.length == 0) { + if (qb.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); - return this; + return ''; } + qb.from_array = qb.from_array.slice(0,1); + var limit_to = qb.limit_to[0] || false; var offset_val = qb.offset_val[0] || false; @@ -1573,8 +1575,9 @@ var QueryBuilder = function() { return batches; }, - delete: function(table, where) { - if ((typeof table == 'string' && table.trim().length > 0) || Object.prototype.toString.call(table) === Object.prototype.toString.call([])) { + delete: function(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + clear_array(this.from_array); this.from(table); } @@ -1653,6 +1656,34 @@ var QueryBuilder = function() { escape: function(val) { return qb_escape(this, val); }, + + empty_table: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } + + return "DELETE FROM " + this.from_array[0]; + }, + + truncate: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } + + return "TRUNCATE " + this.from_array[0]; + }, } }; diff --git a/test/mysql/test-empty_table.js b/test/mysql/test-empty_table.js new file mode 100755 index 0000000..7d17165 --- /dev/null +++ b/test/mysql/test-empty_table.js @@ -0,0 +1,50 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +describe('emtpy_table()', function() { + it('should exist', function() { + should.exist(qb.empty_table); + }); + it('should be a function', function() { + qb.empty_table.should.be.a('function'); + }); + it('should return a string', function() { + qb.reset_query(); + var sql = qb.empty_table('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper DELETE statement', function() { + qb.reset_query(); + var sql = qb.empty_table('galaxies'); + sql.should.eql('DELETE FROM `galaxies`'); + }); + it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { + qb.reset_query(); + + expect(function() { qb.empty_table([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.empty_table({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.empty_table(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.empty_table(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.empty_table(true); }, 'true provided').to.throw(Error); + expect(function() { qb.empty_table(Infinity);}, 'Infinity provided').to.throw(Error); + expect(function() { qb.empty_table([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.empty_table(/foobar/);}, 'regex provided').to.throw(Error); + expect(function() { qb.empty_table(NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.empty_table(false); }, 'false provided').to.throw(Error); + expect(function() { qb.empty_table(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.empty_table(' '); }, 'string full of spaces provided').to.throw(Error); + expect(function() { qb.empty_table(null); }, 'null provided').to.throw(Error); + + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(function() { qb.empty_table(undefined); },'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(function() { qb.empty_table(); },'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { + qb.reset_query(); + var sql = qb.from(['galaxies','star_systems','planets']).empty_table(); + sql.should.eql("DELETE FROM `galaxies`"); + }); +}); \ No newline at end of file diff --git a/test/mysql/test-truncate.js b/test/mysql/test-truncate.js new file mode 100755 index 0000000..b6d387e --- /dev/null +++ b/test/mysql/test-truncate.js @@ -0,0 +1,24 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +describe('truncate()', function() { + it('should exist', function() { + should.exist(qb.truncate); + }); + it('should be a function', function() { + qb.truncate.should.be.a('function'); + }); + it('should return a string', function() { + qb.reset_query(); + var sql = qb.truncate('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper truncate statement', function() { + qb.reset_query(); + var sql = qb.truncate('galaxies'); + sql.should.eql('TRUNCATE `galaxies`'); + }); +}); \ No newline at end of file diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 60fa86c..ab273cd 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -14,25 +14,40 @@ describe('delete()', function() { qb.delete('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); - it('should add a set of tables to from_array when an array of tables is supplied', function() { + it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { + qb.reset_query(); + + expect(function() { qb.delete([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.delete({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.delete(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.delete(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.delete(true); }, 'true provided').to.throw(Error); + expect(function() { qb.delete(Infinity);}, 'Infinity provided').to.throw(Error); + expect(function() { qb.delete([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.delete(/foobar/);}, 'regex provided').to.throw(Error); + expect(function() { qb.delete(NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.delete(false); }, 'false provided').to.throw(Error); + expect(function() { qb.delete(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.delete(' '); }, 'string full of spaces provided').to.throw(Error); + expect(function() { qb.delete(null); }, 'null provided').to.throw(Error); + + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(function() { qb.delete(undefined); },'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(function() { qb.delete(); },'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { qb.reset_query(); - qb.delete(['galaxies','star_systems','planets']); - qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + var sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql("DELETE FROM (`galaxies`)"); }); it('should add where conditions to where_array when conditions are supplied', function() { qb.reset_query(); qb.delete('planets', {continents: 7, star_system: 'Solar'}); qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); }); - it('should accept an array of tables and an object of where conditions and put each in their respective arrays', function() { - qb.reset_query(); - qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); - qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); - qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); - }); it('should return a string', function() { qb.reset_query(); - var sql = qb.delete(['galaxies','star_systems','planets'], {continents: 7, star_system: 'Solar'}); + var sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); @@ -47,17 +62,7 @@ describe('delete()', function() { var sql = qb.delete('galaxies', {class: 'M'}); sql.should.eql("DELETE FROM (`galaxies`) WHERE `class` = 'M'"); }); - it('should build a properly-escaped delete statement that deletes all records in a set of tables if an array of tables is given', function() { - qb.reset_query(); - var sql = qb.delete(['galaxies','star_systems','planets']); - sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); - }); - it('should build a properly-escaped delete statement that deletes all records in a set of tables that match the passed conditions', function() { - qb.reset_query(); - var sql = qb.delete(['galaxies','star_systems','planets'], {class: 'M'}); - sql.should.eql("DELETE FROM (`galaxies`, `star_systems`, `planets`) WHERE `class` = 'M'"); - }); - it('should use tables added previously via the from() method', function() { + it('should use ONLY the FIRST table added previously via the from() method', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.delete(); @@ -65,7 +70,7 @@ describe('delete()', function() { qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).delete(); - sql.should.eql('DELETE FROM (`galaxies`, `star_systems`, `planets`)'); + sql.should.eql('DELETE FROM (`galaxies`)'); }); it('should accept where conditions added previously via the where() method', function() { qb.reset_query(); From d7a95ba08536c080ef7a97e0dc086b984434e748 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 5 Feb 2015 18:00:54 -0500 Subject: [PATCH 066/200] Added tests for insert_batch() and fixed a few bugs. --- drivers/mysql/query_builder.js | 41 +++++++++--- test/mysql/test-update_batch.js | 4 +- test/mysql/tests-insert.js | 3 +- test/mysql/tests-insert_batch.js | 103 +++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 14 deletions(-) create mode 100755 test/mysql/tests-insert_batch.js diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 441348c..238a3b9 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1242,12 +1242,12 @@ var QueryBuilder = function() { table = table.trim(); if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { - throw new Error("insert(): Invalid table name provided!"); + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } if (table == '') { if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); + throw new Error('insert(): No tables set to insert into!'); } table = this.from_array[0]; } else { @@ -1264,17 +1264,26 @@ var QueryBuilder = function() { insert_batch: function(table,set,ignore,suffix) { var self = this; - table = table || ''; + var orig_table = table = table || ''; set = set || null; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if(suffix == ' ') suffix = ''; + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } if (table == '') { if (this.from_array.length === 0) { throw new Error("insert_batch(): You have not set any tables to insert into."); } - table = this.from_array[0]; } else { clear_array(this.from_array); @@ -1286,15 +1295,27 @@ var QueryBuilder = function() { } for (var key in set) { - var value = set[key]; - var is_object = Object.prototype.toString.call(value) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(value).length === 0)) { + var row = set[key]; + var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (var i in row) { + var v = row[i]; + + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } } } - - if (set.length == 0) return false; + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } var map = []; var columns = []; diff --git a/test/mysql/test-update_batch.js b/test/mysql/test-update_batch.js index 2367e19..75f1843 100755 --- a/test/mysql/test-update_batch.js +++ b/test/mysql/test-update_batch.js @@ -5,8 +5,6 @@ var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_where = {id:3}; var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -// table, data, callback, ignore, suffix - describe('update_batch()', function() { it('should exist', function() { should.exist(qb.update_batch); @@ -14,7 +12,7 @@ describe('update_batch()', function() { it('should be a function', function() { qb.update_batch.should.be.a('function'); }); - it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + it('should build a proper batch UPDATE string', function() { qb.reset_query(); var sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE `galaxies` SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index f937dcb..e0dbb7c 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -96,7 +96,8 @@ describe('insert()', function() { it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies', test_data_set); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + var sql_b = qb.insert_batch('galaxies', test_data_set); + sql.should.eql(sql_b); }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js new file mode 100755 index 0000000..eba7c60 --- /dev/null +++ b/test/mysql/tests-insert_batch.js @@ -0,0 +1,103 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +var test_where = {id:3}; +var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +describe('insert_batch()', function() { + it('should exist', function() { + should.exist(qb.insert_batch); + }); + it('should be a function', function() { + qb.insert_batch.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.insert_batch('galaxies', test_data); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a string for the table (first) parameter', function() { + qb.reset_query(); + + // Doing these to prevent other errors + qb.from('galaxies'); + + expect(function() { qb.insert_batch([], test_data); }, 'empty array provided').to.throw(Error); + expect(function() { qb.insert_batch({}, test_data); }, 'empty object provided').to.throw(Error); + expect(function() { qb.insert_batch(3, test_data); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert_batch(3.5, test_data); }, 'float provided').to.throw(Error); + expect(function() { qb.insert_batch(true, test_data); }, 'true provided').to.throw(Error); + expect(function() { qb.insert_batch(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert_batch([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert_batch(/foobar/, test_data); }, 'regex provided').to.throw(Error); + + expect(function() { qb.insert_batch(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + expect(function() { qb.insert_batch(false, test_data); }, 'false provided').to.not.throw(Error); + expect(function() { qb.insert_batch('', test_data); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert_batch(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.insert_batch(null, test_data); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert_batch(undefined, test_data); }, 'undefined provided').to.not.throw(Error); + }); + it('should build a proper batch INSERT string', function() { + qb.reset_query(); + var sql = qb.insert_batch('galaxies', test_data); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should only accept an array as the second parameter', function() { + qb.reset_query(); + + expect(function() { qb.insert_batch('galaxies',test_data); }, 'array of objects provided').to.not.throw(Error); + expect(function() { qb.insert_batch('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + + expect(function() { qb.insert_batch('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[test_data,test_data]); }, 'array of arrays provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',null); }, 'null provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',undefined); }, 'undefined provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies'); }, 'nothing provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',true); }, 'true provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[Date, /foobar/, null]); }, 'array of non-standard objects provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',false); }, 'false provided').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', function() { + qb.reset_query(); + var sql = qb.insert_batch('galaxies',[]); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', function() { + qb.reset_query(); + qb.from('galaxies'); + var sql = qb.insert_batch(null,[]); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { + qb.reset_query(); + var func = function() { console.log("foo"); }; + var regex = /foobar/; + var arr = [1,2,3]; + var obj = {foo: 'bar'}; + + expect(function() { qb.insert_batch('galaxies',[{id: func}]); }, 'function in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: regex}]); }, 'regex in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: Infinity}]); }, 'Infinity in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: undefined}]); }, 'undefined in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: NaN}]); }, 'NaN in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: arr}]); }, 'array in data').to.throw(Error); + expect(function() { qb.insert_batch('galaxies',[{id: obj}]); }, 'object in data').to.throw(Error); + }); + it('should support insert ignore statements', function() { + qb.reset_query(); + var sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); +}); \ No newline at end of file From 557411926b663858860ab91d17d751420d78aaa6 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Feb 2015 14:56:06 -0500 Subject: [PATCH 067/200] Added documentation for the and methods. Modified the documentation. --- README.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7980cb2..d08106d 100755 --- a/README.md +++ b/README.md @@ -1408,13 +1408,15 @@ As you can see, in each `CASE` statement, the `key` and it's value are being use ------------- -### .insert(table,data,callback) +### .insert(table,data[,ignore[,on_dupe]],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to insert into | -| data | Object | Required | The data to insert (ex. `{field: value}`) | -| callback | Function | Required | What to do when the driver has responded. | +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to insert into | +| data | Object | Required | The data to insert (ex. `{field: value}`) | +| ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. | +| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | +| callback | Function | Required | What to do when the driver has responded. | This method is used to insert new data into a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the ID of the newly inserted item, the affected rows (should be 1), etc... @@ -1459,16 +1461,110 @@ app.post('/add_article', function(req, res) { ------------- -### .insert_batch(table,dataset,callback) +### .insert_batch(table,dataset[,ignore[,on_dupe]],callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to delete records from. | +| dataset | Array | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. | +| ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. | +| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | +| callback | Function | Required | What to do when the driver has responded. | + +The goal of this method is to speed the insertion of many rows. For instance, if you were insert 1,000 rows... Instead of making 1,000 queries to the server, you could just call `insert_batch()` and it would generate a single query to insert 1,000 rows. This is *much* more efficient and less taxing on your app and database server. -Documentation for this method coming soon! +**Type of Response Sent to Callback** + +Object containing information about the result of the query. + +**Example** + +```javascript +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); + +var data = [ + {name: 'MySQL', version: '5.5.40'}, + {name: 'Mongo', version: '2.6.7' }, + {name: 'Postgres', version: '8.4'} +]; + +qb.insert_batch('db_engines', data, function(err, res) { + if (err) throw err; + + // INSERT INTO `db_engines` (`name`, `version`) + // VALUES ('MySQL', '5.5.40'), ('Mongo', '2.6.7'), ('Postgres', '8.4'); + console.log(qb.last_query()); +}); +``` ------------- -### .insert_ignore(table,data,callback) +### .insert_ignore(table,data[,on_dupe],callback) + +| Parameter | Type | Default | Description | +| :-------- | :-------- | :----- | :-------------------------------------------------------------------------------------------------------------------- | +| table | String | Required | The table/collection you'd like to delete records from. | +| data | Object | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. | +| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | +| callback | Function | Required | What to do when the driver has responded. | + +This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd paramter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). + +**Type of Response Sent to Callback** -Documentation for this method coming soon! +Object containing information about the result of the query. + +**Example** + +```javascript +/* + * Current Table Structure: + * + * [ + * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, + * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, + * {name: 'Postgres', version: '8.4', last_modified: 1423252248 } + * ] + */ + +var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +qb.insert_ignore('db_engines', data, function(err, res) { + if (err) throw err; + var data = {name: 'Postgres', version: '8.4'}; + + // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); + console.log(qb.last_query()); + + // 0 (because this data already exists...) + console.log(res.affected_rows); +}); +``` + +This time we'll do it with an `on_dupe` string + +```javascript +qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { + if (err) throw err; + var data = {name: 'Postgres', version: '8.4'}; + + // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); + console.log(qb.last_query()); + + // 1 (because we updated the last_modified field) + console.log(res.affected_rows); + + /* + * Resulting Table Structure: + * + * [ + * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, + * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, + * {name: 'Postgres', version: '8.4', last_modified: 1423264972 } + * ] + */ +}); +``` ------------- From 535257f4c7ff6d787015280bf144f5e02a75850a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Feb 2015 16:42:04 -0500 Subject: [PATCH 068/200] Wrote tests for the compilation methods. --- README.md | 26 +++++-- drivers/mysql/query_builder.js | 12 ++-- test/mysql/tests-compilation_methods.js | 69 +++++++++++++++++++ .../{test-distinct.js => tests-distinct.js} | 0 ...st-empty_table.js => tests-empty_table.js} | 0 test/mysql/tests-insert.js | 39 +++++++++-- test/mysql/tests-insert_batch.js | 8 +-- test/mysql/{test-limit.js => tests-limit.js} | 0 .../mysql/{test-offset.js => tests-offset.js} | 0 .../{test-truncate.js => tests-truncate.js} | 0 test/mysql/tests-update.js | 8 +-- ...-update_batch.js => tests-update_batch.js} | 2 +- 12 files changed, 138 insertions(+), 26 deletions(-) create mode 100755 test/mysql/tests-compilation_methods.js rename test/mysql/{test-distinct.js => tests-distinct.js} (100%) rename test/mysql/{test-empty_table.js => tests-empty_table.js} (100%) rename test/mysql/{test-limit.js => tests-limit.js} (100%) rename test/mysql/{test-offset.js => tests-offset.js} (100%) rename test/mysql/{test-truncate.js => tests-truncate.js} (100%) rename test/mysql/{test-update_batch.js => tests-update_batch.js} (71%) diff --git a/README.md b/README.md index d08106d..3df0516 100755 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ Chainable methods can be called as many times as you'd like in any order you lik | [from()](#from) | FROM | ✓ | | | | | | | [join()](#join) | JOIN | ✓ | | | | | | | [where()](#where) | WHERE | ✓ | | | | | | +| [where_in()](#where_in) | IN | ✓ | | | | | | | [where_not_in()](#where_not_in) | WHERE | ✓ | | | | | | | [or_where()](#or_where) | WHERE | ✓ | | | | | | | [or_where_in()](#or_where_in) | WHERE | ✓ | | | | | | @@ -230,7 +231,6 @@ Chainable methods can be called as many times as you'd like in any order you lik | [or_like()](#or_like) | LIKE | ✓ | | | | | | | [or_not_like()](#or_not_like) | LIKE | ✓ | | | | | | | [not_like()](#not_like) | LIKE | ✓ | | | | | | -| [where_in()](#where_in) | IN | ✓ | | | | | | | [group_by()](#group-by) | GROUP BY | ✓ | | | | | | | [having()](#having) | HAVING | ✓ | | | | | | | [or_having()](#or_having) | HAVING | ✓ | | | | | | @@ -1763,9 +1763,9 @@ pool.get_connection(function(qb) { Releases a connection back to the pool when you are done with it. Calling this is *super* important! -**Example** +**Examples** -Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It them loops over each one and removed the `|` from the username and reinserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. +Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. ```javascript var settings = require('db.json'); @@ -1790,12 +1790,29 @@ pool.get_connection(function(qb) { }); ``` +Here's a simpler example so you can better see how it will most often be used + +```javascript +var settings = require('db.json'); +var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +pool.get_connection(function(qb) { + qb.get_where('users', {username: 'foobar'}, function(err, res) { + qb.release(); + if (err) throw err; + console.dir(res); + }); +}); +``` + ------------- ### .last_query() -This is used to ascertain the query string that was most-recently executed. This MUST be called before closing the connection or releasing a connection back to the pool. This is useful for debugging what the `node-querybuilder` library is executing (or trying to execute). +This is used to retrieve the query string that was most-recently executed. This MUST be called before closing the connection or releasing a connection back to the pool. This is useful for debugging what the `node-querybuilder` library is executing (or trying to execute). + +If you'd rather the engine not execute the query first, you can always use the appropriate [compilation methods](#compilation_methods) detailed below. **Examples** @@ -1848,6 +1865,7 @@ qb.query(sql, function(err, res) { ------------- + ### SQL Compilation Methods These methods can be used to build a query string without having to execute it. This is a fantastic option if you want to use the querybuilder to simply build queries and display the resulting string or to send the compiled query string off to a driver/engine other than the one offered by `node-querybuilder`. diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 238a3b9..ed371a0 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -423,7 +423,7 @@ var QueryBuilder = function() { var limit_to = qb.limit_to[0] || false; var offset_val = qb.offset_val[0] || false; - var sql = 'UPDATE ' + table + " SET " + valstr.join(', '); + var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); return build_limit_clause(sql, limit_to, offset_val); @@ -432,6 +432,7 @@ var QueryBuilder = function() { var compile_insert = function(qb, ignore, suffix) { var keys = []; var values = []; + suffix = suffix || ''; for (var i in qb.set_array) { var key = Object.keys(qb.set_array[i])[0]; @@ -443,7 +444,6 @@ var QueryBuilder = function() { var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - if (qb.from_array.length === 1) { var table = qb.from_array.toString(); } else { @@ -451,11 +451,11 @@ var QueryBuilder = function() { throw new Error("You haven't provided any tables to build INSERT querty with!"); return ''; } - throw new Error("You have provided too many tables to build UPDATE query with!"); + throw new Error("You have provided too many tables to build INSERT query with!"); return ''; } - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + return verb + 'INTO (' + qb.from_array[0] + ') (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// @@ -1345,7 +1345,7 @@ var QueryBuilder = function() { } var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; + return verb + 'INTO (' + this.from_array[0] + ') (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, get: function(table) { @@ -1573,7 +1573,7 @@ var QueryBuilder = function() { } // Build the actual SQL statement - var sql = 'UPDATE ' + table + ' SET '; + var sql = 'UPDATE (' + table + ') SET '; var cases = ''; for (var l in when_then) { diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js new file mode 100755 index 0000000..e632925 --- /dev/null +++ b/test/mysql/tests-compilation_methods.js @@ -0,0 +1,69 @@ +var should = require('chai').should(); +var expect = require('chai').expect; +var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + +describe('get_compiled_select()', function() { + it('should exist', function() { + should.exist(qb.get_compiled_select); + }); + it('should be a function', function() { + qb.get_compiled_select.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.get_compiled_select('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', function() { + qb.reset_query(); + qb.get_compiled_select(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + }); + it('should return a SQL string', function() { + qb.reset_query(); + var sql = qb.get_compiled_select('galaxies'); + sql.should.eql('SELECT * FROM (`galaxies`)'); + }); +}); + +describe('get_compiled_insert()', function() { + it('should exist', function() { + should.exist(qb.get_compiled_insert); + }); + it('should be a function', function() { + qb.get_compiled_insert.should.be.a('function'); + }); + it('should return a SQL string', function() { + qb.reset_query(); + var sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); + sql.should.eql("INSERT INTO (`galaxies`) (`foo`) VALUES ('bar')"); + }); +}); + +describe('get_compiled_update()', function() { + it('should exist', function() { + should.exist(qb.get_compiled_update); + }); + it('should be a function', function() { + qb.get_compiled_update.should.be.a('function'); + }); + it('should return a SQL string', function() { + qb.reset_query(); + var sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); + sql.should.eql("UPDATE (`galaxies`) SET `foo` = 'bar' WHERE `id` = 45"); + }); +}); + +describe('get_compiled_delete()', function() { + it('should exist', function() { + should.exist(qb.get_compiled_delete); + }); + it('should be a function', function() { + qb.get_compiled_delete.should.be.a('function'); + }); + it('should return a SQL string', function() { + qb.reset_query(); + var sql = qb.where('id',45).get_compiled_delete('galaxies'); + sql.should.eql("DELETE FROM (`galaxies`) WHERE `id` = 45"); + }); +}); \ No newline at end of file diff --git a/test/mysql/test-distinct.js b/test/mysql/tests-distinct.js similarity index 100% rename from test/mysql/test-distinct.js rename to test/mysql/tests-distinct.js diff --git a/test/mysql/test-empty_table.js b/test/mysql/tests-empty_table.js similarity index 100% rename from test/mysql/test-empty_table.js rename to test/mysql/tests-empty_table.js diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index e0dbb7c..dffbd73 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -68,30 +68,30 @@ describe('insert()', function() { it('should allow for an empty data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); }); it('should utilize pre-existing values set in in set_array', function() { qb.reset_query(); qb.set(test_data); var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies', test_data); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); @@ -115,9 +115,34 @@ describe('insert()', function() { expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); }); - it('should support insert ignore statements', function() { +}); + +describe('insert_ignore()', function() { + it('should exist', function() { + should.exist(qb.insert_ignore); + }); + it('should be a function', function() { + qb.insert_ignore.should.be.a('function'); + }); + it('should create an INSERT IGNORE statement', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data); + sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data); + var sql_b = qb.insert('galaxies', test_data, true); + sql.should.eql(sql_b); + }); + it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data_set); + sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); \ No newline at end of file diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index eba7c60..239b4e7 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -42,7 +42,7 @@ describe('insert_batch()', function() { it('should build a proper batch INSERT string', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies', test_data); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); it('should only accept an array as the second parameter', function() { qb.reset_query(); @@ -72,13 +72,13 @@ describe('insert_batch()', function() { it('should allow for an empty data parameter', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies',[]); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.insert_batch(null,[]); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); }); it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { qb.reset_query(); @@ -98,6 +98,6 @@ describe('insert_batch()', function() { it('should support insert ignore statements', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); \ No newline at end of file diff --git a/test/mysql/test-limit.js b/test/mysql/tests-limit.js similarity index 100% rename from test/mysql/test-limit.js rename to test/mysql/tests-limit.js diff --git a/test/mysql/test-offset.js b/test/mysql/tests-offset.js similarity index 100% rename from test/mysql/test-offset.js rename to test/mysql/tests-offset.js diff --git a/test/mysql/test-truncate.js b/test/mysql/tests-truncate.js similarity index 100% rename from test/mysql/test-truncate.js rename to test/mysql/tests-truncate.js diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 4c97e42..62b23d8 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -75,24 +75,24 @@ describe('update()', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.update(null, test_data, test_where); - sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); }); it('should utilize pre-existing value set in in set_array', function() { qb.reset_query(); qb.set(test_data); var sql = qb.update('galaxies'); - sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); var sql = qb.update(); - sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); var sql = qb.update('galaxies', test_data); - sql.should.eql("UPDATE `galaxies` SET `name` = 'Milky Way', `type` = 'spiral'"); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); diff --git a/test/mysql/test-update_batch.js b/test/mysql/tests-update_batch.js similarity index 71% rename from test/mysql/test-update_batch.js rename to test/mysql/tests-update_batch.js index 75f1843..bfc2610 100755 --- a/test/mysql/test-update_batch.js +++ b/test/mysql/tests-update_batch.js @@ -15,6 +15,6 @@ describe('update_batch()', function() { it('should build a proper batch UPDATE string', function() { qb.reset_query(); var sql = qb.update_batch('galaxies', test_data, 'id'); - sql.should.eql(["UPDATE `galaxies` SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); + sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); }); }); \ No newline at end of file From 6bd2043daaa4afa8c676ff90c7f2ae1cb31e3562 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Feb 2015 16:51:51 -0500 Subject: [PATCH 069/200] Added database creation to travis_ci YML. --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fdb4a91..06cff5f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,11 @@ +before_script: + - mysql -e 'create database mock_db;' language: node_js node_js: - - "0.10" \ No newline at end of file + - "0.10" +services: mysql +test: + adapter: mysql2 + database: mock_db + username: travis + encoding: utf8 \ No newline at end of file From 7864459e793ea01ca0cab3004acff138d2f5db7f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Feb 2015 16:52:46 -0500 Subject: [PATCH 070/200] Fixed indentation in travis ci config. --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 06cff5f..4df6110 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ before_script: - - mysql -e 'create database mock_db;' + - mysql -e 'create database mock_db;' language: node_js node_js: - "0.10" services: mysql test: - adapter: mysql2 - database: mock_db - username: travis - encoding: utf8 \ No newline at end of file + adapter: mysql2 + database: mock_db + username: travis + encoding: utf8 \ No newline at end of file From c5477e84349572fda03b66233210f689cf8a19be Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 9 Feb 2015 17:48:05 -0500 Subject: [PATCH 071/200] Added testing for the MySQL connection adapter. --- README.md | 5 +- build/config.gypi | 40 +++++++ drivers/drivers.json | 43 ++++--- drivers/mysql/adapters.js | 15 ++- drivers/mysql/query_builder.js | 12 +- index.js | 10 +- package.json | 13 ++- test/mysql/tests-adapter.js | 199 +++++++++++++++++++++++++++++++++ test/mysql/tests-where.js | 2 +- 9 files changed, 297 insertions(+), 42 deletions(-) create mode 100644 build/config.gypi create mode 100755 test/mysql/tests-adapter.js diff --git a/README.md b/README.md index 3df0516..be80d85 100755 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Quick Reference | Driver | Default | Ready | single | pool | cluster | Additional Connection Options | | :-------- | :------ | :----- | :-------- | :---- | :---- | :-------------------------------------------------------- | -| mysql | ✓ | Yes | Yes | Yes | Yes | https://github.com/felixge/node-mysql#connection-options | +| mysql | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) | | mssql | | No | Yes | ??? | ??? | | | sqlite | | No | Yes | ??? | ??? | | | oracle | | No | Yes | ??? | ??? | | @@ -125,7 +125,10 @@ The options listed below are available for all database drivers. Additional prop | user | NULL | No | The database user | | password | NULL | Yes | The database `user`'s password | | database | NULL | Yes | The database to connect to | +| port | NULL | Yes | The database port to use when connecting | | pool_size | 10 | Yes | Max connections for `pool` connection type | +| debug | false | Yes | If true, debug info will be place in app log | +| version | default | Yes | Version of database driver to use | The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. diff --git a/build/config.gypi b/build/config.gypi new file mode 100644 index 0000000..5e195e9 --- /dev/null +++ b/build/config.gypi @@ -0,0 +1,40 @@ +# Do not edit. File was generated by node-gyp's "configure" step +{ + "target_defaults": { + "cflags": [], + "default_configuration": "Release", + "defines": [], + "include_dirs": [], + "libraries": [] + }, + "variables": { + "clang": 0, + "gcc_version": 48, + "host_arch": "x64", + "node_install_npm": "false", + "node_prefix": "/usr", + "node_shared_cares": "true", + "node_shared_http_parser": "true", + "node_shared_libuv": "true", + "node_shared_openssl": "true", + "node_shared_v8": "true", + "node_shared_zlib": "true", + "node_tag": "", + "node_unsafe_optimizations": 0, + "node_use_dtrace": "false", + "node_use_etw": "false", + "node_use_openssl": "true", + "node_use_perfctr": "false", + "node_use_systemtap": "false", + "openssl_no_asm": 0, + "python": "/usr/bin/python", + "target_arch": "x64", + "v8_enable_gdbjit": 0, + "v8_no_strict_aliasing": 1, + "v8_use_snapshot": "true", + "want_separate_host_toolset": 0, + "nodedir": "/usr/share/node", + "copy_dev_lib": "true", + "standalone_static_library": 1 + } +} diff --git a/drivers/drivers.json b/drivers/drivers.json index 9093d98..8abbe3a 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -2,68 +2,63 @@ "mysql": { "connection_types": { "single": true, "pool": true, "cluster": true }, "versions": { - "default": { + "2.5.4": { "path": "./drivers/mysql/", "active": true }, - "6": { - "path": "./drivers/mysql/6/", - "active": false + "default": { + "version": "2.5.4" } } }, "mssql": { "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { - "default": { + "1.3.0": { "path": "./drivers/mssql/", "active": false - } + }, + "default": "1.3.0" } }, "oracle": { "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { - "default": { - "path": "./drivers/mssql/", + "0.3.7": { + "path": "./drivers/oracle/", "active": false - } + }, + "default": "0.3.7" } }, "postgres": { "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { - "default": { + "4.2.0": { "path": "./drivers/postgres/", "active": false - } + }, + "default": "4.2.0" } }, "sqlite": { "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { - "default": { + "3.0.4": { "path": "./drivers/sqlite/", "active": false - } - } - }, - "sqlite3": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "default": { - "path": "./drivers/sqlite3/", - "active": false - } + }, + "default": "3.0.4" } }, "mongo": { "connection_types": { "single": true, "pool": false, "cluster": false }, "versions": { - "default": { + "1.4.30": { "path": "./drivers/mongo/", "active": false - } + }, + "default": "1.4.30" } } } \ No newline at end of file diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index ddee8f7..64656b3 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -18,9 +18,10 @@ var Adapters = function(nqb) { } // Verify that required fields are provided... + if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - if (!nqb.settings.hasOwnProperty('password')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); this.connection_settings = {}; var that = this; @@ -114,6 +115,10 @@ var Adapters = function(nqb) { return that.connection_settings; }, + connect: function(callback) { + return connection.connect(callback); + }, + connection: function() { return connection; }, @@ -122,8 +127,12 @@ var Adapters = function(nqb) { return connection.escape(str); }, - disconnect: function(str) { - return connection.end(); + escape_id: function(str) { + return connection.escapeId(str); + }, + + disconnect: function(callback) { + return connection.end(callback); }, release: function() { diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index ed371a0..1d728d7 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -282,14 +282,12 @@ var QueryBuilder = function() { var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); var do_escape = SqlString.escape; - if (typeof str === 'string' && !str.match(/^\d+$/)) { - str = do_escape(str); - } - else if (typeof str === 'boolean') { + if (typeof str === 'boolean') { str = (str === false ? 0 : 1); - } - else if (str === null) { - str = 'NULL'; + } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { + str *= 1; + } else { + str = do_escape(str); } return str; diff --git a/index.js b/index.js index 8758ab3..7c087dc 100755 --- a/index.js +++ b/index.js @@ -74,8 +74,12 @@ var QueryBuilder = function(settings,driver,type) { } // Retrieve info about driver if available, error if not - if (Object.keys(qb.drivers[qb.driver].versions).indexOf(qb.driver_version) !== -1) { - qb.driver_info = qb.drivers[qb.driver].versions[qb.driver_version]; + if (qb.drivers[qb.driver].versions.hasOwnProperty(qb.driver_version)) { + if (qb.drivers[qb.driver].versions[qb.driver_version].hasOwnProperty('version')) { + qb.driver_info = qb.drivers[qb.driver].versions[qb.drivers[qb.driver].versions[qb.driver_version].version]; + } else { + qb.driver_info = qb.drivers[qb.driver].versions[qb.driver_version]; + } } else { throw new Error(qb.driver_version + " is not a version of the " + qb.driver + " driver that this library specifically supports. Try being more generic."); } @@ -116,7 +120,7 @@ var QueryBuilder = function(settings,driver,type) { var adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); return adapter; } catch(e) { - throw new Error("Couldn't load the Connection library for " + qb.driver + ": " + e); + throw new Error("Couldn't load the Connection library for " + qb.driver + "(" + JSON.stringify(qb.settings) + "): " + e); } }; diff --git a/package.json b/package.json index 6016a23..abc7bb2 100755 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name" : "node-querybuilder", "version": "0.9.0", "author": "Kyle Farris ", - "description": "MySQL QueryBuilder pattern implementation on top of the mysql module.", + "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", + "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", "scripts": { "test": "make test" }, @@ -17,11 +18,17 @@ "Daehyub Kim " ], "dependencies": { + "mssql": "1.3.*", + "pg": "4.2.*", "mysql": "2.5.*", - "chai": "~1.4.2", - "mocha": "*", + "sqlite3": "3.0.*", + "mongodb": "1.4.*", "underscore": "*" }, + "devDependencies": { + "chai": "~1.4.2", + "mocha": "*" + }, "main" : "./", "engines": { "node": "*" diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js new file mode 100755 index 0000000..530a543 --- /dev/null +++ b/test/mysql/tests-adapter.js @@ -0,0 +1,199 @@ +var _ = require('underscore'); +var should = require('chai').should(); +var expect = require('chai').expect; +var nqb = require('../../index.js'); + +var check = function(done, f) { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +describe('QueryBuilder() - MySQL', function() { + var on_connect = function(err) { + if (err) { console.error("Not connected!"); return; } + console.log("connected!"); + }; + var driver = 'mysql'; + var settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306 + }; + var bad_user = _.extend({},settings); bad_user.user = 'foobar'; + var bad_host = _.extend({},settings); bad_host.host = 'nonlocalhost'; + var bad_password = _.extend({},settings); bad_password.password = 'password'; + var bad_database = _.extend({},settings); bad_database.database = 'bad_mock_db'; + var bad_port = _.extend({},settings); bad_port.port = 1; + var bad_version = _.extend({},settings); bad_version.version = 12; + + it('should exist', function() { + should.exist(nqb.QueryBuilder); + }); + it('should be a function', function() { + nqb.QueryBuilder.should.be.a('function'); + }); + it('should establish a single connection given valid connection credentials', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should be connected').to.not.be.instanceof(Error); + }); + }); + }); + it('should allow us to disconnect from MySQL', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + should.exist(qb.disconnect); + qb.disconnect.should.be.a('function'); + + qb.disconnect(function(err) { + var connection = qb.connection(); + + check(done, function() { + expect(err, 'should be diconnected').to.not.be.instanceof(Error); + expect(connection._protocol._ended).to.be.true; + }); + }); + }); + }); + + + it('should fail to establish a single connection given no parameters', function() { + expect(function() { nqb.QueryBuilder(); }).to.throw(Error); + }); + it('should fail to establish a single connection given no connection credentials', function() { + expect(function() { nqb.QueryBuilder({},driver); }).to.throw(Error); + }); + it('should fail to establish a single connection given connection credentials with bad user', function(done) { + try { + var qb = nqb.QueryBuilder(bad_user, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad host', function(done) { + try { + var qb = nqb.QueryBuilder(bad_host, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad database', function(done) { + try { + var qb = nqb.QueryBuilder(bad_database, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad password', function(done) { + try { + var qb = nqb.QueryBuilder(bad_password, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad port', function(done) { + try { + var qb = nqb.QueryBuilder(bad_port, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(function(err) { + check(done, function() { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish connection if an invalid driver is specified', function() { + var qb; + expect(function() { nqb.QueryBuilder(settings); }, 'no driver specified').to.throw(Error); + expect(function() { nqb.QueryBuilder(settings,'foobar'); }, 'invalid driver specified').to.throw(Error); + }); + it('should fail to establish connection if an invalid driver version is specified', function() { + var qb; + expect(function() { nqb.QueryBuilder( _.extend({}, settings), driver); }, 'valid driver version').to.not.throw(Error); + expect(function() { nqb.QueryBuilder(bad_version, driver); }, 'invalid driver version').to.throw(Error); + }); + + it('should allow us to retrieve our connection settings for reference', function(done) { + var conn_settings = _.extend({}, settings, {password: undefined}); + delete conn_settings.version; + + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + check(done, function() { + should.exist(qb.connection_settings); + qb.connection_settings.should.be.a('function'); + var settings = qb.connection_settings(); + expect(settings).to.be.instanceof(Object); + expect(settings).to.be.eql(conn_settings); + qb.disconnect(); + }); + }); + }); + it('should allow us to escape certain values', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + check(done, function() { + should.exist(qb.escape); + qb.escape.should.be.a('function'); + expect(qb.escape(null)).to.be.eql('NULL'); + expect(qb.escape('3')).to.be.eql(3); + expect(qb.escape(false)).to.be.eql(0); + expect(qb.escape(true)).to.be.eql(1); + expect(qb.escape(null)).to.be.eql('NULL'); + qb.disconnect(); + }); + }); + }); + it('should allow us to escape identifiers the MySQL way', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + check(done, function() { + should.exist(qb.escape_id); + qb.escape_id.should.be.a('function'); + expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); + expect(qb.escape_id('`foo`'), 'pre-escaped').to.be.eql('```foo```'); + expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('`foo`.`bar`'); + qb.disconnect(); + }); + }); + }); +}); diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js index bf4ce84..7275ac1 100755 --- a/test/mysql/tests-where.js +++ b/test/mysql/tests-where.js @@ -130,7 +130,7 @@ describe('where()', function() { qb.where({star_system:'Solar',planet:['Earth','Mars']},false); qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); - it("should split out and escape custom WHERE strings when that is the only thing provided (except when string containing parenthesis)", function() { + it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", function() { qb.reset_query(); qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); From 720fffb03a037302d3422c07676302b8c0bf06d2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 9 Feb 2015 17:54:38 -0500 Subject: [PATCH 072/200] Updated some connection settings documentation. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index be80d85..dcbe199 100755 --- a/README.md +++ b/README.md @@ -104,14 +104,14 @@ Connecting to Your Database Quick Reference --------------- -| Driver | Default | Ready | single | pool | cluster | Additional Connection Options | -| :-------- | :------ | :----- | :-------- | :---- | :---- | :-------------------------------------------------------- | -| mysql | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) | -| mssql | | No | Yes | ??? | ??? | | -| sqlite | | No | Yes | ??? | ??? | | -| oracle | | No | Yes | ??? | ??? | | -| postgres | | No | Yes | ??? | ??? | | -| mongodb | | No | Yes | ??? | ??? | | +| Driver | Default | Ready | single | pool | cluster | Additional Connection Options | +| :---------------------------------------- | :------ | :----- | :-------- | :---- | :---- | :-------------------------------------------------------- | +| [mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) | +| [mssql](//www.npmjs.com/package/mssql) | | No | Yes | ??? | ??? | | +| [sqlite3](//www.npmjs.com/package/sqlite3)| | No | Yes | ??? | ??? | | +| [oracle](//www.npmjs.com/package/oracle) | | No | Yes | ??? | ??? | | +| [postgres](//www.npmjs.com/package/pg) | | Pending | Yes | Yes | ??? | | +| [mongodb](//www.npmjs.com/package/mongodb)| | No | Yes | ??? | ??? | | Standard Connection Settings From bf21429188d4bc2b73abc39ab6fcc5240dfc36c1 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 10 Feb 2015 14:00:48 -0500 Subject: [PATCH 073/200] Added some mock data for travis. Hopefull it works. --- .gitignore | 1 + .travis.yml | 3 +- test/create_mock_dbs.sh | 2 ++ test/mysql/create_mysql_mock.sh | 3 ++ test/mysql/mock_data.sql | 51 +++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100755 test/create_mock_dbs.sh create mode 100755 test/mysql/create_mysql_mock.sh create mode 100644 test/mysql/mock_data.sql diff --git a/.gitignore b/.gitignore index 0571778..70b4ddd 100755 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules npm-debug* index2.js +build diff --git a/.travis.yml b/.travis.yml index 4df6110..fd8f5b8 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ -before_script: - - mysql -e 'create database mock_db;' +before_script: ./test/create_mock_dbs.sh language: node_js node_js: - "0.10" diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh new file mode 100755 index 0000000..810723b --- /dev/null +++ b/test/create_mock_dbs.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +./mysql/create_mysql_mock.sh \ No newline at end of file diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh new file mode 100755 index 0000000..74e5237 --- /dev/null +++ b/test/mysql/create_mysql_mock.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +mysql -e 'create database mock_db;' +mysql -utravis mock_db < mock_data.sql \ No newline at end of file diff --git a/test/mysql/mock_data.sql b/test/mysql/mock_data.sql new file mode 100644 index 0000000..bacf6ba --- /dev/null +++ b/test/mysql/mock_data.sql @@ -0,0 +1,51 @@ +-- MySQL dump 10.14 Distrib 5.5.40-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: mock_db +-- ------------------------------------------------------ +-- Server version 5.5.40-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `cities` +-- + +DROP TABLE IF EXISTS `cities`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cities` ( + `city` varchar(50) NOT NULL, + `state_code` char(2) NOT NULL, + KEY `idx_state_code` (`state_code`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cities` +-- + +LOCK TABLES `cities` WRITE; +/*!40000 ALTER TABLE `cities` DISABLE KEYS */; +INSERT INTO `cities` VALUES ('Aaronsburg','PA'),('Abbeville','AL'),('Abbeville','GA'),('Abbeville','LA'),('Abbeville','MS'),('Abbeville','SC'),('Abbot','ME'),('Abbotsford','WI'),('Abbott','TX'),('Abbottstown','PA'),('Abbyville','KS'),('Abell','MD'),('Abercrombie','ND'),('Aberdeen','ID'),('Aberdeen','KY'),('Aberdeen','MD'),('Aberdeen','MS'),('Aberdeen','NC'),('Aberdeen','OH'),('Aberdeen','SD'),('Aberdeen','WA'),('Aberdeen Proving Ground','MD'),('Abernant','AL'),('Abernathy','TX'),('Abie','NE'),('Abilene','KS'),('Abilene','TX'),('Abingdon','IL'),('Abingdon','MD'),('Abingdon','VA'),('Abington','CT'),('Abington','MA'),('Abington','PA'),('Abiquiu','NM'),('Abita Springs','LA'),('Abrams','WI'),('Absaraka','ND'),('Absarokee','MT'),('Absecon','NJ'),('Acampo','CA'),('Accident','MD'),('Accokeek','MD'),('Accomac','VA'),('Accord','MA'),('Accord','NY'),('Accoville','WV'),('Ace','TX'),('Achille','OK'),('Achilles','VA'),('Ackerly','TX'),('Ackerman','MS'),('Ackermanville','PA'),('Ackley','IA'),('Ackworth','IA'),('Acme','LA'),('Acme','MI'),('Acme','PA'),('Acme','WA'),('Acosta','PA'),('Acra','NY'),('Acton','CA'),('Acton','MA'),('Acton','ME'),('Acton','MT'),('Acushnet','MA'),('Acworth','GA'),('Acworth','NH'),('Ada','MI'),('Ada','MN'),('Ada','OH'),('Ada','OK'),('Adah','PA'),('Adair','IA'),('Adair','IL'),('Adair','OK'),('Adairsville','GA'),('Adairville','KY'),('Adak','AK'),('Adamant','VT'),('Adams','KY'),('Adams','MA'),('Adams','MN'),('Adams','ND'),('Adams','NE'),('Adams','NY'),('Adams','OK'),('Adams','OR'),('Adams','TN'),('Adams','WI'),('Adams Basin','NY'),('Adams Center','NY'),('Adams Run','SC'),('Adamsburg','PA'),('Adamstown','MD'),('Adamstown','PA'),('Adamsville','AL'),('Adamsville','OH'),('Adamsville','PA'),('Adamsville','RI'),('Adamsville','TN'),('Addieville','IL'),('Addington','OK'),('Addis','LA'),('Addison','AL'),('Addison','IL'),('Addison','ME'),('Addison','MI'),('Addison','NY'),('Addison','PA'),('Addison','TX'),('Addy','WA'),('Addyston','OH'),('Adel','GA'),('Adel','IA'),('Adel','OR'),('Adelanto','CA'),('Adell','WI'),('Adelphi','OH'),('Adelphia','NJ'),('Adena','OH'),('Adger','AL'),('Adin','CA'),('Adirondack','NY'),('Adjuntas','PR'),('Adkins','TX'),('Admire','KS'),('Adna','WA'),('Adolph','MN'),('Adolphus','KY'),('Adona','AR'),('Adrian','GA'),('Adrian','MI'),('Adrian','MN'),('Adrian','MO'),('Adrian','OR'),('Adrian','PA'),('Adrian','TX'),('Adrian','WV'),('Advance','IN'),('Advance','MO'),('Advance','NC'),('Advent','WV'),('Afton','IA'),('Afton','MI'),('Afton','MN'),('Afton','NY'),('Afton','OK'),('Afton','TN'),('Afton','TX'),('Afton','VA'),('Afton','WI'),('Afton','WY'),('Agar','SD'),('Agate','CO'),('Agate','ND'),('Agawam','MA'),('Agency','IA'),('Agency','MO'),('Agenda','KS'),('Ages Brookside','KY'),('Agness','OR'),('Agoura Hills','CA'),('Agra','KS'),('Agra','OK'),('Agua Dulce','TX'),('Aguada','PR'),('Aguadilla','PR'),('Aguanga','CA'),('Aguas Buenas','PR'),('Aguila','AZ'),('Aguilar','CO'),('Aguirre','PR'),('Ah Gwah Ching','MN'),('Ahmeek','MI'),('Ahoskie','NC'),('Ahsahka','ID'),('Ahwahnee','CA'),('Aibonito','PR'),('Aiea','HI'),('Aiken','SC'),('Aiken','TX'),('Ailey','GA'),('Aimwell','LA'),('Ainsworth','IA'),('Ainsworth','NE'),('Airville','PA'),('Airway Heights','WA'),('Aitkin','MN'),('Ajo','AZ'),('Akaska','SD'),('Akeley','MN'),('Akers','LA'),('Akiachak','AK'),('Akiak','AK'),('Akin','IL'),('Akron','AL'),('Akron','CO'),('Akron','IA'),('Akron','IN'),('Akron','MI'),('Akron','NY'),('Akron','OH'),('Akron','PA'),('Akutan','AK'),('Alabaster','AL'),('Alachua','FL'),('Aladdin','WY'),('Alakanuk','AK'),('Alamance','NC'),('Alameda','CA'),('Alamo','CA'),('Alamo','GA'),('Alamo','IN'),('Alamo','ND'),('Alamo','NV'),('Alamo','TN'),('Alamo','TX'),('Alamogordo','NM'),('Alamosa','CO'),('Alanreed','TX'),('Alanson','MI'),('Alapaha','GA'),('Alba','MI'),('Alba','MO'),('Alba','PA'),('Alba','TX'),('Albany','CA'),('Albany','GA'),('Albany','IL'),('Albany','IN'),('Albany','KY'),('Albany','LA'),('Albany','MN'),('Albany','MO'),('Albany','NY'),('Albany','OH'),('Albany','OK'),('Albany','OR'),('Albany','TX'),('Albany','VT'),('Albany','WI'),('Albemarle','NC'),('Albers','IL'),('Albert','KS'),('Albert','OK'),('Albert City','IA'),('Albert Lea','MN'),('Alberta','AL'),('Alberta','MN'),('Alberta','VA'),('Alberton','MT'),('Albertson','NC'),('Albertson','NY'),('Albertville','AL'),('Albertville','MN'),('Albia','IA'),('Albin','WY'),('Albion','CA'),('Albion','IA'),('Albion','ID'),('Albion','IL'),('Albion','IN'),('Albion','ME'),('Albion','MI'),('Albion','NE'),('Albion','NY'),('Albion','OK'),('Albion','PA'),('Albion','RI'),('Albion','WA'),('Alborn','MN'),('Albright','WV'),('Albrightsville','PA'),('Albuquerque','NM'),('Alburgh','VT'),('Alburnett','IA'),('Alburtis','PA'),('Alcalde','NM'),('Alcester','SD'),('Alcoa','TN'),('Alcolu','SC'),('Alcova','WY'),('Alcove','NY'),('Alda','NE'),('Alden','IA'),('Alden','IL'),('Alden','KS'),('Alden','MI'),('Alden','MN'),('Alden','NY'),('Alder','MT'),('Alder Creek','NY'),('Alderpoint','CA'),('Alderson','OK'),('Alderson','WV'),('Aldie','VA'),('Aldrich','MN'),('Aldrich','MO'),('Aledo','IL'),('Aledo','TX'),('Aleknagik','AK'),('Aleppo','PA'),('Alex','OK'),('Alexander','AR'),('Alexander','IA'),('Alexander','IL'),('Alexander','KS'),('Alexander','NC'),('Alexander','ND'),('Alexander','NY'),('Alexander City','AL'),('Alexandria','AL'),('Alexandria','IN'),('Alexandria','KY'),('Alexandria','LA'),('Alexandria','MN'),('Alexandria','MO'),('Alexandria','NE'),('Alexandria','OH'),('Alexandria','PA'),('Alexandria','SD'),('Alexandria','TN'),('Alexandria','VA'),('Alexandria Bay','NY'),('Alexis','IL'),('Alexis','NC'),('Alford','FL'),('Alfred','ME'),('Alfred','NY'),('Alfred Station','NY'),('Alger','MI'),('Alger','OH'),('Algodones','NM'),('Algoma','MS'),('Algoma','WI'),('Algona','IA'),('Algonac','MI'),('Algonquin','IL'),('Alhambra','CA'),('Alhambra','IL'),('Alice','TX'),('Aliceville','AL'),('Alicia','AR'),('Alief','TX'),('Aline','OK'),('Aliquippa','PA'),('Aliso Viejo','CA'),('Alix','AR'),('Alkol','WV'),('Allakaket','AK'),('Allamuchy','NJ'),('Allardt','TN'),('Alledonia','OH'),('Alleene','AR'),('Allegan','MI'),('Allegany','NY'),('Allegany','OR'),('Alleghany','CA'),('Alleman','IA'),('Allen','KS'),('Allen','KY'),('Allen','MD'),('Allen','MI'),('Allen','NE'),('Allen','OK'),('Allen','SD'),('Allen','TX'),('Allen Junction','WV'),('Allen Park','MI'),('Allendale','IL'),('Allendale','MI'),('Allendale','MO'),('Allendale','NJ'),('Allendale','SC'),('Allenhurst','GA'),('Allenhurst','NJ'),('Allenport','PA'),('Allenspark','CO'),('Allensville','KY'),('Allensville','PA'),('Allenton','MI'),('Allenton','MO'),('Allenton','WI'),('Allentown','GA'),('Allentown','NJ'),('Allentown','NY'),('Allentown','PA'),('Allenwood','NJ'),('Allenwood','PA'),('Allerton','IA'),('Allerton','IL'),('Alleyton','TX'),('Allgood','AL'),('Alliance','NC'),('Alliance','NE'),('Alliance','OH'),('Alligator','MS'),('Allison','IA'),('Allison','PA'),('Allison','TX'),('Allison Park','PA'),('Allons','TN'),('Allouez','MI'),('Alloway','NJ'),('Alloy','WV'),('Allport','PA'),('Allred','TN'),('Allston','MA'),('Allyn','WA'),('Alma','AL'),('Alma','AR'),('Alma','CO'),('Alma','GA'),('Alma','IL'),('Alma','KS'),('Alma','MI'),('Alma','MO'),('Alma','NE'),('Alma','NY'),('Alma','WI'),('Alma','WV'),('Alma Center','WI'),('Almelund','MN'),('Almena','KS'),('Almena','WI'),('Almira','WA'),('Almo','ID'),('Almo','KY'),('Almond','NC'),('Almond','NY'),('Almond','WI'),('Almont','CO'),('Almont','MI'),('Almont','ND'),('Almyra','AR'),('Alna','ME'),('Alpaugh','CA'),('Alpena','AR'),('Alpena','MI'),('Alpena','SD'),('Alpha','IL'),('Alpha','KY'),('Alpha','MI'),('Alpha','MN'),('Alpha','OH'),('Alpharetta','GA'),('Alpine','AL'),('Alpine','AR'),('Alpine','AZ'),('Alpine','CA'),('Alpine','NJ'),('Alpine','NY'),('Alpine','TN'),('Alpine','TX'),('Alpine','UT'),('Alpine','WY'),('Alplaus','NY'),('Alsea','OR'),('Alsen','ND'),('Alsey','IL'),('Alsip','IL'),('Alstead','NH'),('Alston','GA'),('Alta','CA'),('Alta','IA'),('Alta','WY'),('Alta Vista','IA'),('Alta Vista','KS'),('Altadena','CA'),('Altair','TX'),('Altamahaw','NC'),('Altamont','IL'),('Altamont','KS'),('Altamont','MO'),('Altamont','NY'),('Altamont','TN'),('Altamont','UT'),('Altamonte Springs','FL'),('Altaville','CA'),('Altavista','VA'),('Altenburg','MO'),('Altha','FL'),('Altheimer','AR'),('Altmar','NY'),('Alto','GA'),('Alto','MI'),('Alto','NM'),('Alto','TX'),('Alto Pass','IL'),('Alton','AL'),('Alton','IA'),('Alton','IL'),('Alton','KS'),('Alton','MO'),('Alton','NH'),('Alton','NY'),('Alton','UT'),('Alton','VA'),('Alton Bay','NH'),('Altona','IL'),('Altona','NY'),('Altonah','UT'),('Altoona','AL'),('Altoona','FL'),('Altoona','IA'),('Altoona','KS'),('Altoona','PA'),('Altoona','WI'),('Altura','MN'),('Alturas','CA'),('Alturas','FL'),('Altus','AR'),('Altus','OK'),('Altus Afb','OK'),('Alum Bank','PA'),('Alum Bridge','WV'),('Alum Creek','WV'),('Alva','FL'),('Alva','OK'),('Alva','WY'),('Alvada','OH'),('Alvadore','OR'),('Alvarado','MN'),('Alvarado','TX'),('Alvaton','KY'),('Alverda','PA'),('Alverton','PA'),('Alvin','IL'),('Alvin','TX'),('Alviso','CA'),('Alvo','NE'),('Alvord','IA'),('Alvord','TX'),('Alvordton','OH'),('Alzada','MT'),('Ama','LA'),('Amado','AZ'),('Amador City','CA'),('Amagansett','NY'),('Amagon','AR'),('Amalia','NM'),('Amana','IA'),('Amanda','OH'),('Amanda Park','WA'),('Amargosa Valley','NV'),('Amarillo','TX'),('Amasa','MI'),('Amawalk','NY'),('Amazonia','MO'),('Amber','OK'),('Amberg','WI'),('Amberson','PA'),('Ambia','IN'),('Ambler','AK'),('Ambler','PA'),('Amboy','CA'),('Amboy','IL'),('Amboy','IN'),('Amboy','MN'),('Amboy','WA'),('Ambridge','PA'),('Ambrose','GA'),('Ambrose','ND'),('Amelia','LA'),('Amelia','NE'),('Amelia','OH'),('Amelia Court House','VA'),('Amenia','ND'),('Amenia','NY'),('American Canyon','CA'),('American Falls','ID'),('American Fork','UT'),('Americus','GA'),('Americus','KS'),('Amery','WI'),('Ames','IA'),('Ames','NE'),('Ames','OK'),('Amesbury','MA'),('Amesville','OH'),('Amherst','CO'),('Amherst','MA'),('Amherst','NE'),('Amherst','NH'),('Amherst','OH'),('Amherst','SD'),('Amherst','TX'),('Amherst','VA'),('Amherst','WI'),('Amherst Junction','WI'),('Amherstdale','WV'),('Amidon','ND'),('Amigo','WV'),('Amissville','VA'),('Amistad','NM'),('Amite','LA'),('Amity','AR'),('Amity','MO'),('Amity','OR'),('Amity','PA'),('Amityville','NY'),('Amlin','OH'),('Amma','WV'),('Ammon','VA'),('Amo','IN'),('Amonate','VA'),('Amoret','MO'),('Amorita','OK'),('Amory','MS'),('Amsterdam','MO'),('Amsterdam','NY'),('Amsterdam','OH'),('Amston','CT'),('Anabel','MO'),('Anacoco','LA'),('Anaconda','MT'),('Anacortes','WA'),('Anadarko','OK'),('Anaheim','CA'),('Anahola','HI'),('Anahuac','TX'),('Anaktuvuk Pass','AK'),('Analomink','PA'),('Anamoose','ND'),('Anamosa','IA'),('Anasco','PR'),('Anatone','WA'),('Anawalt','WV'),('Anchor','IL'),('Anchor Point','AK'),('Anchorage','AK'),('Anchorville','MI'),('Ancona','IL'),('Ancram','NY'),('Ancramdale','NY'),('Andale','KS'),('Andalusia','AL'),('Andalusia','IL'),('Anderson','AK'),('Anderson','AL'),('Anderson','CA'),('Anderson','IN'),('Anderson','MO'),('Anderson','SC'),('Anderson','TX'),('Anderson Island','WA'),('Andersonville','GA'),('Andersonville','TN'),('Andes','NY'),('Andover','CT'),('Andover','IA'),('Andover','IL'),('Andover','KS'),('Andover','MA'),('Andover','ME'),('Andover','MN'),('Andover','NH'),('Andover','NJ'),('Andover','NY'),('Andover','OH'),('Andover','SD'),('Andover','VA'),('Andreas','PA'),('Andrew','IA'),('Andrews','IN'),('Andrews','NC'),('Andrews','SC'),('Andrews','TX'),('Andrews Air Force Base','MD'),('Aneta','ND'),('Aneth','UT'),('Angel Fire','NM'),('Angela','MT'),('Angeles','PR'),('Angelica','NY'),('Angels Camp','CA'),('Angelus Oaks','CA'),('Angie','LA'),('Angier','NC'),('Angle Inlet','MN'),('Angleton','TX'),('Angola','IN'),('Angola','LA'),('Angola','NY'),('Angoon','AK'),('Angora','MN'),('Angora','NE'),('Anguilla','MS'),('Angwin','CA'),('Aniak','AK'),('Animas','NM'),('Anita','IA'),('Anita','PA'),('Aniwa','WI'),('Ankeny','IA'),('Anmoore','WV'),('Ann Arbor','MI'),('Anna','IL'),('Anna','OH'),('Anna','TX'),('Anna Maria','FL'),('Annabella','UT'),('Annada','MO'),('Annandale','MN'),('Annandale','NJ'),('Annandale','VA'),('Annandale On Hudson','NY'),('Annapolis','CA'),('Annapolis','IL'),('Annapolis','MD'),('Annapolis','MO'),('Annapolis Junction','MD'),('Annawan','IL'),('Annemanie','AL'),('Anniston','AL'),('Anniston','MO'),('Annona','TX'),('Annville','KY'),('Annville','PA'),('Anoka','MN'),('Anselmo','NE'),('Ansley','NE'),('Anson','ME'),('Anson','TX'),('Ansonia','CT'),('Ansonia','OH'),('Ansonville','NC'),('Ansted','WV'),('Antelope','CA'),('Antelope','MT'),('Antelope','OR'),('Antes Fort','PA'),('Anthon','IA'),('Anthony','FL'),('Anthony','KS'),('Anthony','NM'),('Anthony','TX'),('Antigo','WI'),('Antimony','UT'),('Antioch','CA'),('Antioch','IL'),('Antioch','TN'),('Antler','ND'),('Antlers','OK'),('Antoine','AR'),('Anton','CO'),('Anton','TX'),('Anton Chico','NM'),('Antonito','CO'),('Antrim','NH'),('Antwerp','NY'),('Antwerp','OH'),('Anvik','AK'),('Anza','CA'),('Apache','OK'),('Apache Junction','AZ'),('Apalachicola','FL'),('Apalachin','NY'),('Apex','NC'),('Apison','TN'),('Aplington','IA'),('Apollo','PA'),('Apollo Beach','FL'),('Apopka','FL'),('Appalachia','VA'),('Apple Creek','OH'),('Apple Grove','WV'),('Apple River','IL'),('Apple Springs','TX'),('Apple Valley','CA'),('Applegate','CA'),('Applegate','MI'),('Appleton','MN'),('Appleton','NY'),('Appleton','WA'),('Appleton','WI'),('Appleton City','MO'),('Appling','GA'),('Appomattox','VA'),('Aptos','CA'),('Apulia Station','NY'),('Aquasco','MD'),('Aquashicola','PA'),('Aquebogue','NY'),('Aquilla','TX'),('Arab','AL'),('Arabi','GA'),('Arabi','LA'),('Aragon','GA'),('Aragon','NM'),('Aransas Pass','TX'),('Arapaho','OK'),('Arapahoe','CO'),('Arapahoe','NC'),('Arapahoe','NE'),('Arapahoe','WY'),('Ararat','NC'),('Ararat','VA'),('Arbela','MO'),('Arboles','CO'),('Arbon','ID'),('Arbovale','WV'),('Arbuckle','CA'),('Arbyrd','MO'),('Arcade','NY'),('Arcadia','CA'),('Arcadia','FL'),('Arcadia','IA'),('Arcadia','IN'),('Arcadia','KS'),('Arcadia','LA'),('Arcadia','MI'),('Arcadia','MO'),('Arcadia','NE'),('Arcadia','OH'),('Arcadia','OK'),('Arcadia','PA'),('Arcadia','SC'),('Arcadia','WI'),('Arcanum','OH'),('Arcata','CA'),('Arch Cape','OR'),('Archbald','PA'),('Archbold','OH'),('Archer','FL'),('Archer','IA'),('Archer','NE'),('Archer City','TX'),('Archibald','LA'),('Archie','MO'),('Arco','ID'),('Arco','MN'),('Arcola','IL'),('Arcola','IN'),('Arcola','MO'),('Arcola','MS'),('Arcola','PA'),('Arcola','VA'),('Arctic Village','AK'),('Ardara','PA'),('Arden','NC'),('Arden','NY'),('Ardenvoir','WA'),('Ardmore','AL'),('Ardmore','OK'),('Ardmore','PA'),('Ardmore','TN'),('Ardsley','NY'),('Ardsley On Hudson','NY'),('Arecibo','PR'),('Aredale','IA'),('Arena','WI'),('Arenas Valley','NM'),('Arendtsville','PA'),('Arenzville','IL'),('Argenta','IL'),('Argillite','KY'),('Argonia','KS'),('Argonne','WI'),('Argos','IN'),('Argusville','ND'),('Argyle','FL'),('Argyle','GA'),('Argyle','IA'),('Argyle','MI'),('Argyle','MN'),('Argyle','MO'),('Argyle','NY'),('Argyle','TX'),('Argyle','WI'),('Ariel','WA'),('Arimo','ID'),('Arion','IA'),('Aripeka','FL'),('Arispe','IA'),('Aristes','PA'),('Ariton','AL'),('Arivaca','AZ'),('Arizona City','AZ'),('Arjay','KY'),('Ark','VA'),('Arkabutla','MS'),('Arkadelphia','AR'),('Arkansas City','AR'),('Arkansas City','KS'),('Arkansaw','WI'),('Arkdale','WI'),('Arkoma','OK'),('Arkport','NY'),('Arkville','NY'),('Arlee','MT'),('Arley','AL'),('Arlington','AL'),('Arlington','AZ'),('Arlington','CO'),('Arlington','GA'),('Arlington','IA'),('Arlington','IL'),('Arlington','IN'),('Arlington','KS'),('Arlington','KY'),('Arlington','MA'),('Arlington','MN'),('Arlington','NE'),('Arlington','OH'),('Arlington','OR'),('Arlington','SD'),('Arlington','TN'),('Arlington','TX'),('Arlington','VA'),('Arlington','VT'),('Arlington','WA'),('Arlington','WI'),('Arlington Heights','IL'),('Arlington Heights','MA'),('Arma','KS'),('Armada','MI'),('Armagh','PA'),('Armbrust','PA'),('Armington','IL'),('Arminto','WY'),('Armona','CA'),('Armonk','NY'),('Armorel','AR'),('Armour','SD'),('Armstrong','IA'),('Armstrong','IL'),('Armstrong','MO'),('Armstrong','TX'),('Armstrong Creek','WI'),('Armuchee','GA'),('Arnaudville','LA'),('Arnegard','ND'),('Arnett','OK'),('Arnett','WV'),('Arnold','CA'),('Arnold','KS'),('Arnold','MD'),('Arnold','MI'),('Arnold','MO'),('Arnold','NE'),('Arnold A F B','TN'),('Arnolds Park','IA'),('Arnoldsburg','WV'),('Arnoldsville','GA'),('Arnot','PA'),('Arock','OR'),('Aroda','VA'),('Aroma Park','IL'),('Aromas','CA'),('Arona','PA'),('Arp','TX'),('Arpin','WI'),('Arrey','NM'),('Arriba','CO'),('Arrington','TN'),('Arrington','VA'),('Arrow Rock','MO'),('Arrowsmith','IL'),('Arroyo','PR'),('Arroyo Grande','CA'),('Arroyo Hondo','NM'),('Arroyo Seco','NM'),('Art','TX'),('Artemas','PA'),('Artemus','KY'),('Artesia','CA'),('Artesia','MS'),('Artesia','NM'),('Artesia Wells','TX'),('Artesian','SD'),('Arthur','IA'),('Arthur','IL'),('Arthur','ND'),('Arthur','NE'),('Arthur','TN'),('Arthur City','TX'),('Arthurdale','WV'),('Artie','WV'),('Artois','CA'),('Arvada','CO'),('Arvada','WY'),('Arverne','NY'),('Arvilla','ND'),('Arvin','CA'),('Arvonia','VA'),('Ary','KY'),('Asbury','MO'),('Asbury','NJ'),('Asbury','WV'),('Asbury Park','NJ'),('Ascutney','VT'),('Ash','NC'),('Ash Flat','AR'),('Ash Fork','AZ'),('Ash Grove','MO'),('Ashaway','RI'),('Ashburn','GA'),('Ashburn','MO'),('Ashburn','VA'),('Ashburnham','MA'),('Ashby','MA'),('Ashby','MN'),('Ashby','NE'),('Ashcamp','KY'),('Ashdown','AR'),('Asheboro','NC'),('Asher','KY'),('Asher','OK'),('Asherton','TX'),('Asheville','NC'),('Ashfield','MA'),('Ashfield','PA'),('Ashford','AL'),('Ashford','CT'),('Ashford','WA'),('Ashford','WV'),('Ashippun','WI'),('Ashkum','IL'),('Ashland','AL'),('Ashland','IL'),('Ashland','KS'),('Ashland','KY'),('Ashland','LA'),('Ashland','MA'),('Ashland','ME'),('Ashland','MO'),('Ashland','MS'),('Ashland','MT'),('Ashland','NE'),('Ashland','NH'),('Ashland','NY'),('Ashland','OH'),('Ashland','OR'),('Ashland','PA'),('Ashland','VA'),('Ashland','WI'),('Ashland City','TN'),('Ashley','IL'),('Ashley','IN'),('Ashley','MI'),('Ashley','ND'),('Ashley','OH'),('Ashley Falls','MA'),('Ashmore','IL'),('Ashtabula','OH'),('Ashton','IA'),('Ashton','ID'),('Ashton','IL'),('Ashton','MD'),('Ashton','NE'),('Ashton','SD'),('Ashton','WV'),('Ashuelot','NH'),('Ashville','AL'),('Ashville','NY'),('Ashville','OH'),('Ashville','PA'),('Ashwood','OR'),('Askov','MN'),('Asotin','WA'),('Aspen','CO'),('Aspermont','TX'),('Aspers','PA'),('Aspinwall','IA'),('Assaria','KS'),('Assawoman','VA'),('Assonet','MA'),('Assumption','IL'),('Astatula','FL'),('Aston','PA'),('Astor','FL'),('Astoria','IL'),('Astoria','NY'),('Astoria','OR'),('Astoria','SD'),('Atalissa','IA'),('Atascadero','CA'),('Atascosa','TX'),('Atchison','KS'),('Atco','NJ'),('Atglen','PA'),('Athelstane','WI'),('Athena','OR'),('Athens','AL'),('Athens','GA'),('Athens','IL'),('Athens','IN'),('Athens','LA'),('Athens','ME'),('Athens','MI'),('Athens','NY'),('Athens','OH'),('Athens','PA'),('Athens','TN'),('Athens','TX'),('Athens','WI'),('Athens','WV'),('Atherton','CA'),('Athol','ID'),('Athol','KS'),('Athol','KY'),('Athol','MA'),('Athol','NY'),('Athol Springs','NY'),('Atka','AK'),('Atkins','AR'),('Atkins','IA'),('Atkins','VA'),('Atkinson','IL'),('Atkinson','NC'),('Atkinson','NE'),('Atkinson','NH'),('Atlanta','GA'),('Atlanta','ID'),('Atlanta','IL'),('Atlanta','IN'),('Atlanta','KS'),('Atlanta','LA'),('Atlanta','MI'),('Atlanta','MO'),('Atlanta','NE'),('Atlanta','NY'),('Atlanta','TX'),('Atlantic','IA'),('Atlantic','NC'),('Atlantic','PA'),('Atlantic','VA'),('Atlantic Beach','FL'),('Atlantic Beach','NC'),('Atlantic Beach','NY'),('Atlantic City','NJ'),('Atlantic Highlands','NJ'),('Atlantic Mine','MI'),('Atlas','MI'),('Atlasburg','PA'),('Atmore','AL'),('Atoka','OK'),('Atoka','TN'),('Atomic City','ID'),('Atqasuk','AK'),('Attalla','AL'),('Attapulgus','GA'),('Attica','IN'),('Attica','KS'),('Attica','MI'),('Attica','NY'),('Attica','OH'),('Attleboro','MA'),('Attleboro Falls','MA'),('Atwater','CA'),('Atwater','MN'),('Atwater','OH'),('Atwood','CA'),('Atwood','CO'),('Atwood','IL'),('Atwood','IN'),('Atwood','KS'),('Atwood','OK'),('Atwood','TN'),('Au Gres','MI'),('Au Sable Forks','NY'),('Au Train','MI'),('Auberry','CA'),('Aubrey','AR'),('Aubrey','TX'),('Auburn','AL'),('Auburn','CA'),('Auburn','GA'),('Auburn','IA'),('Auburn','IL'),('Auburn','IN'),('Auburn','KS'),('Auburn','KY'),('Auburn','MA'),('Auburn','ME'),('Auburn','MI'),('Auburn','NE'),('Auburn','NH'),('Auburn','NY'),('Auburn','PA'),('Auburn','WA'),('Auburn','WV'),('Auburn','WY'),('Auburn Hills','MI'),('Auburn University','AL'),('Auburndale','FL'),('Auburndale','MA'),('Auburndale','WI'),('Auburntown','TN'),('Audubon','IA'),('Audubon','MN'),('Audubon','NJ'),('Audubon','PA'),('Augusta','AR'),('Augusta','GA'),('Augusta','IL'),('Augusta','KS'),('Augusta','KY'),('Augusta','ME'),('Augusta','MI'),('Augusta','MO'),('Augusta','MT'),('Augusta','NJ'),('Augusta','OH'),('Augusta','WI'),('Augusta','WV'),('Augusta Springs','VA'),('Auke Bay','AK'),('Aulander','NC'),('Ault','CO'),('Aultman','PA'),('Aumsville','OR'),('Aurelia','IA'),('Auriesville','NY'),('Aurora','CO'),('Aurora','IA'),('Aurora','IL'),('Aurora','IN'),('Aurora','KS'),('Aurora','ME'),('Aurora','MN'),('Aurora','MO'),('Aurora','NC'),('Aurora','NE'),('Aurora','NY'),('Aurora','OH'),('Aurora','OR'),('Aurora','SD'),('Aurora','UT'),('Aurora','WV'),('Austell','GA'),('Austerlitz','NY'),('Austin','AR'),('Austin','CO'),('Austin','IN'),('Austin','KY'),('Austin','MN'),('Austin','NV'),('Austin','PA'),('Austin','TX'),('Austinburg','OH'),('Austinville','IA'),('Austinville','VA'),('Austwell','TX'),('Autaugaville','AL'),('Autryville','NC'),('Auxier','KY'),('Auxvasse','MO'),('Ava','IL'),('Ava','MO'),('Ava','NY'),('Ava','OH'),('Avalon','CA'),('Avalon','NJ'),('Avalon','TX'),('Avalon','WI'),('Avant','OK'),('Avawam','KY'),('Avella','PA'),('Avenal','CA'),('Avenel','NJ'),('Avenue','MD'),('Avera','GA'),('Averill','VT'),('Averill Park','NY'),('Avery','CA'),('Avery','ID'),('Avery','TX'),('Avery Island','LA'),('Avila Beach','CA'),('Avilla','IN'),('Avilla','MO'),('Avinger','TX'),('Avis','PA'),('Aviston','IL'),('Avoca','AR'),('Avoca','IA'),('Avoca','IN'),('Avoca','MI'),('Avoca','MN'),('Avoca','NE'),('Avoca','NY'),('Avoca','TX'),('Avoca','WI'),('Avon','CO'),('Avon','CT'),('Avon','IL'),('Avon','IN'),('Avon','MA'),('Avon','MN'),('Avon','MS'),('Avon','MT'),('Avon','NC'),('Avon','NY'),('Avon','OH'),('Avon','SD'),('Avon By The Sea','NJ'),('Avon Lake','OH'),('Avon Park','FL'),('Avondale','AZ'),('Avondale','CO'),('Avondale','PA'),('Avondale','WV'),('Avondale Estates','GA'),('Avonmore','PA'),('Awendaw','SC'),('Axis','AL'),('Axson','GA'),('Axtell','KS'),('Axtell','NE'),('Axtell','TX'),('Axtell','UT'),('Axton','VA'),('Ayden','NC'),('Aydlett','NC'),('Ayer','MA'),('Aylett','VA'),('Aynor','SC'),('Ayr','ND'),('Ayr','NE'),('Ayrshire','IA'),('Azalea','OR'),('Azalia','MI'),('Azle','TX'),('Aztec','NM'),('Azusa','CA'),('Babb','MT'),('Babbitt','MN'),('Babcock','WI'),('Babson Park','FL'),('Babson Park','MA'),('Babylon','NY'),('Backus','MN'),('Bacliff','TX'),('Baconton','GA'),('Bacova','VA'),('Bad Axe','MI'),('Baden','PA'),('Badger','CA'),('Badger','IA'),('Badger','MN'),('Badger','SD'),('Badin','NC'),('Bagdad','AZ'),('Bagdad','FL'),('Bagdad','KY'),('Baggs','WY'),('Bagley','IA'),('Bagley','MN'),('Bagley','WI'),('Bagwell','TX'),('Bahama','NC'),('Bailey','CO'),('Bailey','MI'),('Bailey','MS'),('Bailey','NC'),('Bailey','TX'),('Bailey Island','ME'),('Baileys Harbor','WI'),('Baileyton','AL'),('Baileyville','IL'),('Baileyville','KS'),('Baileyville','ME'),('Bainbridge','GA'),('Bainbridge','IN'),('Bainbridge','NY'),('Bainbridge','OH'),('Bainbridge','PA'),('Bainbridge Island','WA'),('Bainville','MT'),('Baird','TX'),('Bairdford','PA'),('Bairoil','WY'),('Baisden','WV'),('Bajadero','PR'),('Baker','CA'),('Baker','FL'),('Baker','LA'),('Baker','MT'),('Baker','NV'),('Baker','WV'),('Baker City','OR'),('Bakers Mills','NY'),('Bakersfield','CA'),('Bakersfield','MO'),('Bakersfield','VT'),('Bakerstown','PA'),('Bakersville','NC'),('Bakersville','OH'),('Bakerton','WV'),('Bakewell','TN'),('Bala Cynwyd','PA'),('Balaton','MN'),('Bald Knob','AR'),('Baldwin','GA'),('Baldwin','IA'),('Baldwin','IL'),('Baldwin','LA'),('Baldwin','MD'),('Baldwin','MI'),('Baldwin','ND'),('Baldwin','NY'),('Baldwin','WI'),('Baldwin City','KS'),('Baldwin Park','CA'),('Baldwin Place','NY'),('Baldwinsville','NY'),('Baldwinville','MA'),('Baldwyn','MS'),('Balfour','ND'),('Balko','OK'),('Ball','LA'),('Ball Ground','GA'),('Ballantine','MT'),('Ballard','WV'),('Ballentine','SC'),('Ballico','CA'),('Ballinger','TX'),('Ballouville','CT'),('Ballston Lake','NY'),('Ballston Spa','NY'),('Ballwin','MO'),('Bally','PA'),('Balm','FL'),('Balmorhea','TX'),('Balsam','NC'),('Balsam Grove','NC'),('Balsam Lake','WI'),('Balta','ND'),('Baltic','CT'),('Baltic','OH'),('Baltic','SD'),('Baltimore','MD'),('Baltimore','OH'),('Bamberg','SC'),('Banco','VA'),('Bancroft','IA'),('Bancroft','ID'),('Bancroft','MI'),('Bancroft','NE'),('Bancroft','WI'),('Bancroft','WV'),('Bandana','KY'),('Bandera','TX'),('Bandon','OR'),('Bandy','VA'),('Bangall','NY'),('Bangor','CA'),('Bangor','ME'),('Bangor','MI'),('Bangor','PA'),('Bangor','WI'),('Bangs','TX'),('Banks','AL'),('Banks','AR'),('Banks','ID'),('Banks','OR'),('Bankston','AL'),('Banner','KY'),('Banner','MS'),('Banner','WY'),('Banner Elk','NC'),('Banning','CA'),('Bannister','MI'),('Bannock','OH'),('Banquete','TX'),('Bantam','CT'),('Bantry','ND'),('Bapchule','AZ'),('Baptistown','NJ'),('Bar Harbor','ME'),('Bar Mills','ME'),('Baraboo','WI'),('Baraga','MI'),('Barataria','LA'),('Barbeau','MI'),('Barberton','OH'),('Barberville','FL'),('Barboursville','VA'),('Barboursville','WV'),('Barbourville','KY'),('Barceloneta','PR'),('Barclay','MD'),('Barco','NC'),('Bard','CA'),('Bard','NM'),('Bardolph','IL'),('Bardstown','KY'),('Bardwell','KY'),('Bardwell','TX'),('Bargersville','IN'),('Barhamsville','VA'),('Baring','MO'),('Baring','WA'),('Barium Springs','NC'),('Bark River','MI'),('Barker','NY'),('Barker','TX'),('Barkhamsted','CT'),('Barksdale','TX'),('Barksdale Afb','LA'),('Barling','AR'),('Barlow','KY'),('Barlow','OH'),('Barnard','KS'),('Barnard','MO'),('Barnard','SD'),('Barnard','VT'),('Barnardsville','NC'),('Barnegat','NJ'),('Barnegat Light','NJ'),('Barnes','KS'),('Barnes City','IA'),('Barneston','NE'),('Barnesville','GA'),('Barnesville','MD'),('Barnesville','MN'),('Barnesville','NC'),('Barnesville','OH'),('Barnesville','PA'),('Barnet','VT'),('Barnett','MO'),('Barneveld','NY'),('Barneveld','WI'),('Barney','GA'),('Barney','ND'),('Barnhart','MO'),('Barnhart','TX'),('Barnhill','IL'),('Barnsdall','OK'),('Barnstable','MA'),('Barnstead','NH'),('Barnum','IA'),('Barnum','MN'),('Barnwell','SC'),('Baroda','MI'),('Barrackville','WV'),('Barranquitas','PR'),('Barre','MA'),('Barre','VT'),('Barren Springs','VA'),('Barrett','MN'),('Barrington','IL'),('Barrington','NH'),('Barrington','NJ'),('Barrington','RI'),('Barron','WI'),('Barronett','WI'),('Barrow','AK'),('Barry','IL'),('Barry','MN'),('Barry','TX'),('Barryton','MI'),('Barrytown','NY'),('Barryville','NY'),('Barstow','CA'),('Barstow','IL'),('Barstow','MD'),('Barstow','TX'),('Bart','PA'),('Bartelso','IL'),('Bartlesville','OK'),('Bartlett','IL'),('Bartlett','KS'),('Bartlett','NE'),('Bartlett','NH'),('Bartlett','OH'),('Bartlett','TX'),('Bartley','NE'),('Bartley','WV'),('Barto','PA'),('Barton','AR'),('Barton','MD'),('Barton','NY'),('Barton','OH'),('Barton','VT'),('Barton City','MI'),('Bartonsville','PA'),('Bartow','FL'),('Bartow','GA'),('Bartow','WV'),('Barwick','GA'),('Basalt','CO'),('Basalt','ID'),('Basco','IL'),('Bascom','FL'),('Bascom','OH'),('Basehor','KS'),('Basile','LA'),('Basin','MT'),('Basin','WY'),('Baskerville','VA'),('Baskett','KY'),('Baskin','LA'),('Basking Ridge','NJ'),('Basom','NY'),('Bass Harbor','ME'),('Bass Lake','CA'),('Bassett','AR'),('Bassett','NE'),('Bassett','VA'),('Bassett','WI'),('Bassfield','MS'),('Bastian','VA'),('Bastrop','LA'),('Bastrop','TX'),('Basye','VA'),('Bat Cave','NC'),('Batavia','IA'),('Batavia','IL'),('Batavia','NY'),('Batavia','OH'),('Batchelor','LA'),('Batchtown','IL'),('Bates','OR'),('Bates City','MO'),('Batesburg','SC'),('Batesland','SD'),('Batesville','AR'),('Batesville','IN'),('Batesville','MS'),('Batesville','TX'),('Batesville','VA'),('Bath','IL'),('Bath','IN'),('Bath','ME'),('Bath','MI'),('Bath','NC'),('Bath','NH'),('Bath','NY'),('Bath','OH'),('Bath','PA'),('Bath','SC'),('Bath','SD'),('Bath Springs','TN'),('Bathgate','ND'),('Baton Rouge','LA'),('Batson','TX'),('Battery Park','VA'),('Battiest','OK'),('Battle Creek','IA'),('Battle Creek','MI'),('Battle Creek','NE'),('Battle Ground','IN'),('Battle Ground','WA'),('Battle Lake','MN'),('Battle Mountain','NV'),('Battleboro','NC'),('Battlement Mesa','CO'),('Battletown','KY'),('Baudette','MN'),('Bausman','PA'),('Bauxite','AR'),('Baxley','GA'),('Baxter','IA'),('Baxter','KY'),('Baxter','MN'),('Baxter','TN'),('Baxter','WV'),('Baxter Springs','KS'),('Bay','AR'),('Bay Center','WA'),('Bay City','MI'),('Bay City','OR'),('Bay City','TX'),('Bay City','WI'),('Bay Minette','AL'),('Bay Pines','FL'),('Bay Port','MI'),('Bay Saint Louis','MS'),('Bay Shore','MI'),('Bay Shore','NY'),('Bay Springs','MS'),('Bay Village','OH'),('Bayamon','PR'),('Bayard','IA'),('Bayard','NE'),('Bayard','NM'),('Bayard','WV'),('Bayboro','NC'),('Bayfield','CO'),('Bayfield','WI'),('Baylis','IL'),('Bayonne','NJ'),('Bayou La Batre','AL'),('Bayport','MN'),('Bayport','NY'),('Bays','KY'),('Bayside','CA'),('Bayside','NY'),('Bayside','TX'),('Baytown','TX'),('Bayview','ID'),('Bayville','NJ'),('Bayville','NY'),('Bazine','KS'),('Beach','ND'),('Beach City','OH'),('Beach Haven','NJ'),('Beach Haven','PA'),('Beach Lake','PA'),('Beachwood','NJ'),('Beachwood','OH'),('Beacon','IA'),('Beacon','NY'),('Beacon Falls','CT'),('Beale Afb','CA'),('Bealeton','VA'),('Beallsville','MD'),('Beallsville','OH'),('Beallsville','PA'),('Beals','ME'),('Beaman','IA'),('Bean Station','TN'),('Bear','DE'),('Bear Branch','KY'),('Bear Creek','AL'),('Bear Creek','NC'),('Bear Creek','PA'),('Bear Creek','WI'),('Bear Lake','MI'),('Bear Lake','PA'),('Bear Mountain','NY'),('Bear River City','UT'),('Bearcreek','MT'),('Bearden','AR'),('Beardsley','MN'),('Beardstown','IL'),('Bearsville','NY'),('Beasley','TX'),('Beason','IL'),('Beatrice','AL'),('Beatrice','NE'),('Beattie','KS'),('Beatty','NV'),('Beatty','OR'),('Beattyville','KY'),('Beaufort','MO'),('Beaufort','NC'),('Beaufort','SC'),('Beaumont','CA'),('Beaumont','KS'),('Beaumont','KY'),('Beaumont','MS'),('Beaumont','TX'),('Beaumont','VA'),('Beauty','KY'),('Beaver','AK'),('Beaver','AR'),('Beaver','IA'),('Beaver','KY'),('Beaver','OH'),('Beaver','OK'),('Beaver','OR'),('Beaver','PA'),('Beaver','UT'),('Beaver','WA'),('Beaver','WV'),('Beaver Bay','MN'),('Beaver City','NE'),('Beaver Creek','MN'),('Beaver Crossing','NE'),('Beaver Dam','KY'),('Beaver Dam','WI'),('Beaver Dams','NY'),('Beaver Falls','NY'),('Beaver Falls','PA'),('Beaver Island','MI'),('Beaver Meadows','PA'),('Beaver Springs','PA'),('Beavercreek','OR'),('Beaverdale','PA'),('Beaverdam','OH'),('Beaverdam','VA'),('Beaverton','AL'),('Beaverton','MI'),('Beaverton','OR'),('Beavertown','PA'),('Beaverville','IL'),('Beccaria','PA'),('Bechtelsville','PA'),('Beckemeyer','IL'),('Becker','MN'),('Becker','MS'),('Becket','MA'),('Beckley','WV'),('Beckville','TX'),('Beckwourth','CA'),('Bedford','IA'),('Bedford','IN'),('Bedford','KY'),('Bedford','MA'),('Bedford','MI'),('Bedford','NH'),('Bedford','NY'),('Bedford','OH'),('Bedford','PA'),('Bedford','TX'),('Bedford','VA'),('Bedford','WY'),('Bedford Hills','NY'),('Bedford Park','IL'),('Bedias','TX'),('Bedminster','NJ'),('Bedminster','PA'),('Bedrock','CO'),('Bee','NE'),('Bee','VA'),('Bee Branch','AR'),('Bee Spring','KY'),('Beebe','AR'),('Beebe Plain','VT'),('Beech Bluff','TN'),('Beech Bottom','WV'),('Beech Creek','KY'),('Beech Creek','PA'),('Beech Grove','AR'),('Beech Grove','IN'),('Beech Grove','KY'),('Beech Island','SC'),('Beecher','IL'),('Beecher City','IL'),('Beecher Falls','VT'),('Beechgrove','TN'),('Beechmont','KY'),('Beedeville','AR'),('Beeler','KS'),('Beemer','NE'),('Beersheba Springs','TN'),('Beeson','WV'),('Beetown','WI'),('Beeville','TX'),('Beggs','OK'),('Beirne','AR'),('Bejou','MN'),('Bel Air','MD'),('Bel Alton','MD'),('Belcamp','MD'),('Belcher','KY'),('Belcher','LA'),('Belchertown','MA'),('Belcourt','ND'),('Belden','CA'),('Belden','MS'),('Belden','NE'),('Beldenville','WI'),('Belding','MI'),('Belen','MS'),('Belen','NM'),('Belews Creek','NC'),('Belfair','WA'),('Belfast','ME'),('Belfast','NY'),('Belfast','TN'),('Belfield','ND'),('Belford','NJ'),('Belfry','KY'),('Belfry','MT'),('Belgium','WI'),('Belgrade','ME'),('Belgrade','MN'),('Belgrade','MO'),('Belgrade','MT'),('Belgrade','NE'),('Belgrade Lakes','ME'),('Belhaven','NC'),('Belington','WV'),('Belk','AL'),('Belknap','IL'),('Bell','CA'),('Bell','FL'),('Bell Buckle','TN'),('Bell City','LA'),('Bell City','MO'),('Bell Gardens','CA'),('Bella Vista','AR'),('Bella Vista','CA'),('Bellaire','MI'),('Bellaire','OH'),('Bellaire','TX'),('Bellamy','AL'),('Bellarthur','NC'),('Bellbrook','OH'),('Belle','MO'),('Belle','WV'),('Belle Center','OH'),('Belle Chasse','LA'),('Belle Fourche','SD'),('Belle Glade','FL'),('Belle Haven','VA'),('Belle Mead','NJ'),('Belle Mina','AL'),('Belle Plaine','IA'),('Belle Plaine','KS'),('Belle Plaine','MN'),('Belle Rive','IL'),('Belle Rose','LA'),('Belle Valley','OH'),('Belle Vernon','PA'),('Belleair Beach','FL'),('Bellefontaine','MS'),('Bellefontaine','OH'),('Bellefonte','PA'),('Bellemont','AZ'),('Bellerose','NY'),('Belleview','FL'),('Belleview','MO'),('Belleville','AR'),('Belleville','IL'),('Belleville','KS'),('Belleville','MI'),('Belleville','NJ'),('Belleville','NY'),('Belleville','PA'),('Belleville','WI'),('Belleville','WV'),('Bellevue','IA'),('Bellevue','ID'),('Bellevue','KY'),('Bellevue','MI'),('Bellevue','NE'),('Bellevue','OH'),('Bellevue','TX'),('Bellevue','WA'),('Bellflower','CA'),('Bellflower','IL'),('Bellflower','MO'),('Bellingham','MA'),('Bellingham','MN'),('Bellingham','WA'),('Bellmawr','NJ'),('Bellmont','IL'),('Bellmore','IN'),('Bellmore','NY'),('Bellona','NY'),('Bellows Falls','VT'),('Bellport','NY'),('Bells','TN'),('Bells','TX'),('Bellvale','NY'),('Bellville','GA'),('Bellville','OH'),('Bellville','TX'),('Bellvue','CO'),('Bellwood','AL'),('Bellwood','IL'),('Bellwood','NE'),('Bellwood','PA'),('Belmar','NJ'),('Belmond','IA'),('Belmont','CA'),('Belmont','LA'),('Belmont','MA'),('Belmont','MI'),('Belmont','MS'),('Belmont','NC'),('Belmont','NH'),('Belmont','NY'),('Belmont','OH'),('Belmont','TX'),('Belmont','VT'),('Belmont','WA'),('Belmont','WI'),('Belmont','WV'),('Belmore','OH'),('Beloit','KS'),('Beloit','OH'),('Beloit','WI'),('Belpre','KS'),('Belpre','OH'),('Belsano','PA'),('Belspring','VA'),('Belt','MT'),('Belton','KY'),('Belton','MO'),('Belton','SC'),('Belton','TX'),('Beltrami','MN'),('Beltsville','MD'),('Belva','WV'),('Belvedere Tiburon','CA'),('Belvidere','IL'),('Belvidere','NC'),('Belvidere','NE'),('Belvidere','NJ'),('Belvidere','SD'),('Belvidere','TN'),('Belvidere Center','VT'),('Belview','MN'),('Belvue','KS'),('Belzoni','MS'),('Bement','IL'),('Bemidji','MN'),('Bemus Point','NY'),('Ben Bolt','TX'),('Ben Franklin','TX'),('Ben Hur','VA'),('Ben Lomond','AR'),('Ben Lomond','CA'),('Ben Wheeler','TX'),('Bena','MN'),('Bena','VA'),('Benavides','TX'),('Bend','OR'),('Bend','TX'),('Bendena','KS'),('Bendersville','PA'),('Benedict','KS'),('Benedict','MD'),('Benedict','MN'),('Benedict','ND'),('Benedict','NE'),('Benedicta','ME'),('Benet Lake','WI'),('Benezett','PA'),('Benge','WA'),('Benham','KY'),('Benicia','CA'),('Benjamin','TX'),('Benkelman','NE'),('Benld','IL'),('Bennet','NE'),('Bennett','CO'),('Bennett','IA'),('Bennett','NC'),('Bennettsville','SC'),('Bennington','IN'),('Bennington','KS'),('Bennington','NE'),('Bennington','NH'),('Bennington','OK'),('Bennington','VT'),('Benoit','MS'),('Benoit','WI'),('Bensalem','PA'),('Bensenville','IL'),('Benson','AZ'),('Benson','IL'),('Benson','MD'),('Benson','MN'),('Benson','NC'),('Benson','VT'),('Bent','NM'),('Bent Mountain','VA'),('Bentley','KS'),('Bentley','LA'),('Bentley','MI'),('Bentleyville','PA'),('Benton','AR'),('Benton','CA'),('Benton','IA'),('Benton','IL'),('Benton','KS'),('Benton','KY'),('Benton','LA'),('Benton','MO'),('Benton','MS'),('Benton','PA'),('Benton','TN'),('Benton','WI'),('Benton City','MO'),('Benton City','WA'),('Benton Harbor','MI'),('Benton Ridge','OH'),('Bentonia','MS'),('Bentonville','AR'),('Bentonville','IN'),('Bentonville','OH'),('Bentonville','VA'),('Benwood','WV'),('Benzonia','MI'),('Berclair','TX'),('Berea','KY'),('Berea','OH'),('Berea','WV'),('Beresford','SD'),('Bergen','NY'),('Bergenfield','NJ'),('Berger','MO'),('Bergheim','TX'),('Bergholz','OH'),('Bergland','MI'),('Bergman','AR'),('Bergoo','WV'),('Bergton','VA'),('Berino','NM'),('Berkeley','CA'),('Berkeley','IL'),('Berkeley Heights','NJ'),('Berkeley Springs','WV'),('Berkey','OH'),('Berkley','MA'),('Berkley','MI'),('Berkshire','MA'),('Berkshire','NY'),('Berlin','CT'),('Berlin','GA'),('Berlin','MA'),('Berlin','MD'),('Berlin','ND'),('Berlin','NH'),('Berlin','NJ'),('Berlin','NY'),('Berlin','OH'),('Berlin','PA'),('Berlin','WI'),('Berlin Center','OH'),('Berlin Heights','OH'),('Bern','ID'),('Bern','KS'),('Bernalillo','NM'),('Bernard','IA'),('Bernard','ME'),('Bernardston','MA'),('Bernardsville','NJ'),('Berne','IN'),('Berne','NY'),('Bernhards Bay','NY'),('Bernice','LA'),('Bernie','MO'),('Bernville','PA'),('Berrien Center','MI'),('Berrien Springs','MI'),('Berry','AL'),('Berry','KY'),('Berry Creek','CA'),('Berrysburg','PA'),('Berryton','KS'),('Berryville','AR'),('Berryville','VA'),('Bertha','MN'),('Berthold','ND'),('Berthoud','CO'),('Bertram','TX'),('Bertrand','MO'),('Bertrand','NE'),('Berwick','IA'),('Berwick','IL'),('Berwick','LA'),('Berwick','ME'),('Berwick','PA'),('Berwind','WV'),('Berwyn','IL'),('Berwyn','PA'),('Beryl','UT'),('Bessemer','AL'),('Bessemer','MI'),('Bessemer','PA'),('Bessemer City','NC'),('Bessie','OK'),('Bethalto','IL'),('Bethania','NC'),('Bethany','CT'),('Bethany','IL'),('Bethany','KY'),('Bethany','LA'),('Bethany','MO'),('Bethany','OK'),('Bethany','WV'),('Bethany Beach','DE'),('Bethel','AK'),('Bethel','CT'),('Bethel','DE'),('Bethel','ME'),('Bethel','MN'),('Bethel','MO'),('Bethel','NC'),('Bethel','NY'),('Bethel','OH'),('Bethel','OK'),('Bethel','PA'),('Bethel','VT'),('Bethel Island','CA'),('Bethel Park','PA'),('Bethel Springs','TN'),('Bethelridge','KY'),('Bethera','SC'),('Bethesda','MD'),('Bethesda','OH'),('Bethlehem','CT'),('Bethlehem','GA'),('Bethlehem','IN'),('Bethlehem','KY'),('Bethlehem','MD'),('Bethlehem','NH'),('Bethlehem','PA'),('Bethpage','NY'),('Bethpage','TN'),('Bethune','CO'),('Bethune','SC'),('Betsy Layne','KY'),('Bettendorf','IA'),('Betterton','MD'),('Bettles Field','AK'),('Bettsville','OH'),('Beulah','CO'),('Beulah','MI'),('Beulah','MO'),('Beulah','MS'),('Beulah','ND'),('Beulah','WY'),('Beulaville','NC'),('Beverly','KS'),('Beverly','KY'),('Beverly','MA'),('Beverly','NJ'),('Beverly','OH'),('Beverly','WA'),('Beverly','WV'),('Beverly Hills','CA'),('Beverly Hills','FL'),('Beverly Shores','IN'),('Bevier','MO'),('Bevington','IA'),('Bevinsville','KY'),('Bexar','AR'),('Beyer','PA'),('Bible School Park','NY'),('Bickleton','WA'),('Bickmore','WV'),('Bicknell','IN'),('Bicknell','UT'),('Biddeford','ME'),('Biddeford Pool','ME'),('Biddle','MT'),('Bidwell','OH'),('Bieber','CA'),('Bienville','LA'),('Big Arm','MT'),('Big Bar','CA'),('Big Bay','MI'),('Big Bear City','CA'),('Big Bear Lake','CA'),('Big Bend','CA'),('Big Bend','WI'),('Big Bend','WV'),('Big Bend National Park','TX'),('Big Cabin','OK'),('Big Clifty','KY'),('Big Cove Tannery','PA'),('Big Creek','CA'),('Big Creek','KY'),('Big Creek','MS'),('Big Creek','WV'),('Big Falls','MN'),('Big Falls','WI'),('Big Flat','AR'),('Big Flats','NY'),('Big Horn','WY'),('Big Indian','NY'),('Big Island','VA'),('Big Lake','AK'),('Big Lake','MN'),('Big Lake','TX'),('Big Laurel','KY'),('Big Oak Flat','CA'),('Big Pine','CA'),('Big Pine Key','FL'),('Big Piney','WY'),('Big Pool','MD'),('Big Prairie','OH'),('Big Rapids','MI'),('Big Rock','IL'),('Big Rock','TN'),('Big Rock','VA'),('Big Run','PA'),('Big Run','WV'),('Big Sandy','MT'),('Big Sandy','TN'),('Big Sandy','TX'),('Big Sandy','WV'),('Big Sky','MT'),('Big Spring','TX'),('Big Springs','NE'),('Big Springs','WV'),('Big Stone City','SD'),('Big Stone Gap','VA'),('Big Sur','CA'),('Big Timber','MT'),('Big Wells','TX'),('Bigelow','AR'),('Bigelow','MN'),('Bigfoot','TX'),('Bigfork','MN'),('Bigfork','MT'),('Biggers','AR'),('Biggs','CA'),('Biggsville','IL'),('Bighill','KY'),('Bighorn','MT'),('Bigler','PA'),('Biglerville','PA'),('Billerica','MA'),('Billings','MO'),('Billings','MT'),('Billings','NY'),('Billings','OK'),('Billingsley','AL'),('Biloxi','MS'),('Bim','WV'),('Bimble','KY'),('Binford','ND'),('Bingen','WA'),('Binger','OK'),('Bingham','IL'),('Bingham','ME'),('Bingham','NE'),('Bingham Canyon','UT'),('Bingham Lake','MN'),('Binghamton','NY'),('Biola','CA'),('Bippus','IN'),('Birch Harbor','ME'),('Birch River','WV'),('Birch Run','MI'),('Birch Tree','MO'),('Birchdale','MN'),('Birchleaf','VA'),('Birchrunville','PA'),('Birchwood','TN'),('Birchwood','WI'),('Bird City','KS'),('Bird In Hand','PA'),('Bird Island','MN'),('Birds Landing','CA'),('Birdsboro','PA'),('Birdseye','IN'),('Birdsnest','VA'),('Birmingham','AL'),('Birmingham','IA'),('Birmingham','MI'),('Birmingham','NJ'),('Birmingham','OH'),('Birnamwood','WI'),('Birney','MT'),('Bisbee','AZ'),('Bisbee','ND'),('Biscoe','AR'),('Biscoe','NC'),('Bishop','CA'),('Bishop','GA'),('Bishop','TX'),('Bishop','VA'),('Bishop Hill','IL'),('Bishopville','MD'),('Bishopville','SC'),('Bismarck','AR'),('Bismarck','IL'),('Bismarck','MO'),('Bismarck','ND'),('Bison','KS'),('Bison','OK'),('Bison','SD'),('Bitely','MI'),('Bittinger','MD'),('Bivalve','MD'),('Bivins','TX'),('Biwabik','MN'),('Bixby','MO'),('Bixby','OK'),('Blachly','OR'),('Black','AL'),('Black','MO'),('Black Canyon City','AZ'),('Black Creek','NC'),('Black Creek','NY'),('Black Creek','WI'),('Black Diamond','WA'),('Black Eagle','MT'),('Black Earth','WI'),('Black Hawk','CO'),('Black Hawk','SD'),('Black Lick','PA'),('Black Mountain','NC'),('Black Oak','AR'),('Black River','MI'),('Black River','NY'),('Black River Falls','WI'),('Black Rock','AR'),('Blackburn','MO'),('Blackduck','MN'),('Blackey','KY'),('Blackfoot','ID'),('Blackford','KY'),('Blacklick','OH'),('Blacksburg','SC'),('Blacksburg','VA'),('Blackshear','GA'),('Blackstock','SC'),('Blackstone','IL'),('Blackstone','MA'),('Blackstone','VA'),('Blacksville','WV'),('Blackville','SC'),('Blackwater','MO'),('Blackwater','VA'),('Blackwell','MO'),('Blackwell','OK'),('Blackwell','TX'),('Blackwood','NJ'),('Bladen','NE'),('Bladenboro','NC'),('Bladensburg','MD'),('Bladensburg','OH'),('Blain','PA'),('Blaine','KY'),('Blaine','ME'),('Blaine','OH'),('Blaine','TN'),('Blaine','WA'),('Blair','NE'),('Blair','OK'),('Blair','SC'),('Blair','WI'),('Blair','WV'),('Blairs','VA'),('Blairs Mills','PA'),('Blairsburg','IA'),('Blairsden-graeagle','CA'),('Blairstown','IA'),('Blairstown','MO'),('Blairstown','NJ'),('Blairsville','GA'),('Blairsville','PA'),('Blakely','GA'),('Blakely Island','WA'),('Blakesburg','IA'),('Blakeslee','OH'),('Blakeslee','PA'),('Blanca','CO'),('Blanch','NC'),('Blanchard','IA'),('Blanchard','ID'),('Blanchard','LA'),('Blanchard','MI'),('Blanchard','ND'),('Blanchard','OK'),('Blanchard','PA'),('Blanchardville','WI'),('Blanchester','OH'),('Blanco','NM'),('Blanco','OK'),('Blanco','TX'),('Bland','MO'),('Bland','VA'),('Blandburg','PA'),('Blandford','MA'),('Blanding','UT'),('Blandinsville','IL'),('Blandon','PA'),('Blanford','IN'),('Blanket','TX'),('Blauvelt','NY'),('Blawenburg','NJ'),('Bledsoe','KY'),('Bledsoe','TX'),('Bleiblerville','TX'),('Blencoe','IA'),('Blenheim','SC'),('Blenker','WI'),('Blessing','TX'),('Blevins','AR'),('Bliss','ID'),('Bliss','NY'),('Blissfield','MI'),('Blissfield','OH'),('Block Island','RI'),('Blocker','OK'),('Blocksburg','CA'),('Blockton','IA'),('Blodgett','MO'),('Blodgett','OR'),('Blodgett Mills','NY'),('Blomkest','MN'),('Bloomburg','TX'),('Bloomdale','OH'),('Bloomer','WI'),('Bloomery','WV'),('Bloomfield','CT'),('Bloomfield','IA'),('Bloomfield','IN'),('Bloomfield','KY'),('Bloomfield','MO'),('Bloomfield','MT'),('Bloomfield','NE'),('Bloomfield','NJ'),('Bloomfield','NM'),('Bloomfield','NY'),('Bloomfield Hills','MI'),('Blooming Glen','PA'),('Blooming Grove','NY'),('Blooming Grove','TX'),('Blooming Prairie','MN'),('Bloomingburg','NY'),('Bloomingburg','OH'),('Bloomingdale','GA'),('Bloomingdale','IL'),('Bloomingdale','IN'),('Bloomingdale','MI'),('Bloomingdale','NJ'),('Bloomingdale','NY'),('Bloomingdale','OH'),('Bloomingrose','WV'),('Bloomington','CA'),('Bloomington','ID'),('Bloomington','IL'),('Bloomington','IN'),('Bloomington','MD'),('Bloomington','NE'),('Bloomington','NY'),('Bloomington','TX'),('Bloomington','WI'),('Bloomington Springs','TN'),('Bloomsburg','PA'),('Bloomsbury','NJ'),('Bloomsdale','MO'),('Bloomville','NY'),('Bloomville','OH'),('Blossburg','PA'),('Blossom','TX'),('Blossvale','NY'),('Blount','WV'),('Blounts Creek','NC'),('Blountstown','FL'),('Blountsville','AL'),('Blountville','TN'),('Blowing Rock','NC'),('Bloxom','VA'),('Blue','AZ'),('Blue Ball','PA'),('Blue Bell','PA'),('Blue Creek','OH'),('Blue Creek','WV'),('Blue Diamond','NV'),('Blue Earth','MN'),('Blue Eye','MO'),('Blue Gap','AZ'),('Blue Grass','IA'),('Blue Grass','VA'),('Blue Hill','ME'),('Blue Hill','NE'),('Blue Island','IL'),('Blue Jay','CA'),('Blue Jay','WV'),('Blue Lake','CA'),('Blue Mound','IL'),('Blue Mound','KS'),('Blue Mounds','WI'),('Blue Mountain','AR'),('Blue Mountain','MS'),('Blue Mountain Lake','NY'),('Blue Point','NY'),('Blue Rapids','KS'),('Blue Ridge','GA'),('Blue Ridge','TX'),('Blue Ridge','VA'),('Blue Ridge Summit','PA'),('Blue River','KY'),('Blue River','OR'),('Blue River','WI'),('Blue Rock','OH'),('Blue Springs','MO'),('Blue Springs','MS'),('Blue Springs','NE'),('Bluebell','UT'),('Bluefield','VA'),('Bluefield','WV'),('Bluegrove','TX'),('Bluejacket','OK'),('Bluemont','VA'),('Bluewater','NM'),('Bluff','UT'),('Bluff City','AR'),('Bluff City','KS'),('Bluff City','TN'),('Bluff Dale','TX'),('Bluff Springs','IL'),('Bluffs','IL'),('Bluffton','AR'),('Bluffton','GA'),('Bluffton','IN'),('Bluffton','MN'),('Bluffton','OH'),('Bluffton','SC'),('Bluffton','TX'),('Bluford','IL'),('Blum','TX'),('Blunt','SD'),('Bly','OR'),('Blythe','CA'),('Blythe','GA'),('Blythedale','MO'),('Blytheville','AR'),('Blythewood','SC'),('Boalsburg','PA'),('Board Camp','AR'),('Boardman','OR'),('Boaz','AL'),('Boaz','KY'),('Bob White','WV'),('Bobtown','PA'),('Boca Grande','FL'),('Boca Raton','FL'),('Bock','MN'),('Bode','IA'),('Bodega','CA'),('Bodega Bay','CA'),('Bodfish','CA'),('Boelus','NE'),('Boerne','TX'),('Bogalusa','LA'),('Bogard','MO'),('Bogart','GA'),('Bogata','TX'),('Boggstown','IN'),('Bogota','NJ'),('Bogota','TN'),('Bogue','KS'),('Bogue Chitto','MS'),('Bohannon','VA'),('Bohemia','NY'),('Boiceville','NY'),('Boiling Springs','NC'),('Boiling Springs','PA'),('Boiling Springs','SC'),('Bois D Arc','MO'),('Boise','ID'),('Boise City','OK'),('Boissevain','VA'),('Bokchito','OK'),('Bokeelia','FL'),('Bokoshe','OK'),('Bolckow','MO'),('Boles','AR'),('Boles','IL'),('Boley','OK'),('Boligee','AL'),('Bolinas','CA'),('Boling','TX'),('Bolingbroke','GA'),('Bolingbrook','IL'),('Bolivar','MO'),('Bolivar','NY'),('Bolivar','OH'),('Bolivar','PA'),('Bolivar','TN'),('Bolivia','NC'),('Bolt','WV'),('Bolton','CT'),('Bolton','MA'),('Bolton','MS'),('Bolton','NC'),('Bolton Landing','NY'),('Bombay','NY'),('Bomont','WV'),('Bomoseen','VT'),('Bon Air','AL'),('Bon Aqua','TN'),('Bon Secour','AL'),('Bon Wier','TX'),('Bonaire','GA'),('Bonanza','OR'),('Bonanza','UT'),('Bonaparte','IA'),('Boncarbo','CO'),('Bond','CO'),('Bondsville','MA'),('Bonduel','WI'),('Bondurant','IA'),('Bondurant','WY'),('Bondville','IL'),('Bondville','VT'),('Bone Gap','IL'),('Bonesteel','SD'),('Boneville','GA'),('Bonfield','IL'),('Bonham','TX'),('Bonifay','FL'),('Bonita','CA'),('Bonita','LA'),('Bonita Springs','FL'),('Bonlee','NC'),('Bonne Terre','MO'),('Bonneau','SC'),('Bonner','MT'),('Bonner Springs','KS'),('Bonnerdale','AR'),('Bonners Ferry','ID'),('Bonney Lake','WA'),('Bonnie','IL'),('Bonnieville','KY'),('Bonnots Mill','MO'),('Bonnyman','KY'),('Bono','AR'),('Bonsall','CA'),('Boody','IL'),('Booker','TX'),('Boomer','NC'),('Boomer','WV'),('Boon','MI'),('Boone','CO'),('Boone','IA'),('Boone','NC'),('Boone Grove','IN'),('Boones Mill','VA'),('Booneville','AR'),('Booneville','IA'),('Booneville','KY'),('Booneville','MS'),('Boons Camp','KY'),('Boonsboro','MD'),('Boonton','NJ'),('Boonville','CA'),('Boonville','IN'),('Boonville','MO'),('Boonville','NC'),('Boonville','NY'),('Booth','AL'),('Boothbay','ME'),('Boothbay Harbor','ME'),('Boothville','LA'),('Boqueron','PR'),('Bordelonville','LA'),('Borden','IN'),('Bordentown','NJ'),('Borderland','WV'),('Borger','TX'),('Boring','MD'),('Boring','OR'),('Boron','CA'),('Borrego Springs','CA'),('Borup','MN'),('Boscobel','WI'),('Bosler','WY'),('Bosque','NM'),('Bosque Farms','NM'),('Boss','MO'),('Bossier City','LA'),('Bostic','NC'),('Boston','GA'),('Boston','IN'),('Boston','KY'),('Boston','MA'),('Boston','NY'),('Boston','VA'),('Bostwick','FL'),('Bostwick','GA'),('Boswell','IN'),('Boswell','OK'),('Boswell','PA'),('Bosworth','MO'),('Bothell','WA'),('Botkins','OH'),('Botsford','CT'),('Bottineau','ND'),('Bouckville','NY'),('Boulder','CO'),('Boulder','MT'),('Boulder','UT'),('Boulder','WY'),('Boulder City','NV'),('Boulder Creek','CA'),('Boulder Junction','WI'),('Boulevard','CA'),('Bound Brook','NJ'),('Bountiful','UT'),('Bourbon','IN'),('Bourbon','MO'),('Bourbonnais','IL'),('Bourg','LA'),('Bourneville','OH'),('Bouse','AZ'),('Bouton','IA'),('Boutte','LA'),('Bovard','PA'),('Bovey','MN'),('Bovill','ID'),('Bovina','TX'),('Bovina Center','NY'),('Bow','NH'),('Bow','WA'),('Bowbells','ND'),('Bowden','WV'),('Bowdle','SD'),('Bowdoin','ME'),('Bowdoinham','ME'),('Bowdon','GA'),('Bowdon','ND'),('Bowdon Junction','GA'),('Bowen','IL'),('Bowers','PA'),('Bowerston','OH'),('Bowersville','GA'),('Bowersville','OH'),('Bowie','AZ'),('Bowie','MD'),('Bowie','TX'),('Bowlegs','OK'),('Bowler','WI'),('Bowling Green','FL'),('Bowling Green','IN'),('Bowling Green','KY'),('Bowling Green','MO'),('Bowling Green','OH'),('Bowling Green','SC'),('Bowling Green','VA'),('Bowlus','MN'),('Bowman','GA'),('Bowman','ND'),('Bowman','SC'),('Bowmansdale','PA'),('Bowmanstown','PA'),('Bowmansville','NY'),('Bowmansville','PA'),('Bowstring','MN'),('Box Elder','MT'),('Box Elder','SD'),('Box Springs','GA'),('Boxborough','MA'),('Boxford','MA'),('Boxholm','IA'),('Boyce','LA'),('Boyce','VA'),('Boyceville','WI'),('Boyd','MN'),('Boyd','MT'),('Boyd','TX'),('Boyd','WI'),('Boyden','IA'),('Boyds','MD'),('Boyds','WA'),('Boydton','VA'),('Boyers','PA'),('Boyertown','PA'),('Boyes','MT'),('Boyes Hot Springs','CA'),('Boykin','AL'),('Boykins','VA'),('Boyle','MS'),('Boylston','MA'),('Boyne City','MI'),('Boyne Falls','MI'),('Boynton','OK'),('Boynton','PA'),('Boynton Beach','FL'),('Boys Ranch','TX'),('Boys Town','NE'),('Bozeman','MT'),('Bozman','MD'),('Bozrah','CT'),('Braceville','IL'),('Bracey','VA'),('Brackenridge','PA'),('Brackettville','TX'),('Brackney','PA'),('Braddock','ND'),('Braddock','PA'),('Braddock Heights','MD'),('Braddyville','IA'),('Braden','TN'),('Bradenton','FL'),('Bradenton Beach','FL'),('Bradenville','PA'),('Bradford','AR'),('Bradford','IA'),('Bradford','IL'),('Bradford','IN'),('Bradford','ME'),('Bradford','NH'),('Bradford','NY'),('Bradford','OH'),('Bradford','PA'),('Bradford','RI'),('Bradford','TN'),('Bradford','VT'),('Bradfordsville','KY'),('Bradfordwoods','PA'),('Bradgate','IA'),('Bradley','AR'),('Bradley','CA'),('Bradley','FL'),('Bradley','IL'),('Bradley','ME'),('Bradley','MI'),('Bradley','OK'),('Bradley','SC'),('Bradley','SD'),('Bradley','WV'),('Bradley Beach','NJ'),('Bradleyville','MO'),('Bradner','OH'),('Bradshaw','NE'),('Bradshaw','WV'),('Brady','MT'),('Brady','NE'),('Brady','TX'),('Brady Lake','OH'),('Bradyville','TN'),('Bragg City','MO'),('Braggadocio','MO'),('Braggs','OK'),('Braham','MN'),('Braidwood','IL'),('Brainard','NE'),('Brainard','NY'),('Brainardsville','NY'),('Brainerd','MN'),('Braintree','MA'),('Braithwaite','LA'),('Braman','OK'),('Bramwell','WV'),('Branch','AR'),('Branch','LA'),('Branch','MI'),('Branchdale','PA'),('Branchland','WV'),('Branchport','NY'),('Branchton','PA'),('Branchville','IN'),('Branchville','NJ'),('Branchville','SC'),('Branchville','VA'),('Brandamore','PA'),('Brandeis','CA'),('Brandenburg','KY'),('Brandon','FL'),('Brandon','IA'),('Brandon','MN'),('Brandon','MS'),('Brandon','SD'),('Brandon','TX'),('Brandon','VT'),('Brandon','WI'),('Brandsville','MO'),('Brandt','SD'),('Brandy Camp','PA'),('Brandy Station','VA'),('Brandywine','MD'),('Brandywine','WV'),('Branford','CT'),('Branford','FL'),('Branscomb','CA'),('Branson','CO'),('Branson','MO'),('Brant','MI'),('Brant','NY'),('Brant Lake','NY'),('Brant Rock','MA'),('Brantingham','NY'),('Brantley','AL'),('Brantwood','WI'),('Braselton','GA'),('Brashear','MO'),('Brashear','TX'),('Brasher Falls','NY'),('Brasstown','NC'),('Brattleboro','VT'),('Brave','PA'),('Brawley','CA'),('Braxton','MS'),('Braymer','MO'),('Brayton','IA'),('Brazeau','MO'),('Brazil','IN'),('Brazoria','TX'),('Brea','CA'),('Breaks','VA'),('Breaux Bridge','LA'),('Breckenridge','CO'),('Breckenridge','MI'),('Breckenridge','MN'),('Breckenridge','MO'),('Breckenridge','TX'),('Brecksville','OH'),('Breda','IA'),('Breeden','WV'),('Breeding','KY'),('Breedsville','MI'),('Breese','IL'),('Breesport','NY'),('Breezewood','PA'),('Breezy Point','NY'),('Breinigsville','PA'),('Bremen','AL'),('Bremen','GA'),('Bremen','IN'),('Bremen','KS'),('Bremen','KY'),('Bremen','ME'),('Bremen','ND'),('Bremerton','WA'),('Bremo Bluff','VA'),('Bremond','TX'),('Brenham','TX'),('Brent','AL'),('Brentford','SD'),('Brenton','WV'),('Brentwood','CA'),('Brentwood','MD'),('Brentwood','NY'),('Brentwood','TN'),('Brethren','MI'),('Bretton Woods','NH'),('Bretz','WV'),('Brevard','NC'),('Brevig Mission','AK'),('Brewer','ME'),('Brewerton','NY'),('Brewster','KS'),('Brewster','MA'),('Brewster','MN'),('Brewster','NE'),('Brewster','NY'),('Brewster','OH'),('Brewster','WA'),('Brewton','AL'),('Brian Head','UT'),('Briar','MO'),('Briarcliff Manor','NY'),('Brice','OH'),('Bricelyn','MN'),('Briceville','TN'),('Brick','NJ'),('Brickeys','AR'),('Bridal Veil','OR'),('Bridge City','TX'),('Bridgehampton','NY'),('Bridgeport','AL'),('Bridgeport','CA'),('Bridgeport','CT'),('Bridgeport','IL'),('Bridgeport','MI'),('Bridgeport','NE'),('Bridgeport','NJ'),('Bridgeport','NY'),('Bridgeport','OH'),('Bridgeport','OR'),('Bridgeport','PA'),('Bridgeport','TX'),('Bridgeport','WA'),('Bridgeport','WV'),('Bridger','MT'),('Bridgeton','IN'),('Bridgeton','MO'),('Bridgeton','NC'),('Bridgeton','NJ'),('Bridgeview','IL'),('Bridgeville','CA'),('Bridgeville','DE'),('Bridgeville','PA'),('Bridgewater','CT'),('Bridgewater','IA'),('Bridgewater','MA'),('Bridgewater','ME'),('Bridgewater','MI'),('Bridgewater','NJ'),('Bridgewater','NY'),('Bridgewater','SD'),('Bridgewater','VA'),('Bridgewater','VT'),('Bridgewater Corners','VT'),('Bridgman','MI'),('Bridgton','ME'),('Bridport','VT'),('Brielle','NJ'),('Brier Hill','NY'),('Brier Hill','PA'),('Brierfield','AL'),('Brigantine','NJ'),('Briggs','TX'),('Briggsdale','CO'),('Briggsville','AR'),('Briggsville','WI'),('Brigham City','UT'),('Brighton','CO'),('Brighton','IA'),('Brighton','IL'),('Brighton','MA'),('Brighton','MI'),('Brighton','MO'),('Brighton','TN'),('Brightwaters','NY'),('Brightwood','OR'),('Brightwood','VA'),('Brill','WI'),('Brilliant','AL'),('Brilliant','OH'),('Brillion','WI'),('Brimfield','IL'),('Brimfield','MA'),('Brimhall','NM'),('Brimley','MI'),('Brimson','MN'),('Bringhurst','IN'),('Brinkhaven','OH'),('Brinkley','AR'),('Brinklow','MD'),('Brinktown','MO'),('Brinnon','WA'),('Brinson','GA'),('Brisbane','CA'),('Brisbin','PA'),('Briscoe','TX'),('Bristol','CT'),('Bristol','FL'),('Bristol','GA'),('Bristol','IL'),('Bristol','IN'),('Bristol','ME'),('Bristol','NH'),('Bristol','PA'),('Bristol','RI'),('Bristol','SD'),('Bristol','TN'),('Bristol','VA'),('Bristol','VT'),('Bristol','WI'),('Bristolville','OH'),('Bristow','IA'),('Bristow','IN'),('Bristow','NE'),('Bristow','OK'),('Bristow','VA'),('Britt','IA'),('Britt','MN'),('Brittany','LA'),('Britton','MI'),('Britton','SD'),('Brixey','MO'),('Broad Brook','CT'),('Broad Run','VA'),('Broad Top','PA'),('Broadalbin','NY'),('Broadbent','OR'),('Broaddus','TX'),('Broadford','VA'),('Broadlands','IL'),('Broadus','MT'),('Broadview','IL'),('Broadview','MT'),('Broadview','NM'),('Broadview Heights','OH'),('Broadwater','NE'),('Broadway','NC'),('Broadway','NJ'),('Broadway','OH'),('Broadway','VA'),('Brock','NE'),('Brocket','ND'),('Brockport','NY'),('Brockport','PA'),('Brockton','MA'),('Brockton','MT'),('Brockton','PA'),('Brockway','MT'),('Brockway','PA'),('Brockwell','AR'),('Brocton','IL'),('Brocton','NY'),('Brodhead','KY'),('Brodhead','WI'),('Brodheadsville','PA'),('Brodnax','VA'),('Brogan','OR'),('Brogue','PA'),('Brohard','WV'),('Brohman','MI'),('Brokaw','WI'),('Broken Arrow','OK'),('Broken Bow','NE'),('Broken Bow','OK'),('Bromide','OK'),('Bronaugh','MO'),('Bronson','FL'),('Bronson','IA'),('Bronson','KS'),('Bronson','MI'),('Bronson','TX'),('Bronston','KY'),('Bronte','TX'),('Bronwood','GA'),('Bronx','NY'),('Bronxville','NY'),('Brook','IN'),('Brook Park','MN'),('Brook Park','OH'),('Brookdale','CA'),('Brooke','VA'),('Brookeland','TX'),('Brooker','FL'),('Brookesmith','TX'),('Brookeville','MD'),('Brookfield','CT'),('Brookfield','GA'),('Brookfield','IL'),('Brookfield','MA'),('Brookfield','MO'),('Brookfield','NY'),('Brookfield','OH'),('Brookfield','VT'),('Brookfield','WI'),('Brookhaven','MS'),('Brookhaven','NY'),('Brookhaven','PA'),('Brookings','OR'),('Brookings','SD'),('Brookland','AR'),('Brooklandville','MD'),('Brooklet','GA'),('Brooklin','ME'),('Brookline','MA'),('Brookline','MO'),('Brookline','NH'),('Brookline Village','MA'),('Brooklyn','AL'),('Brooklyn','CT'),('Brooklyn','IA'),('Brooklyn','IN'),('Brooklyn','MD'),('Brooklyn','MI'),('Brooklyn','MS'),('Brooklyn','NY'),('Brooklyn','PA'),('Brooklyn','WI'),('Brookneal','VA'),('Brookport','IL'),('Brooks','CA'),('Brooks','GA'),('Brooks','KY'),('Brooks','ME'),('Brooks','MN'),('Brookshire','TX'),('Brookside','AL'),('Brookside','NJ'),('Brookston','IN'),('Brookston','MN'),('Brookston','TX'),('Brooksville','FL'),('Brooksville','KY'),('Brooksville','ME'),('Brooksville','MS'),('Brookton','ME'),('Brooktondale','NY'),('Brookville','IN'),('Brookville','KS'),('Brookville','OH'),('Brookville','PA'),('Brookwood','AL'),('Broomall','PA'),('Broomes Island','MD'),('Broomfield','CO'),('Brooten','MN'),('Broseley','MO'),('Brothers','OR'),('Broughton','IL'),('Broussard','LA'),('Browder','KY'),('Browerville','MN'),('Brown City','MI'),('Brownell','KS'),('Brownfield','ME'),('Brownfield','PA'),('Brownfield','TX'),('Browning','IL'),('Browning','MO'),('Browning','MT'),('Browns','IL'),('Browns Mills','NJ'),('Browns Summit','NC'),('Browns Valley','CA'),('Browns Valley','MN'),('Brownsboro','AL'),('Brownsboro','TX'),('Brownsburg','IN'),('Brownsburg','VA'),('Brownsdale','MN'),('Brownstown','IL'),('Brownstown','IN'),('Brownstown','PA'),('Brownstown','WA'),('Brownsville','CA'),('Brownsville','IN'),('Brownsville','KY'),('Brownsville','MD'),('Brownsville','MN'),('Brownsville','OH'),('Brownsville','OR'),('Brownsville','PA'),('Brownsville','TN'),('Brownsville','TX'),('Brownsville','VT'),('Brownsville','WI'),('Brownton','MN'),('Browntown','WI'),('Brownville','ME'),('Brownville','NE'),('Brownville','NY'),('Brownville Junction','ME'),('Brownwood','MO'),('Brownwood','TX'),('Broxton','GA'),('Bruce','MS'),('Bruce','SD'),('Bruce','WI'),('Bruce Crossing','MI'),('Bruceton','TN'),('Bruceton Mills','WV'),('Brucetown','VA'),('Bruceville','IN'),('Bruceville','TX'),('Bruin','PA'),('Bruington','VA'),('Brule','NE'),('Brule','WI'),('Brumley','MO'),('Brundidge','AL'),('Bruneau','ID'),('Bruner','MO'),('Bruni','TX'),('Bruning','NE'),('Bruno','MN'),('Bruno','NE'),('Bruno','WV'),('Brunson','SC'),('Brunsville','IA'),('Brunswick','GA'),('Brunswick','MD'),('Brunswick','ME'),('Brunswick','MO'),('Brunswick','NC'),('Brunswick','NE'),('Brunswick','OH'),('Brunswick','TN'),('Brusett','MT'),('Brush','CO'),('Brush Creek','TN'),('Brush Prairie','WA'),('Brush Valley','PA'),('Brushton','NY'),('Brusly','LA'),('Brussels','IL'),('Brussels','WI'),('Brutus','MI'),('Bryan','OH'),('Bryan','TX'),('Bryans Road','MD'),('Bryant','AL'),('Bryant','AR'),('Bryant','FL'),('Bryant','IA'),('Bryant','IL'),('Bryant','IN'),('Bryant','SD'),('Bryant','WI'),('Bryant Pond','ME'),('Bryantown','MD'),('Bryants Store','KY'),('Bryantsville','KY'),('Bryantville','MA'),('Bryce','UT'),('Bryceville','FL'),('Bryn Athyn','PA'),('Bryn Mawr','CA'),('Bryn Mawr','PA'),('Bryson','TX'),('Bryson City','NC'),('Buchanan','GA'),('Buchanan','MI'),('Buchanan','ND'),('Buchanan','NY'),('Buchanan','TN'),('Buchanan','VA'),('Buchanan Dam','TX'),('Buchtel','OH'),('Buck Creek','IN'),('Buck Hill Falls','PA'),('Buckatunna','MS'),('Buckeye','AZ'),('Buckeye','IA'),('Buckeye','WV'),('Buckeye Lake','OH'),('Buckeystown','MD'),('Buckfield','ME'),('Buckhannon','WV'),('Buckhead','GA'),('Buckholts','TX'),('Buckhorn','KY'),('Buckhorn','NM'),('Buckingham','IA'),('Buckingham','IL'),('Buckingham','PA'),('Buckingham','VA'),('Buckland','AK'),('Buckland','MA'),('Buckland','OH'),('Buckley','IL'),('Buckley','MI'),('Buckley','WA'),('Bucklin','KS'),('Bucklin','MO'),('Buckman','MN'),('Buckner','AR'),('Buckner','IL'),('Buckner','KY'),('Buckner','MO'),('Bucks','AL'),('Buckskin','IN'),('Bucksport','ME'),('Bucoda','WA'),('Bucyrus','KS'),('Bucyrus','MO'),('Bucyrus','OH'),('Bud','WV'),('Buda','IL'),('Buda','TX'),('Budd Lake','NJ'),('Bude','MS'),('Buellton','CA'),('Buena','NJ'),('Buena','WA'),('Buena Park','CA'),('Buena Vista','CO'),('Buena Vista','GA'),('Buena Vista','NM'),('Buena Vista','PA'),('Buena Vista','TN'),('Buena Vista','VA'),('Buffalo','IA'),('Buffalo','IL'),('Buffalo','IN'),('Buffalo','KS'),('Buffalo','KY'),('Buffalo','MN'),('Buffalo','MO'),('Buffalo','MT'),('Buffalo','ND'),('Buffalo','NY'),('Buffalo','OH'),('Buffalo','OK'),('Buffalo','SC'),('Buffalo','SD'),('Buffalo','TX'),('Buffalo','WV'),('Buffalo','WY'),('Buffalo Center','IA'),('Buffalo Creek','CO'),('Buffalo Gap','SD'),('Buffalo Gap','TX'),('Buffalo Grove','IL'),('Buffalo Junction','VA'),('Buffalo Lake','MN'),('Buffalo Mills','PA'),('Buffalo Prairie','IL'),('Buffalo Valley','TN'),('Buford','GA'),('Buford','OH'),('Buford','WY'),('Buhl','AL'),('Buhl','ID'),('Buhl','MN'),('Buhler','KS'),('Buies Creek','NC'),('Bula','TX'),('Bulan','KY'),('Bulger','PA'),('Bull Shoals','AR'),('Bullard','TX'),('Bullhead','SD'),('Bullhead City','AZ'),('Bullock','NC'),('Bulls Gap','TN'),('Bullville','NY'),('Bulpitt','IL'),('Bulverde','TX'),('Bumpass','VA'),('Bumpus Mills','TN'),('Buna','TX'),('Bunceton','MO'),('Bunch','OK'),('Buncombe','IL'),('Bunker','MO'),('Bunker Hill','IL'),('Bunker Hill','IN'),('Bunker Hill','KS'),('Bunker Hill','WV'),('Bunkerville','NV'),('Bunkie','LA'),('Bunn','NC'),('Bunnell','FL'),('Bunnlevel','NC'),('Bunola','PA'),('Buras','LA'),('Burbank','CA'),('Burbank','IL'),('Burbank','OH'),('Burbank','OK'),('Burbank','SD'),('Burbank','WA'),('Burchard','NE'),('Burden','KS'),('Burdett','KS'),('Burdett','NY'),('Burdette','AR'),('Burdick','KS'),('Burdine','KY'),('Bureau','IL'),('Burfordville','MO'),('Burgaw','NC'),('Burgess','VA'),('Burgettstown','PA'),('Burghill','OH'),('Burgin','KY'),('Burgoon','OH'),('Burkburnett','TX'),('Burke','NY'),('Burke','SD'),('Burke','VA'),('Burkes Garden','VA'),('Burkesville','KY'),('Burket','IN'),('Burkett','TX'),('Burkettsville','OH'),('Burkeville','TX'),('Burkeville','VA'),('Burkittsville','MD'),('Burleson','TX'),('Burley','ID'),('Burley','WA'),('Burlingame','CA'),('Burlingame','KS'),('Burlingham','NY'),('Burlington','CO'),('Burlington','CT'),('Burlington','IA'),('Burlington','IL'),('Burlington','IN'),('Burlington','KS'),('Burlington','KY'),('Burlington','MA'),('Burlington','ME'),('Burlington','MI'),('Burlington','NC'),('Burlington','ND'),('Burlington','NJ'),('Burlington','OK'),('Burlington','PA'),('Burlington','TX'),('Burlington','VT'),('Burlington','WA'),('Burlington','WI'),('Burlington','WV'),('Burlington','WY'),('Burlington Flats','NY'),('Burlington Junction','MO'),('Burlison','TN'),('Burna','KY'),('Burnet','TX'),('Burnett','WI'),('Burnettsville','IN'),('Burney','CA'),('Burneyville','OK'),('Burnham','ME'),('Burnham','PA'),('Burnips','MI'),('Burns','CO'),('Burns','KS'),('Burns','OR'),('Burns','TN'),('Burns','WY'),('Burns Flat','OK'),('Burnside','IA'),('Burnside','KY'),('Burnside','LA'),('Burnside','PA'),('Burnsville','MN'),('Burnsville','MS'),('Burnsville','NC'),('Burnsville','WV'),('Burnt Cabins','PA'),('Burnt Corn','AL'),('Burnt Hills','NY'),('Burnt Prairie','IL'),('Burnt Ranch','CA'),('Burnwell','AL'),('Burr','NE'),('Burr Hill','VA'),('Burr Oak','KS'),('Burr Oak','MI'),('Burrel','CA'),('Burrows','IN'),('Burrton','KS'),('Burson','CA'),('Burt','IA'),('Burt','MI'),('Burt','NY'),('Burt Lake','MI'),('Burton','MI'),('Burton','OH'),('Burton','TX'),('Burton','WA'),('Burton','WV'),('Burtonsville','MD'),('Burtrum','MN'),('Burwell','NE'),('Busby','MT'),('Bush','KY'),('Bush','LA'),('Bushkill','PA'),('Bushland','TX'),('Bushnell','FL'),('Bushnell','IL'),('Bushnell','NE'),('Bushton','KS'),('Bushwood','MD'),('Buskirk','NY'),('Bussey','IA'),('Bustins Island','ME'),('Busy','KY'),('Butler','AL'),('Butler','GA'),('Butler','IL'),('Butler','IN'),('Butler','KY'),('Butler','MD'),('Butler','MO'),('Butler','NJ'),('Butler','OH'),('Butler','OK'),('Butler','PA'),('Butler','TN'),('Butler','WI'),('Butlerville','IN'),('Butner','NC'),('Butte','MT'),('Butte','ND'),('Butte','NE'),('Butte City','CA'),('Butte Des Morts','WI'),('Butte Falls','OR'),('Butterfield','MN'),('Butterfield','MO'),('Butternut','WI'),('Buttonwillow','CA'),('Buttzville','NJ'),('Buxton','ME'),('Buxton','NC'),('Buxton','ND'),('Buxton','OR'),('Buzzards Bay','MA'),('Byars','OK'),('Bybee','TN'),('Byers','CO'),('Byers','KS'),('Byers','TX'),('Byesville','OH'),('Byfield','MA'),('Byhalia','MS'),('Bylas','AZ'),('Bynum','AL'),('Bynum','MT'),('Bynum','NC'),('Bynum','TX'),('Bypro','KY'),('Byram','MS'),('Byrdstown','TN'),('Byrnedale','PA'),('Byromville','GA'),('Byron','CA'),('Byron','GA'),('Byron','IL'),('Byron','MI'),('Byron','MN'),('Byron','NE'),('Byron','NY'),('Byron','WY'),('Byron Center','MI'),('Caballo','NM'),('Cabazon','CA'),('Cabery','IL'),('Cabin Creek','WV'),('Cabin John','MD'),('Cabins','WV'),('Cable','OH'),('Cable','WI'),('Cabo Rojo','PR'),('Cabool','MO'),('Cabot','AR'),('Cabot','PA'),('Cabot','VT'),('Cache','OK'),('Cache Junction','UT'),('Cactus','TX'),('Caddo','OK'),('Caddo','TX'),('Caddo Gap','AR'),('Caddo Mills','TX'),('Cade','LA'),('Cades','SC'),('Cadet','MO'),('Cadillac','MI'),('Cadiz','KY'),('Cadiz','OH'),('Cadogan','PA'),('Cadott','WI'),('Cadwell','GA'),('Cadyville','NY'),('Caguas','PR'),('Cahone','CO'),('Cainsville','MO'),('Cairnbrook','PA'),('Cairo','GA'),('Cairo','IL'),('Cairo','MO'),('Cairo','NE'),('Cairo','NY'),('Cairo','OH'),('Cairo','WV'),('Cal Nev Ari','NV'),('Calabasas','CA'),('Calabash','NC'),('Calais','ME'),('Calais','VT'),('Calamus','IA'),('Calcium','NY'),('Calder','ID'),('Caldwell','AR'),('Caldwell','ID'),('Caldwell','KS'),('Caldwell','NJ'),('Caldwell','OH'),('Caldwell','TX'),('Caldwell','WV'),('Cale','AR'),('Caledonia','IL'),('Caledonia','MI'),('Caledonia','MN'),('Caledonia','MO'),('Caledonia','MS'),('Caledonia','ND'),('Caledonia','NY'),('Caledonia','OH'),('Caledonia','WI'),('Calera','AL'),('Calera','OK'),('Calexico','CA'),('Calhan','CO'),('Calhoun','GA'),('Calhoun','IL'),('Calhoun','KY'),('Calhoun','LA'),('Calhoun','MO'),('Calhoun','TN'),('Calhoun City','MS'),('Calhoun Falls','SC'),('Calico Rock','AR'),('Caliente','CA'),('Caliente','NV'),('Califon','NJ'),('California','KY'),('California','MD'),('California','MO'),('California','PA'),('California City','CA'),('California Hot Springs','CA'),('Calimesa','CA'),('Calion','AR'),('Calipatria','CA'),('Calistoga','CA'),('Call','TX'),('Callahan','CA'),('Callahan','FL'),('Callands','VA'),('Callao','MO'),('Callao','VA'),('Callaway','MD'),('Callaway','MN'),('Callaway','NE'),('Callaway','VA'),('Callender','IA'),('Callensburg','PA'),('Callery','PA'),('Callicoon','NY'),('Callicoon Center','NY'),('Calliham','TX'),('Calmar','IA'),('Calpella','CA'),('Calpine','CA'),('Calumet','IA'),('Calumet','MI'),('Calumet','MN'),('Calumet','OK'),('Calumet','PA'),('Calumet City','IL'),('Calvary','GA'),('Calvert','AL'),('Calvert','TX'),('Calvert City','KY'),('Calverton','NY'),('Calverton','VA'),('Calvin','KY'),('Calvin','LA'),('Calvin','ND'),('Calvin','OK'),('Calvin','PA'),('Calvin','WV'),('Calypso','NC'),('Camak','GA'),('Camanche','IA'),('Camano Island','WA'),('Camargo','IL'),('Camargo','OK'),('Camarillo','CA'),('Camas','WA'),('Camas Valley','OR'),('Cambra','PA'),('Cambria','CA'),('Cambria','IL'),('Cambria','WI'),('Cambria Heights','NY'),('Cambridge','IA'),('Cambridge','ID'),('Cambridge','IL'),('Cambridge','KS'),('Cambridge','MA'),('Cambridge','MD'),('Cambridge','ME'),('Cambridge','MN'),('Cambridge','NE'),('Cambridge','NY'),('Cambridge','OH'),('Cambridge','VT'),('Cambridge','WI'),('Cambridge City','IN'),('Cambridge Springs','PA'),('Cambridgeport','VT'),('Camby','IN'),('Camden','AL'),('Camden','AR'),('Camden','IL'),('Camden','IN'),('Camden','ME'),('Camden','MI'),('Camden','MO'),('Camden','MS'),('Camden','NC'),('Camden','NJ'),('Camden','NY'),('Camden','OH'),('Camden','SC'),('Camden','TN'),('Camden','TX'),('Camden','WV'),('Camden On Gauley','WV'),('Camden Point','MO'),('Camden Wyoming','DE'),('Camdenton','MO'),('Cameron','AZ'),('Cameron','IL'),('Cameron','LA'),('Cameron','MO'),('Cameron','MT'),('Cameron','NC'),('Cameron','NY'),('Cameron','OH'),('Cameron','OK'),('Cameron','SC'),('Cameron','TX'),('Cameron','WI'),('Cameron','WV'),('Cameron Mills','NY'),('Camilla','GA'),('Camillus','NY'),('Camino','CA'),('Cammal','PA'),('Camp','AR'),('Camp Creek','WV'),('Camp Crook','SD'),('Camp Dennison','OH'),('Camp Douglas','WI'),('Camp Grove','IL'),('Camp H M Smith','HI'),('Camp Hill','AL'),('Camp Hill','PA'),('Camp Lake','WI'),('Camp Lejeune','NC'),('Camp Meeker','CA'),('Camp Murray','WA'),('Camp Nelson','CA'),('Camp Pendleton','CA'),('Camp Point','IL'),('Camp Sherman','OR'),('Camp Verde','AZ'),('Camp Wood','TX'),('Campaign','TN'),('Campbell','AL'),('Campbell','CA'),('Campbell','MN'),('Campbell','MO'),('Campbell','NE'),('Campbell','NY'),('Campbell','OH'),('Campbell','TX'),('Campbell Hall','NY'),('Campbell Hill','IL'),('Campbellsburg','IN'),('Campbellsburg','KY'),('Campbellsport','WI'),('Campbellsville','KY'),('Campbellton','FL'),('Campbellton','TX'),('Campbelltown','PA'),('Campo','CA'),('Campo','CO'),('Campo Seco','CA'),('Campobello','SC'),('Campti','LA'),('Campton','KY'),('Campton','NH'),('Camptonville','CA'),('Camptown','PA'),('Campus','IL'),('Camuy','PR'),('Cana','VA'),('Canaan','CT'),('Canaan','IN'),('Canaan','ME'),('Canaan','NH'),('Canaan','NY'),('Canaan','VT'),('Canada','KY'),('Canadensis','PA'),('Canadian','OK'),('Canadian','TX'),('Canadys','SC'),('Canajoharie','NY'),('Canal Fulton','OH'),('Canal Point','FL'),('Canal Winchester','OH'),('Canalou','MO'),('Canandaigua','NY'),('Canaseraga','NY'),('Canastota','NY'),('Canby','CA'),('Canby','MN'),('Canby','OR'),('Candia','NH'),('Candler','FL'),('Candler','NC'),('Cando','ND'),('Candor','NC'),('Candor','NY'),('Cane Valley','KY'),('Caneadea','NY'),('Canehill','AR'),('Caney','KS'),('Caney','OK'),('Caneyville','KY'),('Canfield','OH'),('Canisteo','NY'),('Canistota','SD'),('Canjilon','NM'),('Canmer','KY'),('Cannel City','KY'),('Cannelburg','IN'),('Cannelton','IN'),('Cannelton','WV'),('Cannon','KY'),('Cannon Afb','NM'),('Cannon Ball','ND'),('Cannon Beach','OR'),('Cannon Falls','MN'),('Cannonsburg','MI'),('Cannonville','UT'),('Canoga Park','CA'),('Canon','GA'),('Canon City','CO'),('Canones','NM'),('Canonsburg','PA'),('Canova','SD'),('Canovanas','PR'),('Canterbury','CT'),('Canterbury','NH'),('Cantil','CA'),('Canton','CT'),('Canton','GA'),('Canton','IL'),('Canton','KS'),('Canton','MA'),('Canton','ME'),('Canton','MI'),('Canton','MN'),('Canton','MO'),('Canton','MS'),('Canton','NC'),('Canton','NY'),('Canton','OH'),('Canton','OK'),('Canton','PA'),('Canton','SD'),('Canton','TX'),('Canton Center','CT'),('Cantonment','FL'),('Cantrall','IL'),('Cantril','IA'),('Cantua Creek','CA'),('Cantwell','AK'),('Canute','OK'),('Canutillo','TX'),('Canvas','WV'),('Canyon','CA'),('Canyon','MN'),('Canyon','TX'),('Canyon City','OR'),('Canyon Country','CA'),('Canyon Creek','MT'),('Canyon Dam','CA'),('Canyon Lake','TX'),('Canyonville','OR'),('Capac','MI'),('Capay','CA'),('Cape Canaveral','FL'),('Cape Charles','VA'),('Cape Coral','FL'),('Cape Elizabeth','ME'),('Cape Fair','MO'),('Cape Girardeau','MO'),('Cape May','NJ'),('Cape May Court House','NJ'),('Cape May Point','NJ'),('Cape Neddick','ME'),('Cape Porpoise','ME'),('Cape Vincent','NY'),('Capeville','VA'),('Capistrano Beach','CA'),('Capitan','NM'),('Capitol','MT'),('Capitol Heights','MD'),('Capitola','CA'),('Caplinger Mills','MO'),('Capon Bridge','WV'),('Capon Springs','WV'),('Caprock','NM'),('Capron','IL'),('Capron','VA'),('Capshaw','AL'),('Captain Cook','HI'),('Captiva','FL'),('Capulin','CO'),('Capulin','NM'),('Caputa','SD'),('Caratunk','ME'),('Caraway','AR'),('Carbon','IA'),('Carbon','IN'),('Carbon','TX'),('Carbon Cliff','IL'),('Carbon Hill','AL'),('Carbon Hill','OH'),('Carbonado','WA'),('Carbondale','CO'),('Carbondale','IL'),('Carbondale','KS'),('Carbondale','OH'),('Carbondale','PA'),('Cardale','PA'),('Cardiff','AL'),('Cardiff By The Sea','CA'),('Cardin','OK'),('Cardinal','VA'),('Cardington','OH'),('Cardwell','MO'),('Cardwell','MT'),('Carefree','AZ'),('Carencro','LA'),('Caret','VA'),('Carey','ID'),('Carey','OH'),('Careywood','ID'),('Caribou','ME'),('Carl Junction','MO'),('Carle Place','NY'),('Carleton','MI'),('Carleton','NE'),('Carlin','NV'),('Carlinville','IL'),('Carlisle','AR'),('Carlisle','IA'),('Carlisle','IN'),('Carlisle','KY'),('Carlisle','MA'),('Carlisle','NY'),('Carlisle','PA'),('Carlisle','SC'),('Carlock','IL'),('Carlos','MN'),('Carlotta','CA'),('Carlsbad','CA'),('Carlsbad','NM'),('Carlsbad','TX'),('Carlsborg','WA'),('Carlstadt','NJ'),('Carlton','AL'),('Carlton','GA'),('Carlton','MN'),('Carlton','OR'),('Carlton','PA'),('Carlton','TX'),('Carlton','WA'),('Carlyle','IL'),('Carman','IL'),('Carmel','CA'),('Carmel','IN'),('Carmel','ME'),('Carmel','NY'),('Carmel By The Sea','CA'),('Carmel Valley','CA'),('Carmen','ID'),('Carmen','OK'),('Carmi','IL'),('Carmichael','CA'),('Carmichaels','PA'),('Carmine','TX'),('Carnation','WA'),('Carnegie','OK'),('Carnegie','PA'),('Carnelian Bay','CA'),('Carnesville','GA'),('Carney','MI'),('Carney','OK'),('Caro','MI'),('Caroga Lake','NY'),('Carol Stream','IL'),('Caroleen','NC'),('Carolina','PR'),('Carolina','RI'),('Carolina','WV'),('Carolina Beach','NC'),('Caroline','WI'),('Carp Lake','MI'),('Carpenter','IA'),('Carpenter','SD'),('Carpenter','WY'),('Carpentersville','IL'),('Carpinteria','CA'),('Carpio','ND'),('Carr','CO'),('Carrabelle','FL'),('Carrboro','NC'),('Carrie','KY'),('Carrier','OK'),('Carrier Mills','IL'),('Carriere','MS'),('Carrington','ND'),('Carrizo Springs','TX'),('Carrizozo','NM'),('Carroll','IA'),('Carroll','NE'),('Carroll','OH'),('Carrolls','WA'),('Carrollton','AL'),('Carrollton','GA'),('Carrollton','IL'),('Carrollton','KY'),('Carrollton','MI'),('Carrollton','MO'),('Carrollton','MS'),('Carrollton','OH'),('Carrollton','TX'),('Carrollton','VA'),('Carrolltown','PA'),('Carrsville','VA'),('Carson','CA'),('Carson','IA'),('Carson','MS'),('Carson','ND'),('Carson','NM'),('Carson','VA'),('Carson','WA'),('Carson City','MI'),('Carson City','NV'),('Carsonville','MI'),('Carter','KY'),('Carter','MT'),('Carter','OK'),('Carter Lake','IA'),('Carteret','NJ'),('Cartersville','GA'),('Cartersville','VA'),('Carterville','IL'),('Carterville','MO'),('Carthage','AR'),('Carthage','IL'),('Carthage','IN'),('Carthage','MO'),('Carthage','MS'),('Carthage','NC'),('Carthage','NY'),('Carthage','SD'),('Carthage','TN'),('Carthage','TX'),('Cartwright','ND'),('Cartwright','OK'),('Caruthers','CA'),('Caruthersville','MO'),('Carver','MA'),('Carver','MN'),('Carversville','PA'),('Carville','LA'),('Cary','IL'),('Cary','MS'),('Cary','NC'),('Caryville','FL'),('Caryville','TN'),('Casa','AR'),('Casa Blanca','NM'),('Casa Grande','AZ'),('Casanova','VA'),('Casar','NC'),('Cascade','CO'),('Cascade','IA'),('Cascade','ID'),('Cascade','MD'),('Cascade','MO'),('Cascade','MT'),('Cascade','VA'),('Cascade','WI'),('Cascade Locks','OR'),('Cascadia','OR'),('Cascilla','MS'),('Casco','ME'),('Casco','MI'),('Casco','WI'),('Caseville','MI'),('Casey','IA'),('Casey','IL'),('Caseyville','IL'),('Cash','AR'),('Cashiers','NC'),('Cashion','AZ'),('Cashion','OK'),('Cashmere','WA'),('Cashton','WI'),('Cashtown','PA'),('Casmalia','CA'),('Casnovia','MI'),('Cason','TX'),('Caspar','CA'),('Casper','WY'),('Caspian','MI'),('Cass','WV'),('Cass City','MI'),('Cass Lake','MN'),('Cassadaga','FL'),('Cassadaga','NY'),('Cassandra','PA'),('Cassatt','SC'),('Casscoe','AR'),('Cassel','CA'),('Casselberry','FL'),('Casselton','ND'),('Cassoday','KS'),('Cassopolis','MI'),('Casstown','OH'),('Cassville','GA'),('Cassville','MO'),('Cassville','NY'),('Cassville','PA'),('Cassville','WI'),('Cassville','WV'),('Castaic','CA'),('Castalia','IA'),('Castalia','NC'),('Castalia','OH'),('Castalian Springs','TN'),('Castana','IA'),('Castanea','PA'),('Castaner','PR'),('Castell','TX'),('Castella','CA'),('Castile','NY'),('Castine','ME'),('Castle','OK'),('Castle Creek','NY'),('Castle Dale','UT'),('Castle Hayne','NC'),('Castle Point','NY'),('Castle Rock','CO'),('Castle Rock','MN'),('Castle Rock','WA'),('Castleberry','AL'),('Castleford','ID'),('Castleton','IL'),('Castleton','VA'),('Castleton','VT'),('Castleton On Hudson','NY'),('Castlewood','SD'),('Castlewood','VA'),('Castor','LA'),('Castorland','NY'),('Castro Valley','CA'),('Castroville','CA'),('Castroville','TX'),('Cat Spring','TX'),('Cataldo','ID'),('Catalina','AZ'),('Catano','PR'),('Cataract','WI'),('Catarina','TX'),('Catasauqua','PA'),('Cataula','GA'),('Cataumet','MA'),('Catawba','NC'),('Catawba','OH'),('Catawba','SC'),('Catawba','VA'),('Catawba','WI'),('Catawissa','MO'),('Catawissa','PA'),('Catharine','KS'),('Catharpin','VA'),('Cathay','ND'),('Cathedral City','CA'),('Catherine','AL'),('Catheys Valley','CA'),('Cathlamet','WA'),('Catlett','VA'),('Catlettsburg','KY'),('Catlin','IL'),('Cato','NY'),('Catonsville','MD'),('Catoosa','OK'),('Catron','MO'),('Catskill','NY'),('Cattaraugus','NY'),('Caulfield','MO'),('Causey','NM'),('Cavalier','ND'),('Cave City','AR'),('Cave City','KY'),('Cave Creek','AZ'),('Cave In Rock','IL'),('Cave Junction','OR'),('Cave Spring','GA'),('Cave Springs','AR'),('Cavendish','VT'),('Cavetown','MD'),('Cavour','SD'),('Cawker City','KS'),('Cawood','KY'),('Cayce','SC'),('Cayey','PR'),('Cayucos','CA'),('Cayuga','IN'),('Cayuga','ND'),('Cayuga','NY'),('Cayuga','TX'),('Cayuta','NY'),('Cazadero','CA'),('Cazenovia','NY'),('Cazenovia','WI'),('Cebolla','NM'),('Cecil','AL'),('Cecil','AR'),('Cecil','GA'),('Cecil','OH'),('Cecil','PA'),('Cecil','WI'),('Cecilia','KY'),('Cecilia','LA'),('Cecilton','MD'),('Cedar','IA'),('Cedar','KS'),('Cedar','MI'),('Cedar','MN'),('Cedar Bluff','AL'),('Cedar Bluff','VA'),('Cedar Bluffs','NE'),('Cedar Brook','NJ'),('Cedar City','UT'),('Cedar Creek','NE'),('Cedar Creek','TX'),('Cedar Crest','NM'),('Cedar Falls','IA'),('Cedar Falls','NC'),('Cedar Glen','CA'),('Cedar Grove','IN'),('Cedar Grove','NC'),('Cedar Grove','NJ'),('Cedar Grove','TN'),('Cedar Grove','WI'),('Cedar Grove','WV'),('Cedar Hill','MO'),('Cedar Hill','TN'),('Cedar Hill','TX'),('Cedar Island','NC'),('Cedar Key','FL'),('Cedar Knolls','NJ'),('Cedar Lake','IN'),('Cedar Lake','MI'),('Cedar Lane','TX'),('Cedar Mountain','NC'),('Cedar Park','TX'),('Cedar Point','IL'),('Cedar Point','KS'),('Cedar Rapids','IA'),('Cedar Rapids','NE'),('Cedar Ridge','CA'),('Cedar Run','PA'),('Cedar Springs','GA'),('Cedar Springs','MI'),('Cedar Vale','KS'),('Cedar Valley','UT'),('Cedarbluff','MS'),('Cedarburg','WI'),('Cedarcreek','MO'),('Cedaredge','CO'),('Cedarhurst','NY'),('Cedarpines Park','CA'),('Cedars','PA'),('Cedartown','GA'),('Cedarvale','NM'),('Cedarville','AR'),('Cedarville','CA'),('Cedarville','IL'),('Cedarville','MI'),('Cedarville','NJ'),('Cedarville','OH'),('Cedarville','WV'),('Cee Vee','TX'),('Ceiba','PR'),('Celeste','TX'),('Celestine','IN'),('Celina','OH'),('Celina','TN'),('Celina','TX'),('Celoron','NY'),('Cement','OK'),('Cement City','MI'),('Centenary','SC'),('Centennial','WY'),('Center','CO'),('Center','KY'),('Center','MO'),('Center','ND'),('Center','NE'),('Center','TX'),('Center Barnstead','NH'),('Center City','MN'),('Center Cross','VA'),('Center Harbor','NH'),('Center Hill','FL'),('Center Junction','IA'),('Center Line','MI'),('Center Lovell','ME'),('Center Moriches','NY'),('Center Ossipee','NH'),('Center Point','IA'),('Center Point','LA'),('Center Point','TX'),('Center Point','WV'),('Center Ridge','AR'),('Center Rutland','VT'),('Center Sandwich','NH'),('Center Strafford','NH'),('Center Tuftonboro','NH'),('Center Valley','PA'),('Centerbrook','CT'),('Centerburg','OH'),('Centereach','NY'),('Centerfield','UT'),('Centerpoint','IN'),('Centerport','NY'),('Centerport','PA'),('Centerton','AR'),('Centertown','KY'),('Centertown','MO'),('Centerview','MO'),('Centerville','AR'),('Centerville','GA'),('Centerville','IA'),('Centerville','IN'),('Centerville','KS'),('Centerville','LA'),('Centerville','MA'),('Centerville','MO'),('Centerville','NY'),('Centerville','PA'),('Centerville','SD'),('Centerville','TN'),('Centerville','TX'),('Centerville','UT'),('Centerville','WA'),('Centrahoma','OK'),('Central','AK'),('Central','AZ'),('Central','IN'),('Central','SC'),('Central','UT'),('Central Bridge','NY'),('Central City','CO'),('Central City','IA'),('Central City','KY'),('Central City','NE'),('Central City','PA'),('Central Falls','RI'),('Central Islip','NY'),('Central Lake','MI'),('Central Point','OR'),('Central Square','NY'),('Central Valley','NY'),('Central Village','CT'),('Centralia','IL'),('Centralia','KS'),('Centralia','MO'),('Centralia','TX'),('Centralia','WA'),('Centre','AL'),('Centre Hall','PA'),('Centreville','AL'),('Centreville','MD'),('Centreville','MI'),('Centreville','MS'),('Centreville','VA'),('Centuria','WI'),('Century','FL'),('Ceredo','WV'),('Ceres','CA'),('Ceres','NY'),('Ceres','VA'),('Ceresco','MI'),('Ceresco','NE'),('Cerrillos','NM'),('Cerritos','CA'),('Cerro','NM'),('Cerro Gordo','IL'),('Cerro Gordo','NC'),('Cerulean','KY'),('Ceylon','MN'),('Chacon','NM'),('Chadbourn','NC'),('Chadds Ford','PA'),('Chadron','NE'),('Chadwick','IL'),('Chadwick','MO'),('Chadwicks','NY'),('Chaffee','MO'),('Chaffee','NY'),('Chagrin Falls','OH'),('Chalfont','PA'),('Chalk Hill','PA'),('Chalkyitsik','AK'),('Challenge','CA'),('Challis','ID'),('Chalmers','IN'),('Chalmette','LA'),('Chama','CO'),('Chama','NM'),('Chamberino','NM'),('Chamberlain','ME'),('Chamberlain','SD'),('Chambers','AZ'),('Chambers','NE'),('Chambersburg','IL'),('Chambersburg','PA'),('Chambersville','PA'),('Chamisal','NM'),('Chamois','MO'),('Champaign','IL'),('Champion','MI'),('Champion','NE'),('Champion','PA'),('Champlain','NY'),('Champlain','VA'),('Champlin','MN'),('Chana','IL'),('Chancellor','AL'),('Chancellor','SD'),('Chandler','AZ'),('Chandler','IN'),('Chandler','MN'),('Chandler','OK'),('Chandler','TX'),('Chandler Heights','AZ'),('Chandlers Valley','PA'),('Chandlersville','OH'),('Chandlerville','IL'),('Changewater','NJ'),('Chanhassen','MN'),('Channahon','IL'),('Channelview','TX'),('Channing','MI'),('Channing','TX'),('Chantilly','VA'),('Chanute','KS'),('Chaparral','NM'),('Chapel Hill','NC'),('Chapel Hill','TN'),('Chapin','IA'),('Chapin','IL'),('Chapin','SC'),('Chaplin','CT'),('Chaplin','KY'),('Chapman','AL'),('Chapman','KS'),('Chapman','NE'),('Chapman Ranch','TX'),('Chapmansboro','TN'),('Chapmanville','WV'),('Chappaqua','NY'),('Chappell','KY'),('Chappell','NE'),('Chappell Hill','TX'),('Chappells','SC'),('Chaptico','MD'),('Chardon','OH'),('Charenton','LA'),('Chariton','IA'),('Charlemont','MA'),('Charleroi','PA'),('Charles City','IA'),('Charles City','VA'),('Charles Town','WV'),('Charleston','AR'),('Charleston','IL'),('Charleston','ME'),('Charleston','MO'),('Charleston','MS'),('Charleston','SC'),('Charleston','TN'),('Charleston','WV'),('Charleston Afb','SC'),('Charlestown','IN'),('Charlestown','MA'),('Charlestown','MD'),('Charlestown','NH'),('Charlestown','RI'),('Charlevoix','MI'),('Charlo','MT'),('Charlotte','AR'),('Charlotte','IA'),('Charlotte','MI'),('Charlotte','NC'),('Charlotte','TN'),('Charlotte','TX'),('Charlotte','VT'),('Charlotte Court House','VA'),('Charlotte Hall','MD'),('Charlottesville','IN'),('Charlottesville','VA'),('Charlotteville','NY'),('Charlton','MA'),('Charlton City','MA'),('Charlton Depot','MA'),('Charlton Heights','WV'),('Charm','OH'),('Charmco','WV'),('Charter Oak','IA'),('Chartley','MA'),('Chase','KS'),('Chase','LA'),('Chase','MD'),('Chase','MI'),('Chase City','VA'),('Chase Mills','NY'),('Chaseburg','WI'),('Chaseley','ND'),('Chaska','MN'),('Chassell','MI'),('Chataignier','LA'),('Chatawa','MS'),('Chateaugay','NY'),('Chatfield','MN'),('Chatfield','OH'),('Chatfield','TX'),('Chatham','IL'),('Chatham','LA'),('Chatham','MA'),('Chatham','MI'),('Chatham','MS'),('Chatham','NH'),('Chatham','NJ'),('Chatham','NY'),('Chatham','PA'),('Chatham','VA'),('Chatom','AL'),('Chatsworth','CA'),('Chatsworth','GA'),('Chatsworth','IA'),('Chatsworth','IL'),('Chatsworth','NJ'),('Chattahoochee','FL'),('Chattanooga','OK'),('Chattanooga','TN'),('Chattaroy','WA'),('Chattaroy','WV'),('Chaumont','NY'),('Chauncey','GA'),('Chauncey','OH'),('Chauncey','WV'),('Chautauqua','KS'),('Chautauqua','NY'),('Chauvin','LA'),('Chavies','KY'),('Chazy','NY'),('Chebanse','IL'),('Chebeague Island','ME'),('Cheboygan','MI'),('Check','VA'),('Checotah','OK'),('Chefornak','AK'),('Chehalis','WA'),('Chelan','WA'),('Chelan Falls','WA'),('Chelmsford','MA'),('Chelsea','AL'),('Chelsea','IA'),('Chelsea','MA'),('Chelsea','MI'),('Chelsea','NY'),('Chelsea','OK'),('Chelsea','VT'),('Cheltenham','MD'),('Cheltenham','PA'),('Chemult','OR'),('Chemung','NY'),('Chenango Bridge','NY'),('Chenango Forks','NY'),('Cheney','KS'),('Cheney','WA'),('Cheneyville','LA'),('Chenoa','IL'),('Chepachet','RI'),('Cheraw','CO'),('Cheraw','SC'),('Cheriton','VA'),('Cherokee','AL'),('Cherokee','IA'),('Cherokee','KS'),('Cherokee','NC'),('Cherokee','OK'),('Cherokee','TX'),('Cherokee Village','AR'),('Cherry','IL'),('Cherry Creek','NY'),('Cherry Creek','SD'),('Cherry Fork','OH'),('Cherry Hill','NJ'),('Cherry Plain','NY'),('Cherry Point','NC'),('Cherry Tree','PA'),('Cherry Valley','AR'),('Cherry Valley','IL'),('Cherry Valley','MA'),('Cherry Valley','NY'),('Cherryfield','ME'),('Cherrylog','GA'),('Cherryvale','KS'),('Cherryville','MO'),('Cherryville','NC'),('Cherryville','PA'),('Chesaning','MI'),('Chesapeake','OH'),('Chesapeake','VA'),('Chesapeake Beach','MD'),('Chesapeake City','MD'),('Cheshire','CT'),('Cheshire','MA'),('Cheshire','OH'),('Cheshire','OR'),('Chesnee','SC'),('Chest Springs','PA'),('Chester','AR'),('Chester','CA'),('Chester','CT'),('Chester','GA'),('Chester','IA'),('Chester','ID'),('Chester','IL'),('Chester','MA'),('Chester','MD'),('Chester','MT'),('Chester','NE'),('Chester','NH'),('Chester','NJ'),('Chester','NY'),('Chester','OH'),('Chester','OK'),('Chester','PA'),('Chester','SC'),('Chester','SD'),('Chester','TX'),('Chester','UT'),('Chester','VA'),('Chester','VT'),('Chester','WV'),('Chester Depot','VT'),('Chester Gap','VA'),('Chester Heights','PA'),('Chester Springs','PA'),('Chesterfield','IL'),('Chesterfield','MA'),('Chesterfield','MO'),('Chesterfield','NH'),('Chesterfield','NJ'),('Chesterfield','SC'),('Chesterfield','VA'),('Chesterhill','OH'),('Chesterland','OH'),('Chesterton','IN'),('Chestertown','MD'),('Chestertown','NY'),('Chesterville','OH'),('Chestnut','IL'),('Chestnut Hill','MA'),('Chestnut Mound','TN'),('Chestnut Mountain','GA'),('Chestnut Ridge','PA'),('Chestnutridge','MO'),('Cheswick','PA'),('Cheswold','DE'),('Chetek','WI'),('Chetopa','KS'),('Chevak','AK'),('Chevy Chase','MD'),('Chewalla','TN'),('Chewelah','WA'),('Chewsville','MD'),('Cheyenne','OK'),('Cheyenne','WY'),('Cheyenne Wells','CO'),('Cheyney','PA'),('Chicago','IL'),('Chicago Heights','IL'),('Chicago Park','CA'),('Chicago Ridge','IL'),('Chichester','NH'),('Chichester','NY'),('Chickamauga','GA'),('Chickasaw','OH'),('Chickasha','OK'),('Chicken','AK'),('Chico','CA'),('Chico','TX'),('Chicopee','MA'),('Chicora','PA'),('Chicota','TX'),('Chidester','AR'),('Chiefland','FL'),('Chignik','AK'),('Chignik Lagoon','AK'),('Chignik Lake','AK'),('Chilcoot','CA'),('Childersburg','AL'),('Childress','TX'),('Childs','MD'),('Childwold','NY'),('Chilhowee','MO'),('Chilhowie','VA'),('Chili','WI'),('Chillicothe','IA'),('Chillicothe','IL'),('Chillicothe','MO'),('Chillicothe','OH'),('Chillicothe','TX'),('Chilmark','MA'),('Chilo','OH'),('Chiloquin','OR'),('Chilton','TX'),('Chilton','WI'),('Chimacum','WA'),('Chimayo','NM'),('Chimney Rock','CO'),('Chimney Rock','NC'),('China','TX'),('China Grove','NC'),('China Spring','TX'),('China Village','ME'),('Chinchilla','PA'),('Chincoteague Island','VA'),('Chinese Camp','CA'),('Chinle','AZ'),('Chino','CA'),('Chino Hills','CA'),('Chino Valley','AZ'),('Chinook','MT'),('Chinook','WA'),('Chinquapin','NC'),('Chipley','FL'),('Chippewa Bay','NY'),('Chippewa Falls','WI'),('Chippewa Lake','MI'),('Chippewa Lake','OH'),('Chireno','TX'),('Chisago City','MN'),('Chisholm','MN'),('Chitina','AK'),('Chittenango','NY'),('Chittenden','VT'),('Chloe','WV'),('Chloride','AZ'),('Choccolocco','AL'),('Chocorua','NH'),('Chocowinity','NC'),('Choctaw','AR'),('Choctaw','OK'),('Chokio','MN'),('Chokoloskee','FL'),('Choteau','MT'),('Choudrant','LA'),('Chouteau','OK'),('Chowchilla','CA'),('Chriesman','TX'),('Chrisman','IL'),('Chrisney','IN'),('Christchurch','VA'),('Christiana','PA'),('Christiana','TN'),('Christiansburg','OH'),('Christiansburg','VA'),('Christine','ND'),('Christine','TX'),('Christmas','FL'),('Christmas Valley','OR'),('Christopher','IL'),('Christoval','TX'),('Chromo','CO'),('Chualar','CA'),('Chuckey','TN'),('Chugiak','AK'),('Chugwater','WY'),('Chula','GA'),('Chula','MO'),('Chula Vista','CA'),('Chunchula','AL'),('Chunky','MS'),('Church Creek','MD'),('Church Hill','MD'),('Church Hill','TN'),('Church Point','LA'),('Church Road','VA'),('Church Rock','NM'),('Church View','VA'),('Churchs Ferry','ND'),('Churchton','MD'),('Churchville','MD'),('Churchville','NY'),('Churchville','VA'),('Churdan','IA'),('Churubusco','IN'),('Churubusco','NY'),('Ciales','PR'),('Cibecue','AZ'),('Cibola','AZ'),('Cibolo','TX'),('Cicero','IL'),('Cicero','IN'),('Cicero','NY'),('Cidra','PR'),('Cima','CA'),('Cimarron','CO'),('Cimarron','KS'),('Cimarron','NM'),('Cincinnati','IA'),('Cincinnati','OH'),('Cincinnatus','NY'),('Cinebar','WA'),('Circle','AK'),('Circle','MT'),('Circle Pines','MN'),('Circleville','KS'),('Circleville','NY'),('Circleville','OH'),('Circleville','UT'),('Circleville','WV'),('Cisco','GA'),('Cisco','IL'),('Cisco','TX'),('Cisco','UT'),('Cisne','IL'),('Cissna Park','IL'),('Citra','FL'),('Citronelle','AL'),('Citrus Heights','CA'),('City Of Industry','CA'),('Clackamas','OR'),('Claflin','KS'),('Claiborne','MD'),('Claire City','SD'),('Clairfield','TN'),('Clairton','PA'),('Clallam Bay','WA'),('Clam Gulch','AK'),('Clam Lake','WI'),('Clancy','MT'),('Clanton','AL'),('Clara','MS'),('Clara City','MN'),('Clarcona','FL'),('Clare','IA'),('Clare','IL'),('Clare','MI'),('Claremont','CA'),('Claremont','IL'),('Claremont','MN'),('Claremont','NC'),('Claremont','NH'),('Claremont','SD'),('Claremont','VA'),('Claremore','OK'),('Clarence','IA'),('Clarence','LA'),('Clarence','MO'),('Clarence','NY'),('Clarence','PA'),('Clarence Center','NY'),('Clarendon','AR'),('Clarendon','NC'),('Clarendon','NY'),('Clarendon','PA'),('Clarendon','TX'),('Clarendon Hills','IL'),('Claridge','PA'),('Clarinda','IA'),('Clarington','OH'),('Clarington','PA'),('Clarion','IA'),('Clarion','PA'),('Clarissa','MN'),('Clarita','OK'),('Clark','CO'),('Clark','MO'),('Clark','NJ'),('Clark','PA'),('Clark','SD'),('Clark Fork','ID'),('Clark Mills','NY'),('Clarkdale','AZ'),('Clarkdale','GA'),('Clarkedale','AR'),('Clarkesville','GA'),('Clarkfield','MN'),('Clarkia','ID'),('Clarklake','MI'),('Clarkrange','TN'),('Clarkridge','AR'),('Clarks','LA'),('Clarks','NE'),('Clarks Grove','MN'),('Clarks Hill','IN'),('Clarks Hill','SC'),('Clarks Mills','PA'),('Clarks Point','AK'),('Clarks Summit','PA'),('Clarksboro','NJ'),('Clarksburg','CA'),('Clarksburg','IN'),('Clarksburg','MD'),('Clarksburg','MO'),('Clarksburg','OH'),('Clarksburg','PA'),('Clarksburg','TN'),('Clarksburg','WV'),('Clarksdale','MO'),('Clarksdale','MS'),('Clarkson','KY'),('Clarkson','NE'),('Clarkson','NY'),('Clarkston','GA'),('Clarkston','MI'),('Clarkston','UT'),('Clarkston','WA'),('Clarksville','AR'),('Clarksville','FL'),('Clarksville','IA'),('Clarksville','IN'),('Clarksville','MD'),('Clarksville','MI'),('Clarksville','MO'),('Clarksville','NY'),('Clarksville','OH'),('Clarksville','PA'),('Clarksville','TN'),('Clarksville','TX'),('Clarksville','VA'),('Clarkton','MO'),('Clarkton','NC'),('Claryville','NY'),('Clatonia','NE'),('Clatskanie','OR'),('Claude','TX'),('Claudville','VA'),('Claunch','NM'),('Claverack','NY'),('Clawson','MI'),('Clawson','UT'),('Claxton','GA'),('Clay','AL'),('Clay','KY'),('Clay','NY'),('Clay','WV'),('Clay Center','KS'),('Clay Center','NE'),('Clay Center','OH'),('Clay City','IL'),('Clay City','IN'),('Clay City','KY'),('Clay Springs','AZ'),('Clayhole','KY'),('Claymont','DE'),('Claypool','AZ'),('Claypool','IN'),('Claysburg','PA'),('Claysville','PA'),('Clayton','AL'),('Clayton','CA'),('Clayton','DE'),('Clayton','GA'),('Clayton','ID'),('Clayton','IL'),('Clayton','IN'),('Clayton','KS'),('Clayton','LA'),('Clayton','MI'),('Clayton','NC'),('Clayton','NJ'),('Clayton','NM'),('Clayton','NY'),('Clayton','OH'),('Clayton','OK'),('Clayton','TX'),('Clayton','WA'),('Clayton','WI'),('Clayton Lake','ME'),('Claytonville','IL'),('Clayville','NY'),('Clayville','RI'),('Cle Elum','WA'),('Clear','AK'),('Clear Brook','VA'),('Clear Creek','IN'),('Clear Creek','WV'),('Clear Fork','WV'),('Clear Lake','IA'),('Clear Lake','MN'),('Clear Lake','SD'),('Clear Lake','WI'),('Clear Spring','MD'),('Clearbrook','MN'),('Clearfield','IA'),('Clearfield','KY'),('Clearfield','PA'),('Clearfield','UT'),('Clearlake','CA'),('Clearlake','WA'),('Clearlake Oaks','CA'),('Clearlake Park','CA'),('Clearmont','MO'),('Clearmont','WY'),('Clearview City','KS'),('Clearville','PA'),('Clearwater','FL'),('Clearwater','KS'),('Clearwater','MN'),('Clearwater','NE'),('Clearwater','SC'),('Clearwater Beach','FL'),('Cleaton','KY'),('Cleburne','TX'),('Cleghorn','IA'),('Clementon','NJ'),('Clements','CA'),('Clements','MD'),('Clements','MN'),('Clemmons','NC'),('Clemons','IA'),('Clemons','NY'),('Clemson','SC'),('Clendenin','WV'),('Cleo Springs','OK'),('Clermont','FL'),('Clermont','GA'),('Clermont','IA'),('Clermont','KY'),('Cleveland','AL'),('Cleveland','AR'),('Cleveland','GA'),('Cleveland','MN'),('Cleveland','MO'),('Cleveland','MS'),('Cleveland','NC'),('Cleveland','ND'),('Cleveland','NM'),('Cleveland','NY'),('Cleveland','OH'),('Cleveland','OK'),('Cleveland','SC'),('Cleveland','TN'),('Cleveland','TX'),('Cleveland','UT'),('Cleveland','VA'),('Cleveland','WI'),('Cleveland','WV'),('Clever','MO'),('Cleverdale','NY'),('Cleves','OH'),('Clewiston','FL'),('Cliff','NM'),('Cliff Island','ME'),('Clifford','IN'),('Clifford','MI'),('Clifford','ND'),('Clifford','PA'),('Clifford','VA'),('Cliffside','NC'),('Cliffside Park','NJ'),('Cliffwood','NJ'),('Clifton','AZ'),('Clifton','CO'),('Clifton','ID'),('Clifton','IL'),('Clifton','KS'),('Clifton','NJ'),('Clifton','OH'),('Clifton','SC'),('Clifton','TN'),('Clifton','TX'),('Clifton','VA'),('Clifton Forge','VA'),('Clifton Heights','PA'),('Clifton Hill','MO'),('Clifton Park','NY'),('Clifton Springs','NY'),('Clifty','KY'),('Climax','CO'),('Climax','GA'),('Climax','MI'),('Climax','MN'),('Climax','NC'),('Climax','NY'),('Climax Springs','MO'),('Climbing Hill','IA'),('Clinchco','VA'),('Clinchfield','GA'),('Clines Corners','NM'),('Clint','TX'),('Clinton','AL'),('Clinton','AR'),('Clinton','CT'),('Clinton','IA'),('Clinton','IL'),('Clinton','IN'),('Clinton','KY'),('Clinton','LA'),('Clinton','MA'),('Clinton','MD'),('Clinton','ME'),('Clinton','MI'),('Clinton','MN'),('Clinton','MO'),('Clinton','MS'),('Clinton','MT'),('Clinton','NC'),('Clinton','NJ'),('Clinton','NY'),('Clinton','OH'),('Clinton','OK'),('Clinton','PA'),('Clinton','SC'),('Clinton','TN'),('Clinton','WA'),('Clinton','WI'),('Clinton Corners','NY'),('Clinton Township','MI'),('Clintondale','NY'),('Clintonville','PA'),('Clintonville','WI'),('Clintwood','VA'),('Clio','AL'),('Clio','CA'),('Clio','IA'),('Clio','MI'),('Clio','SC'),('Clipper Mills','CA'),('Clitherall','MN'),('Clive','IA'),('Clockville','NY'),('Clontarf','MN'),('Clopton','AL'),('Cloquet','MN'),('Closplint','KY'),('Closter','NJ'),('Clothier','WV'),('Cloudcroft','NM'),('Cloutierville','LA'),('Clover','SC'),('Clover','VA'),('Cloverdale','AL'),('Cloverdale','CA'),('Cloverdale','IN'),('Cloverdale','MI'),('Cloverdale','OH'),('Cloverdale','OR'),('Cloverdale','VA'),('Cloverport','KY'),('Clovis','CA'),('Clovis','NM'),('Clubb','MO'),('Clune','PA'),('Cluster Springs','VA'),('Clute','TX'),('Clutier','IA'),('Clyde','KS'),('Clyde','MO'),('Clyde','NC'),('Clyde','NY'),('Clyde','OH'),('Clyde','TX'),('Clyde Park','MT'),('Clyman','WI'),('Clymer','NY'),('Clymer','PA'),('Clyo','GA'),('Coachella','CA'),('Coahoma','MS'),('Coahoma','TX'),('Coal Center','PA'),('Coal City','IL'),('Coal City','IN'),('Coal City','WV'),('Coal Creek','CO'),('Coal Hill','AR'),('Coal Mountain','WV'),('Coal Run','OH'),('Coal Township','PA'),('Coal Valley','IL'),('Coaldale','CO'),('Coaldale','PA'),('Coalfield','TN'),('Coalgate','OK'),('Coalgood','KY'),('Coaling','AL'),('Coalinga','CA'),('Coalmont','CO'),('Coalmont','IN'),('Coalmont','TN'),('Coalport','PA'),('Coalton','OH'),('Coalton','WV'),('Coalville','UT'),('Coalwood','WV'),('Coamo','PR'),('Coarsegold','CA'),('Coatesville','IN'),('Coatesville','PA'),('Coats','KS'),('Coats','NC'),('Coatsburg','IL'),('Coatsville','MO'),('Cobalt','CT'),('Cobalt','ID'),('Cobb','CA'),('Cobb','GA'),('Cobb','WI'),('Cobb Island','MD'),('Cobbs Creek','VA'),('Cobbtown','GA'),('Cobden','IL'),('Cobleskill','NY'),('Coburn','PA'),('Cochecton','NY'),('Cochecton Center','NY'),('Cochise','AZ'),('Cochiti Lake','NM'),('Cochiti Pueblo','NM'),('Cochran','GA'),('Cochrane','WI'),('Cochranton','PA'),('Cochranville','PA'),('Cockeysville','MD'),('Cocoa','FL'),('Cocoa Beach','FL'),('Cocolalla','ID'),('Cocolamus','PA'),('Coconut Creek','FL'),('Coden','AL'),('Codorus','PA'),('Cody','NE'),('Cody','WY'),('Coeburn','VA'),('Coello','IL'),('Coeur D Alene','ID'),('Coeymans','NY'),('Coeymans Hollow','NY'),('Coffee Creek','MT'),('Coffee Springs','AL'),('Coffeen','IL'),('Coffeeville','AL'),('Coffeeville','MS'),('Coffey','MO'),('Coffeyville','KS'),('Coffman Cove','AK'),('Cofield','NC'),('Cogan Station','PA'),('Coggon','IA'),('Cogswell','ND'),('Cohagen','MT'),('Cohasset','MA'),('Cohasset','MN'),('Cohoctah','MI'),('Cohocton','NY'),('Cohoes','NY'),('Cohutta','GA'),('Coila','MS'),('Coin','IA'),('Coinjock','NC'),('Cokato','MN'),('Cokeburg','PA'),('Coker','AL'),('Cokercreek','TN'),('Cokeville','WY'),('Colbert','GA'),('Colbert','OK'),('Colbert','WA'),('Colburn','ID'),('Colby','KS'),('Colby','WI'),('Colchester','CT'),('Colchester','IL'),('Colchester','VT'),('Colcord','OK'),('Colcord','WV'),('Cold Bay','AK'),('Cold Brook','NY'),('Cold Spring','MN'),('Cold Spring','NY'),('Cold Spring Harbor','NY'),('Colden','NY'),('Coldiron','KY'),('Coldspring','TX'),('Coldwater','KS'),('Coldwater','MI'),('Coldwater','MS'),('Coldwater','OH'),('Cole Camp','MO'),('Colebrook','CT'),('Colebrook','NH'),('Coleharbor','ND'),('Coleman','FL'),('Coleman','GA'),('Coleman','MI'),('Coleman','OK'),('Coleman','TX'),('Coleman','WI'),('Coleman Falls','VA'),('Colerain','NC'),('Colerain','OH'),('Coleraine','MN'),('Coleridge','NE'),('Coles Point','VA'),('Colesburg','IA'),('Coleville','CA'),('Colfax','CA'),('Colfax','IA'),('Colfax','IL'),('Colfax','IN'),('Colfax','LA'),('Colfax','NC'),('Colfax','ND'),('Colfax','WA'),('Colfax','WI'),('Colfax','WV'),('Colgate','WI'),('Collbran','CO'),('College Corner','OH'),('College Grove','TN'),('College Park','MD'),('College Place','WA'),('College Point','NY'),('College Springs','IA'),('College Station','AR'),('College Station','TX'),('Collegedale','TN'),('Collegeport','TX'),('Collegeville','MN'),('Collegeville','PA'),('Collettsville','NC'),('Colleyville','TX'),('Colliers','WV'),('Colliersville','NY'),('Collierville','TN'),('Collins','GA'),('Collins','IA'),('Collins','MO'),('Collins','MS'),('Collins','NY'),('Collins','OH'),('Collins','WI'),('Collins Center','NY'),('Collinston','LA'),('Collinston','UT'),('Collinsville','AL'),('Collinsville','CT'),('Collinsville','IL'),('Collinsville','MS'),('Collinsville','OH'),('Collinsville','OK'),('Collinsville','TX'),('Collinsville','VA'),('Collinwood','TN'),('Collison','IL'),('Collyer','KS'),('Colman','SD'),('Colmar','PA'),('Colmesneil','TX'),('Colo','IA'),('Cologne','MN'),('Cologne','NJ'),('Coloma','CA'),('Coloma','MI'),('Coloma','WI'),('Colome','SD'),('Colon','MI'),('Colon','NE'),('Colona','IL'),('Colonia','NJ'),('Colonial Beach','VA'),('Colonial Heights','VA'),('Colony','KS'),('Colony','OK'),('Colora','MD'),('Colorado City','AZ'),('Colorado City','CO'),('Colorado City','TX'),('Colorado Springs','CO'),('Colp','IL'),('Colquitt','GA'),('Colrain','MA'),('Colstrip','MT'),('Colt','AR'),('Colton','CA'),('Colton','NY'),('Colton','OH'),('Colton','OR'),('Colton','SD'),('Colton','WA'),('Coltons Point','MD'),('Colts Neck','NJ'),('Columbia','AL'),('Columbia','CA'),('Columbia','CT'),('Columbia','IA'),('Columbia','IL'),('Columbia','KY'),('Columbia','LA'),('Columbia','MD'),('Columbia','MO'),('Columbia','MS'),('Columbia','NC'),('Columbia','NJ'),('Columbia','PA'),('Columbia','SC'),('Columbia','SD'),('Columbia','TN'),('Columbia','VA'),('Columbia City','IN'),('Columbia City','OR'),('Columbia Cross Roads','PA'),('Columbia Falls','ME'),('Columbia Falls','MT'),('Columbia Station','OH'),('Columbiana','AL'),('Columbiana','OH'),('Columbiaville','MI'),('Columbiaville','NY'),('Columbus','AR'),('Columbus','GA'),('Columbus','IN'),('Columbus','KS'),('Columbus','KY'),('Columbus','MI'),('Columbus','MS'),('Columbus','MT'),('Columbus','NC'),('Columbus','ND'),('Columbus','NE'),('Columbus','NJ'),('Columbus','NM'),('Columbus','OH'),('Columbus','PA'),('Columbus','TX'),('Columbus','WI'),('Columbus City','IA'),('Columbus Grove','OH'),('Columbus Junction','IA'),('Colusa','CA'),('Colusa','IL'),('Colver','PA'),('Colville','WA'),('Colwell','IA'),('Colwich','KS'),('Comanche','OK'),('Comanche','TX'),('Combes','TX'),('Combined Locks','WI'),('Combs','AR'),('Combs','KY'),('Comer','GA'),('Comerio','PR'),('Comfort','NC'),('Comfort','TX'),('Comfort','WV'),('Comfrey','MN'),('Comins','MI'),('Commack','NY'),('Commerce','GA'),('Commerce','MO'),('Commerce','OK'),('Commerce','TX'),('Commerce City','CO'),('Commerce Township','MI'),('Commercial Point','OH'),('Commiskey','IN'),('Commodore','PA'),('Como','CO'),('Como','MS'),('Como','NC'),('Como','TN'),('Como','TX'),('Comptche','CA'),('Compton','AR'),('Compton','CA'),('Compton','IL'),('Compton','MD'),('Comstock','MI'),('Comstock','MN'),('Comstock','NE'),('Comstock','NY'),('Comstock','TX'),('Comstock','WI'),('Comstock Park','MI'),('Conasauga','TN'),('Concan','TX'),('Concepcion','TX'),('Conception','MO'),('Conception Junction','MO'),('Conchas Dam','NM'),('Concho','AZ'),('Concho','OK'),('Conconully','WA'),('Concord','AR'),('Concord','CA'),('Concord','GA'),('Concord','IL'),('Concord','MA'),('Concord','MI'),('Concord','NC'),('Concord','NE'),('Concord','NH'),('Concord','PA'),('Concord','TX'),('Concord','VA'),('Concord','VT'),('Concordia','KS'),('Concordia','MO'),('Concordville','PA'),('Concrete','WA'),('Conda','ID'),('Conde','SD'),('Condon','MT'),('Condon','OR'),('Conehatta','MS'),('Conejos','CO'),('Conestee','SC'),('Conestoga','PA'),('Conesus','NY'),('Conesville','IA'),('Conesville','OH'),('Conetoe','NC'),('Conewango Valley','NY'),('Confluence','PA'),('Conger','MN'),('Congers','NY'),('Congerville','IL'),('Congress','AZ'),('Conifer','CO'),('Conklin','MI'),('Conklin','NY'),('Conley','GA'),('Conneaut','OH'),('Conneaut Lake','PA'),('Conneautville','PA'),('Connell','WA'),('Connellsville','PA'),('Connelly','NY'),('Connellys Springs','NC'),('Conner','MT'),('Connersville','IN'),('Connerville','OK'),('Connoquenessing','PA'),('Conover','NC'),('Conover','OH'),('Conover','WI'),('Conowingo','MD'),('Conrad','IA'),('Conrad','MT'),('Conrath','WI'),('Conroe','TX'),('Conroy','IA'),('Conshohocken','PA'),('Constable','NY'),('Constableville','NY'),('Constantia','NY'),('Constantine','MI'),('Continental','OH'),('Continental Divide','NM'),('Contoocook','NH'),('Convent','LA'),('Converse','IN'),('Converse','LA'),('Converse','SC'),('Converse','TX'),('Convoy','OH'),('Conway','AR'),('Conway','MA'),('Conway','MI'),('Conway','MO'),('Conway','NC'),('Conway','NH'),('Conway','PA'),('Conway','SC'),('Conway','WA'),('Conway Springs','KS'),('Conyers','GA'),('Conyngham','PA'),('Cook','MN'),('Cook','MO'),('Cook','NE'),('Cook Springs','AL'),('Cooke City','MT'),('Cookeville','TN'),('Cooks','MI'),('Cooksburg','PA'),('Cookson','OK'),('Cookstown','NJ'),('Cooksville','IL'),('Cooksville','MD'),('Cookville','TX'),('Cool','CA'),('Cool Ridge','WV'),('Cooleemee','NC'),('Coolidge','AZ'),('Coolidge','GA'),('Coolidge','KS'),('Coolidge','TX'),('Coolin','ID'),('Coolspring','PA'),('Coolville','OH'),('Coon Rapids','IA'),('Coon Valley','WI'),('Cooper','IA'),('Cooper','TX'),('Cooper Landing','AK'),('Coopers Mills','ME'),('Coopers Plains','NY'),('Coopersburg','PA'),('Cooperstown','ND'),('Cooperstown','NY'),('Cooperstown','PA'),('Coopersville','MI'),('Coos Bay','OR'),('Coosa','GA'),('Coosada','AL'),('Coosawhatchie','SC'),('Cooter','MO'),('Copake','NY'),('Copake Falls','NY'),('Copalis Beach','WA'),('Copalis Crossing','WA'),('Copan','OK'),('Cope','CO'),('Cope','SC'),('Copeland','FL'),('Copeland','KS'),('Copemish','MI'),('Copen','WV'),('Copenhagen','NY'),('Copeville','TX'),('Copiague','NY'),('Coplay','PA'),('Coppell','TX'),('Copper Center','AK'),('Copper City','MI'),('Copper Harbor','MI'),('Copper Hill','VA'),('Copperas Cove','TX'),('Copperhill','TN'),('Copperopolis','CA'),('Coquille','OR'),('Cora','WV'),('Cora','WY'),('Coral','MI'),('Coral','PA'),('Coralville','IA'),('Coram','MT'),('Coram','NY'),('Coraopolis','PA'),('Corapeake','NC'),('Corbett','OR'),('Corbettsville','NY'),('Corbin','KY'),('Corbin','VA'),('Corcoran','CA'),('Cord','AR'),('Cordele','GA'),('Cordell','OK'),('Corder','MO'),('Cordesville','SC'),('Cordova','AK'),('Cordova','AL'),('Cordova','IL'),('Cordova','MD'),('Cordova','NC'),('Cordova','NE'),('Cordova','NM'),('Cordova','SC'),('Cordova','TN'),('Corea','ME'),('Corfu','NY'),('Corinna','ME'),('Corinne','UT'),('Corinne','WV'),('Corinth','KY'),('Corinth','ME'),('Corinth','MS'),('Corinth','NY'),('Corinth','VT'),('Corn','OK'),('Cornelia','GA'),('Cornelius','NC'),('Cornelius','OR'),('Cornell','IL'),('Cornell','MI'),('Cornell','WI'),('Cornersville','TN'),('Cornettsville','KY'),('Corning','AR'),('Corning','CA'),('Corning','IA'),('Corning','KS'),('Corning','NY'),('Corning','OH'),('Cornish','ME'),('Cornish','NH'),('Cornish','UT'),('Cornish Flat','NH'),('Cornland','IL'),('Cornucopia','WI'),('Cornville','AZ'),('Cornwall','CT'),('Cornwall','NY'),('Cornwall','PA'),('Cornwall Bridge','CT'),('Cornwall On Hudson','NY'),('Cornwallville','NY'),('Corolla','NC'),('Corona','CA'),('Corona','NM'),('Corona','NY'),('Corona','SD'),('Corona Del Mar','CA'),('Coronado','CA'),('Corozal','PR'),('Corpus Christi','TX'),('Corral','ID'),('Corrales','NM'),('Correctionville','IA'),('Correll','MN'),('Corrigan','TX'),('Corriganville','MD'),('Corry','PA'),('Corryton','TN'),('Corsica','PA'),('Corsica','SD'),('Corsicana','TX'),('Cortaro','AZ'),('Corte Madera','CA'),('Cortez','CO'),('Cortez','FL'),('Cortland','IL'),('Cortland','IN'),('Cortland','NE'),('Cortland','NY'),('Cortland','OH'),('Cortlandt Manor','NY'),('Corunna','IN'),('Corunna','MI'),('Corvallis','MT'),('Corvallis','OR'),('Corwith','IA'),('Cory','CO'),('Cory','IN'),('Corydon','IA'),('Corydon','IN'),('Corydon','KY'),('Cos Cob','CT'),('Cosby','MO'),('Cosby','TN'),('Coshocton','OH'),('Cosmopolis','WA'),('Cosmos','MN'),('Cossayuna','NY'),('Cost','TX'),('Costa','WV'),('Costa Mesa','CA'),('Costilla','NM'),('Cotati','CA'),('Coto Laurel','PR'),('Cotopaxi','CO'),('Cottage Grove','MN'),('Cottage Grove','OR'),('Cottage Grove','TN'),('Cottage Grove','WI'),('Cottage Hills','IL'),('Cottageville','SC'),('Cottageville','WV'),('Cottekill','NY'),('Cotter','AR'),('Cottleville','MO'),('Cotton','GA'),('Cotton','MN'),('Cotton Center','TX'),('Cotton Plant','AR'),('Cotton Valley','LA'),('Cottondale','AL'),('Cottondale','FL'),('Cottonport','LA'),('Cottonton','AL'),('Cottontown','TN'),('Cottonwood','AL'),('Cottonwood','AZ'),('Cottonwood','CA'),('Cottonwood','ID'),('Cottonwood','MN'),('Cottonwood Falls','KS'),('Cotuit','MA'),('Cotulla','TX'),('Couch','MO'),('Couderay','WI'),('Coudersport','PA'),('Cougar','WA'),('Coulee City','WA'),('Coulee Dam','WA'),('Coulter','IA'),('Coulters','PA'),('Coulterville','CA'),('Coulterville','IL'),('Counce','TN'),('Council','ID'),('Council','NC'),('Council Bluffs','IA'),('Council Grove','KS'),('Council Hill','OK'),('Counselor','NM'),('Country Club Hills','IL'),('Countyline','OK'),('Coupeville','WA'),('Coupland','TX'),('Coupon','PA'),('Courtenay','ND'),('Courtland','AL'),('Courtland','CA'),('Courtland','KS'),('Courtland','MN'),('Courtland','MS'),('Courtland','VA'),('Coushatta','LA'),('Cove','AR'),('Cove','OR'),('Cove City','NC'),('Covel','WV'),('Covelo','CA'),('Coventry','CT'),('Coventry','RI'),('Coventry','VT'),('Covert','MI'),('Covesville','VA'),('Covina','CA'),('Covington','GA'),('Covington','IN'),('Covington','KY'),('Covington','LA'),('Covington','MI'),('Covington','OH'),('Covington','OK'),('Covington','PA'),('Covington','TN'),('Covington','TX'),('Covington','VA'),('Cowan','TN'),('Cowanesque','PA'),('Cowansville','PA'),('Coward','SC'),('Cowarts','AL'),('Cowden','IL'),('Cowdrey','CO'),('Cowen','WV'),('Coweta','OK'),('Cowgill','MO'),('Cowiche','WA'),('Cowlesville','NY'),('Cowley','WY'),('Cowpens','SC'),('Coxs Creek','KY'),('Coxs Mills','WV'),('Coxsackie','NY'),('Coy','AL'),('Coy','AR'),('Coyanosa','TX'),('Coyle','OK'),('Coyote','CA'),('Coyote','NM'),('Cozad','NE'),('Crab Orchard','KY'),('Crab Orchard','NE'),('Crab Orchard','TN'),('Crab Orchard','WV'),('Crabtree','OR'),('Crabtree','PA'),('Craddockville','VA'),('Craftsbury','VT'),('Craftsbury Common','VT'),('Cragford','AL'),('Cragsmoor','NY'),('Craig','AK'),('Craig','CO'),('Craig','MO'),('Craig','NE'),('Craigmont','ID'),('Craigsville','VA'),('Craigsville','WV'),('Craigville','IN'),('Craley','PA'),('Cramerton','NC'),('Cranberry','PA'),('Cranberry Isles','ME'),('Cranberry Lake','NY'),('Cranberry Twp','PA'),('Cranbury','NJ'),('Crandall','GA'),('Crandall','IN'),('Crandall','TX'),('Crandon','WI'),('Crane','IN'),('Crane','MO'),('Crane','MT'),('Crane','OR'),('Crane','TX'),('Crane Hill','AL'),('Crane Lake','MN'),('Cranesville','PA'),('Cranfills Gap','TX'),('Cranford','NJ'),('Cranks','KY'),('Cranston','RI'),('Crapo','MD'),('Crary','ND'),('Craryville','NY'),('Crater Lake','OR'),('Crawford','CO'),('Crawford','GA'),('Crawford','MS'),('Crawford','NE'),('Crawford','OK'),('Crawford','TN'),('Crawford','TX'),('Crawford','WV'),('Crawfordsville','AR'),('Crawfordsville','IA'),('Crawfordsville','IN'),('Crawfordsville','OR'),('Crawfordville','FL'),('Crawfordville','GA'),('Crawley','WV'),('Crayne','KY'),('Creal Springs','IL'),('Cream Ridge','NJ'),('Creamery','PA'),('Creede','CO'),('Creedmoor','NC'),('Creekside','PA'),('Creighton','MO'),('Creighton','NE'),('Creighton','PA'),('Crenshaw','MS'),('Creola','AL'),('Creola','OH'),('Creole','LA'),('Cresbard','SD'),('Crescent','GA'),('Crescent','IA'),('Crescent','OK'),('Crescent','OR'),('Crescent','PA'),('Crescent City','CA'),('Crescent City','FL'),('Crescent City','IL'),('Crescent Lake','OR'),('Crescent Mills','CA'),('Crescent Valley','NV'),('Cresco','IA'),('Cresco','PA'),('Cressey','CA'),('Cresskill','NJ'),('Cresson','PA'),('Cresson','TX'),('Cressona','PA'),('Crest Hill','IL'),('Crest Park','CA'),('Crested Butte','CO'),('Crestline','CA'),('Crestline','KS'),('Crestline','OH'),('Creston','CA'),('Creston','IA'),('Creston','IL'),('Creston','NC'),('Creston','NE'),('Creston','OH'),('Creston','WA'),('Creston','WV'),('Crestone','CO'),('Crestview','FL'),('Crestwood','KY'),('Creswell','NC'),('Creswell','OR'),('Crete','IL'),('Crete','NE'),('Creve Coeur','IL'),('Crewe','VA'),('Criders','VA'),('Crimora','VA'),('Cripple Creek','CO'),('Cripple Creek','VA'),('Crisfield','MD'),('Crittenden','KY'),('Crittenden','NY'),('Critz','VA'),('Crivitz','WI'),('Crocheron','MD'),('Crocker','MO'),('Crockett','CA'),('Crockett','KY'),('Crockett','TX'),('Crockett','VA'),('Crockett Mills','TN'),('Crocketts Bluff','AR'),('Crocketville','SC'),('Crofton','KY'),('Crofton','MD'),('Crofton','NE'),('Croghan','NY'),('Cromona','KY'),('Crompond','NY'),('Cromwell','CT'),('Cromwell','IA'),('Cromwell','IN'),('Cromwell','KY'),('Cromwell','MN'),('Cromwell','OK'),('Crook','CO'),('Crooked Creek','AK'),('Crooks','SD'),('Crookston','MN'),('Crookston','NE'),('Crooksville','OH'),('Cropsey','IL'),('Cropseyville','NY'),('Cropwell','AL'),('Crosby','MN'),('Crosby','MS'),('Crosby','ND'),('Crosby','PA'),('Crosby','TX'),('Crosbyton','TX'),('Cross','SC'),('Cross Anchor','SC'),('Cross City','FL'),('Cross Fork','PA'),('Cross Hill','SC'),('Cross Junction','VA'),('Cross Plains','IN'),('Cross Plains','TN'),('Cross Plains','TX'),('Cross Plains','WI'),('Cross River','NY'),('Cross Timbers','MO'),('Cross Village','MI'),('Crossett','AR'),('Crosslake','MN'),('Crossnore','NC'),('Crossroads','NM'),('Crossville','AL'),('Crossville','IL'),('Crossville','TN'),('Croswell','MI'),('Crothersville','IN'),('Croton','OH'),('Croton Falls','NY'),('Croton On Hudson','NY'),('Crouse','NC'),('Crouseville','ME'),('Crow Agency','MT'),('Crowder','MS'),('Crowder','OK'),('Crowell','TX'),('Crowheart','WY'),('Crowley','CO'),('Crowley','LA'),('Crowley','TX'),('Crown','PA'),('Crown City','OH'),('Crown King','AZ'),('Crown Point','IN'),('Crown Point','NY'),('Crownpoint','NM'),('Crownsville','MD'),('Crows Landing','CA'),('Crowville','LA'),('Croydon','PA'),('Croydon','UT'),('Crozet','VA'),('Crozier','VA'),('Crp Christi','TX'),('Crucible','PA'),('Cruger','MS'),('Crum','WV'),('Crum Lynne','PA'),('Crump','TN'),('Crumpler','NC'),('Crumpton','MD'),('Crumrod','AR'),('Crystal','MI'),('Crystal','ND'),('Crystal Bay','MN'),('Crystal Bay','NV'),('Crystal Beach','FL'),('Crystal City','MO'),('Crystal City','TX'),('Crystal Falls','MI'),('Crystal Hill','VA'),('Crystal Lake','IA'),('Crystal Lake','IL'),('Crystal River','FL'),('Crystal Spring','PA'),('Crystal Springs','FL'),('Crystal Springs','MS'),('Cub Run','KY'),('Cuba','AL'),('Cuba','IL'),('Cuba','KS'),('Cuba','MO'),('Cuba','NM'),('Cuba','NY'),('Cuba','OH'),('Cuba City','WI'),('Cubero','NM'),('Cucumber','WV'),('Cudahy','WI'),('Cuddebackville','NY'),('Cuddy','PA'),('Cuero','TX'),('Cuervo','NM'),('Culberson','NC'),('Culbertson','MT'),('Culbertson','NE'),('Culdesac','ID'),('Culebra','PR'),('Cullen','LA'),('Cullen','VA'),('Culleoka','TN'),('Cullman','AL'),('Culloden','GA'),('Culloden','WV'),('Cullom','IL'),('Cullowhee','NC'),('Culp Creek','OR'),('Culpeper','VA'),('Culver','IN'),('Culver','OR'),('Culver City','CA'),('Cumberland','IA'),('Cumberland','KY'),('Cumberland','MD'),('Cumberland','NC'),('Cumberland','OH'),('Cumberland','RI'),('Cumberland','VA'),('Cumberland','WI'),('Cumberland Center','ME'),('Cumberland City','TN'),('Cumberland Foreside','ME'),('Cumberland Furnace','TN'),('Cumberland Gap','TN'),('Cumbola','PA'),('Cumby','TX'),('Cummaquid','MA'),('Cumming','GA'),('Cumming','IA'),('Cummings','KS'),('Cummings','ND'),('Cummington','MA'),('Cumnock','NC'),('Cuney','TX'),('Cunningham','KS'),('Cunningham','KY'),('Cunningham','TN'),('Cunningham','TX'),('Cupertino','CA'),('Curdsville','KY'),('Curlew','IA'),('Curlew','WA'),('Curllsville','PA'),('Curran','MI'),('Currie','MN'),('Currie','NC'),('Currituck','NC'),('Curryville','MO'),('Curryville','PA'),('Curtice','OH'),('Curtin','OR'),('Curtis','AR'),('Curtis','MI'),('Curtis','NE'),('Curtis','WA'),('Curtis Bay','MD'),('Curtiss','WI'),('Curtisville','PA'),('Curwensville','PA'),('Cushing','IA'),('Cushing','ME'),('Cushing','MN'),('Cushing','OK'),('Cushing','TX'),('Cushing','WI'),('Cushman','AR'),('Cusick','WA'),('Cusseta','AL'),('Cusseta','GA'),('Custar','OH'),('Custer','KY'),('Custer','MI'),('Custer','MT'),('Custer','SD'),('Custer','WA'),('Custer','WI'),('Custer City','OK'),('Custer City','PA'),('Cut Bank','MT'),('Cut Off','LA'),('Cutchogue','NY'),('Cuthbert','GA'),('Cutler','CA'),('Cutler','IL'),('Cutler','IN'),('Cutler','ME'),('Cutler','OH'),('Cutten','CA'),('Cuttingsville','VT'),('Cuttyhunk','MA'),('Cuyahoga Falls','OH'),('Cyclone','PA'),('Cyclone','WV'),('Cygnet','OH'),('Cylinder','IA'),('Cynthiana','IN'),('Cynthiana','KY'),('Cynthiana','OH'),('Cypress','CA'),('Cypress','FL'),('Cypress','IL'),('Cypress','TX'),('Cypress Inn','TN'),('Cyril','OK'),('Cyrus','MN'),('D Hanis','TX'),('D Lo','MS'),('Dacoma','OK'),('Dacono','CO'),('Dacula','GA'),('Dade City','FL'),('Dadeville','AL'),('Dadeville','MO'),('Dafter','MI'),('Daggett','CA'),('Daggett','MI'),('Dagmar','MT'),('Dagsboro','DE'),('Dagus Mines','PA'),('Dahinda','IL'),('Dahlen','ND'),('Dahlgren','IL'),('Dahlgren','VA'),('Dahlonega','GA'),('Dailey','WV'),('Daingerfield','TX'),('Dairy','OR'),('Daisetta','TX'),('Daisy','GA'),('Daisy','MO'),('Daisy','OK'),('Daisytown','PA'),('Dakota','IL'),('Dakota','MN'),('Dakota City','IA'),('Dakota City','NE'),('Dalbo','MN'),('Dale','IL'),('Dale','IN'),('Dale','NY'),('Dale','SC'),('Dale','TX'),('Dale','WI'),('Daleville','AL'),('Daleville','IN'),('Daleville','MS'),('Daleville','VA'),('Dalhart','TX'),('Dallardsville','TX'),('Dallas','GA'),('Dallas','IA'),('Dallas','NC'),('Dallas','OR'),('Dallas','PA'),('Dallas','SD'),('Dallas','TX'),('Dallas','WI'),('Dallas','WV'),('Dallas Center','IA'),('Dallas City','IL'),('Dallastown','PA'),('Dallesport','WA'),('Dalmatia','PA'),('Dalton','GA'),('Dalton','MA'),('Dalton','MN'),('Dalton','MO'),('Dalton','NE'),('Dalton','NY'),('Dalton','OH'),('Dalton','PA'),('Dalton','WI'),('Dalton City','IL'),('Daly City','CA'),('Dalzell','IL'),('Dalzell','SC'),('Damar','KS'),('Damariscotta','ME'),('Damascus','AR'),('Damascus','GA'),('Damascus','MD'),('Damascus','OH'),('Damascus','OR'),('Damascus','PA'),('Damascus','VA'),('Dameron','MD'),('Dammeron Valley','UT'),('Damon','TX'),('Dana','IA'),('Dana','IL'),('Dana','IN'),('Dana','KY'),('Dana','NC'),('Dana Point','CA'),('Danboro','PA'),('Danbury','CT'),('Danbury','IA'),('Danbury','NC'),('Danbury','NE'),('Danbury','NH'),('Danbury','TX'),('Danbury','WI'),('Danby','VT'),('Danciger','TX'),('Dandridge','TN'),('Dane','WI'),('Danese','WV'),('Danevang','TX'),('Danforth','IL'),('Danforth','ME'),('Dania','FL'),('Daniel','WY'),('Daniels','WV'),('Danielson','CT'),('Danielsville','GA'),('Danielsville','PA'),('Dannebrog','NE'),('Dannemora','NY'),('Dansville','MI'),('Dansville','NY'),('Dante','SD'),('Dante','VA'),('Danube','MN'),('Danvers','IL'),('Danvers','MA'),('Danvers','MN'),('Danville','AL'),('Danville','AR'),('Danville','CA'),('Danville','GA'),('Danville','IA'),('Danville','IL'),('Danville','IN'),('Danville','KS'),('Danville','KY'),('Danville','ME'),('Danville','NH'),('Danville','OH'),('Danville','PA'),('Danville','VA'),('Danville','VT'),('Danville','WA'),('Danville','WV'),('Daphne','AL'),('Darby','MT'),('Darby','PA'),('Dardanelle','AR'),('Dardanelle','CA'),('Darden','TN'),('Darfur','MN'),('Darien','CT'),('Darien','GA'),('Darien','IL'),('Darien','WI'),('Darien Center','NY'),('Darling','MS'),('Darlington','IN'),('Darlington','MD'),('Darlington','MO'),('Darlington','PA'),('Darlington','SC'),('Darlington','WI'),('Darragh','PA'),('Darrington','WA'),('Darrouzett','TX'),('Darrow','LA'),('Dartmouth','MA'),('Darwin','CA'),('Darwin','MN'),('Dassel','MN'),('Dateland','AZ'),('Datil','NM'),('Datto','AR'),('Daufuskie Island','SC'),('Dauphin','PA'),('Dauphin Island','AL'),('Davenport','CA'),('Davenport','FL'),('Davenport','IA'),('Davenport','ND'),('Davenport','NE'),('Davenport','NY'),('Davenport','OK'),('Davenport','VA'),('Davenport','WA'),('Davenport Center','NY'),('Davey','NE'),('David','KY'),('David City','NE'),('Davidson','NC'),('Davidson','OK'),('Davidsonville','MD'),('Davidsville','PA'),('Davilla','TX'),('Davin','WV'),('Davis','CA'),('Davis','IL'),('Davis','NC'),('Davis','OK'),('Davis','SD'),('Davis','WV'),('Davis City','IA'),('Davis Creek','CA'),('Davis Junction','IL'),('Davis Station','SC'),('Davis Wharf','VA'),('Davisboro','GA'),('Davisburg','MI'),('Davison','MI'),('Daviston','AL'),('Davisville','MO'),('Davisville','WV'),('Davy','WV'),('Dawes','WV'),('Dawn','MO'),('Dawn','TX'),('Dawson','AL'),('Dawson','GA'),('Dawson','IA'),('Dawson','IL'),('Dawson','MN'),('Dawson','ND'),('Dawson','NE'),('Dawson','PA'),('Dawson','TX'),('Dawson Springs','KY'),('Dawsonville','GA'),('Day','FL'),('Dayhoit','KY'),('Daykin','NE'),('Days Creek','OR'),('Dayton','IA'),('Dayton','ID'),('Dayton','IN'),('Dayton','KY'),('Dayton','MD'),('Dayton','MN'),('Dayton','MT'),('Dayton','NJ'),('Dayton','NV'),('Dayton','NY'),('Dayton','OH'),('Dayton','OR'),('Dayton','PA'),('Dayton','TN'),('Dayton','TX'),('Dayton','VA'),('Dayton','WA'),('Dayton','WY'),('Daytona Beach','FL'),('Dayville','CT'),('Dayville','OR'),('Dazey','ND'),('De Armanville','AL'),('De Beque','CO'),('De Berry','TX'),('De Borgia','MT'),('De Forest','WI'),('De Graff','OH'),('De Kalb','MO'),('De Kalb','MS'),('De Kalb','TX'),('De Kalb Junction','NY'),('De Lancey','PA'),('De Land','IL'),('De Leon','TX'),('De Leon Springs','FL'),('De Mossville','KY'),('De Pere','WI'),('De Peyster','NY'),('De Queen','AR'),('De Ruyter','NY'),('De Smet','SD'),('De Soto','GA'),('De Soto','IA'),('De Soto','IL'),('De Soto','KS'),('De Soto','MO'),('De Soto','WI'),('De Tour Village','MI'),('De Valls Bluff','AR'),('De Witt','AR'),('De Witt','IA'),('De Witt','MO'),('De Witt','NE'),('De Young','PA'),('Deadwood','OR'),('Deadwood','SD'),('Deal','NJ'),('Deal Island','MD'),('Deale','MD'),('Deane','KY'),('Deansboro','NY'),('Deanville','TX'),('Dearborn','MI'),('Dearborn','MO'),('Dearborn Heights','MI'),('Dearing','GA'),('Dearing','KS'),('Deary','ID'),('Death Valley','CA'),('Deatsville','AL'),('Deaver','WY'),('Debary','FL'),('Debord','KY'),('Decatur','AL'),('Decatur','AR'),('Decatur','GA'),('Decatur','IA'),('Decatur','IL'),('Decatur','IN'),('Decatur','MI'),('Decatur','MS'),('Decatur','NE'),('Decatur','OH'),('Decatur','TN'),('Decatur','TX'),('Decaturville','TN'),('Decherd','TN'),('Decker','IN'),('Decker','MI'),('Decker','MT'),('Deckerville','MI'),('Declo','ID'),('Decorah','IA'),('Dedham','IA'),('Dedham','MA'),('Deedsville','IN'),('Deep Gap','NC'),('Deep River','CT'),('Deep River','IA'),('Deep Run','NC'),('Deep Water','WV'),('Deepwater','MO'),('Deepwater','NJ'),('Deer','AR'),('Deer Creek','IL'),('Deer Creek','MN'),('Deer Creek','OK'),('Deer Grove','IL'),('Deer Harbor','WA'),('Deer Island','OR'),('Deer Isle','ME'),('Deer Lodge','MT'),('Deer Lodge','TN'),('Deer Park','AL'),('Deer Park','CA'),('Deer Park','NY'),('Deer Park','TX'),('Deer Park','WA'),('Deer Park','WI'),('Deer River','MN'),('Deer River','NY'),('Deer Trail','CO'),('Deerbrook','WI'),('Deerfield','IL'),('Deerfield','KS'),('Deerfield','MA'),('Deerfield','MI'),('Deerfield','MO'),('Deerfield','NH'),('Deerfield','OH'),('Deerfield','VA'),('Deerfield','WI'),('Deerfield Beach','FL'),('Deerfield Street','NJ'),('Deering','AK'),('Deering','MO'),('Deering','ND'),('Deersville','OH'),('Deerton','MI'),('Deerwood','MN'),('Deeth','NV'),('Deferiet','NY'),('Defiance','IA'),('Defiance','MO'),('Defiance','OH'),('Defiance','PA'),('Deford','MI'),('Defuniak Springs','FL'),('Dekalb','IL'),('Del Mar','CA'),('Del Norte','CO'),('Del Rey','CA'),('Del Rio','TN'),('Del Rio','TX'),('Del Valle','TX'),('Delafield','WI'),('Delancey','NY'),('Deland','FL'),('Delano','CA'),('Delano','MN'),('Delano','PA'),('Delano','TN'),('Delanson','NY'),('Delaplaine','AR'),('Delaplane','VA'),('Delavan','IL'),('Delavan','MN'),('Delavan','WI'),('Delaware','AR'),('Delaware','IA'),('Delaware','NJ'),('Delaware','OH'),('Delaware','OK'),('Delaware City','DE'),('Delaware Water Gap','PA'),('Delbarton','WV'),('Delcambre','LA'),('Delco','NC'),('Delevan','NY'),('Delhi','CA'),('Delhi','IA'),('Delhi','LA'),('Delhi','NY'),('Delia','KS'),('Delight','AR'),('Dell','AR'),('Dell','MT'),('Dell City','TX'),('Dell Rapids','SD'),('Dellrose','TN'),('Dellroy','OH'),('Dellslow','WV'),('Dellwood','WI'),('Delmar','AL'),('Delmar','DE'),('Delmar','IA'),('Delmar','MD'),('Delmar','NY'),('Delmita','TX'),('Delmont','NJ'),('Delmont','PA'),('Delmont','SD'),('Deloit','IA'),('Delong','IN'),('Delphi','IN'),('Delphi Falls','NY'),('Delphia','KY'),('Delphos','KS'),('Delphos','OH'),('Delran','NJ'),('Delray','WV'),('Delray Beach','FL'),('Delta','AL'),('Delta','CO'),('Delta','IA'),('Delta','LA'),('Delta','MO'),('Delta','OH'),('Delta','PA'),('Delta','UT'),('Delta City','MS'),('Delta Junction','AK'),('Deltaville','VA'),('Delton','MI'),('Deltona','FL'),('Dema','KY'),('Demarest','NJ'),('Deming','NM'),('Deming','WA'),('Demopolis','AL'),('Demorest','GA'),('Demotte','IN'),('Denair','CA'),('Denali National Park','AK'),('Denbo','PA'),('Dendron','VA'),('Denham Springs','LA'),('Denhoff','ND'),('Denio','NV'),('Denison','IA'),('Denison','KS'),('Denison','TX'),('Denmark','IA'),('Denmark','ME'),('Denmark','NY'),('Denmark','SC'),('Denmark','TN'),('Denmark','WI'),('Dennard','AR'),('Dennehotso','AZ'),('Dennis','KS'),('Dennis','MA'),('Dennis','MS'),('Dennis','TX'),('Dennis Port','MA'),('Dennison','IL'),('Dennison','MN'),('Dennison','OH'),('Denniston','KY'),('Dennisville','NJ'),('Dennysville','ME'),('Dent','MN'),('Denton','GA'),('Denton','KS'),('Denton','KY'),('Denton','MD'),('Denton','MT'),('Denton','NC'),('Denton','NE'),('Denton','TX'),('Denver','CO'),('Denver','IA'),('Denver','IN'),('Denver','MO'),('Denver','NC'),('Denver','NY'),('Denver','PA'),('Denver City','TX'),('Denville','NJ'),('Depauville','NY'),('Depauw','IN'),('Depew','NY'),('Depew','OK'),('Depoe Bay','OR'),('Deport','TX'),('Deposit','NY'),('Depue','IL'),('Deputy','IN'),('Dequincy','LA'),('Derby','CT'),('Derby','IA'),('Derby','IN'),('Derby','KS'),('Derby','NY'),('Derby','OH'),('Derby','VT'),('Derby Line','VT'),('Deridder','LA'),('Derma','MS'),('Dermott','AR'),('Derrick City','PA'),('Derry','NH'),('Derry','NM'),('Derry','PA'),('Derwent','OH'),('Derwood','MD'),('Des Allemands','LA'),('Des Arc','AR'),('Des Arc','MO'),('Des Lacs','ND'),('Des Moines','IA'),('Des Moines','NM'),('Des Plaines','IL'),('Descanso','CA'),('Desdemona','TX'),('Desert Center','CA'),('Desert Hot Springs','CA'),('Desha','AR'),('Deshler','NE'),('Deshler','OH'),('Desmet','ID'),('Desoto','TX'),('Destin','FL'),('Destrehan','LA'),('Detroit','AL'),('Detroit','ME'),('Detroit','MI'),('Detroit','OR'),('Detroit','TX'),('Detroit Lakes','MN'),('Devault','PA'),('Devens','MA'),('Devers','TX'),('Deville','LA'),('Devils Elbow','MO'),('Devils Lake','ND'),('Devils Tower','WY'),('Devine','TX'),('Devol','OK'),('Devon','PA'),('Dewar','IA'),('Dewar','OK'),('Dewart','PA'),('Deweese','NE'),('Dewey','AZ'),('Dewey','IL'),('Dewey','OK'),('Deweyville','TX'),('Deweyville','UT'),('Dewitt','IL'),('Dewitt','KY'),('Dewitt','MI'),('Dewitt','VA'),('Dewittville','NY'),('Dewy Rose','GA'),('Dexter','GA'),('Dexter','IA'),('Dexter','KS'),('Dexter','KY'),('Dexter','ME'),('Dexter','MI'),('Dexter','MN'),('Dexter','MO'),('Dexter','NM'),('Dexter','NY'),('Dexter','OR'),('Dexter City','OH'),('Diablo','CA'),('Diagonal','IA'),('Diamond','MO'),('Diamond','OH'),('Diamond','OR'),('Diamond Bar','CA'),('Diamond City','AR'),('Diamond Point','NY'),('Diamond Springs','CA'),('Diamondhead','MS'),('Diamondville','WY'),('Diana','TX'),('Diana','WV'),('Diaz','AR'),('Dibble','OK'),('Diberville','MS'),('Diboll','TX'),('Dice','KY'),('Dickens','IA'),('Dickens','NE'),('Dickens','TX'),('Dickerson','MD'),('Dickerson Run','PA'),('Dickey','ND'),('Dickeyville','WI'),('Dickinson','AL'),('Dickinson','ND'),('Dickinson','TX'),('Dickinson Center','NY'),('Dickson','TN'),('Dickson City','PA'),('Dierks','AR'),('Dieterich','IL'),('Dietrich','ID'),('Diggins','MO'),('Diggs','VA'),('Dighton','KS'),('Dighton','MA'),('Dike','IA'),('Dike','TX'),('Dill City','OK'),('Dillard','GA'),('Dillard','OR'),('Dille','WV'),('Diller','NE'),('Dilley','TX'),('Dilliner','PA'),('Dillingham','AK'),('Dillon','CO'),('Dillon','MT'),('Dillon','SC'),('Dillon Beach','CA'),('Dillonvale','OH'),('Dillsboro','IN'),('Dillsboro','NC'),('Dillsburg','PA'),('Dilltown','PA'),('Dillwyn','VA'),('Dilworth','MN'),('Dime Box','TX'),('Dimmitt','TX'),('Dimock','PA'),('Dimock','SD'),('Dimondale','MI'),('Dinero','TX'),('Dingess','WV'),('Dingle','ID'),('Dingmans Ferry','PA'),('Dinosaur','CO'),('Dinuba','CA'),('Dinwiddie','VA'),('Discovery Bay','CA'),('Disney','OK'),('Disputanta','VA'),('Distant','PA'),('District Heights','MD'),('Dittmer','MO'),('Divernon','IL'),('Divide','CO'),('Divide','MT'),('Dividing Creek','NJ'),('Dix','IL'),('Dix','NE'),('Dixfield','ME'),('Dixie','GA'),('Dixie','WA'),('Dixie','WV'),('Dixmont','ME'),('Dixon','CA'),('Dixon','IA'),('Dixon','IL'),('Dixon','KY'),('Dixon','MO'),('Dixon','MT'),('Dixon','NE'),('Dixon','NM'),('Dixon','WY'),('Dixon Springs','TN'),('Dixons Mills','AL'),('Dixonville','PA'),('Dobbin','TX'),('Dobbins','CA'),('Dobbs Ferry','NY'),('Dobson','NC'),('Docena','AL'),('Doctors Inlet','FL'),('Dodd City','TX'),('Doddridge','AR'),('Doddsville','MS'),('Dodge','ND'),('Dodge','NE'),('Dodge','TX'),('Dodge','WI'),('Dodge Center','MN'),('Dodge City','KS'),('Dodgeville','MI'),('Dodgeville','WI'),('Dodson','LA'),('Dodson','MT'),('Dodson','TX'),('Doe Hill','VA'),('Doe Run','MO'),('Doerun','GA'),('Dogue','VA'),('Dola','OH'),('Dolan Springs','AZ'),('Doland','SD'),('Dolgeville','NY'),('Dollar Bay','MI'),('Dolliver','IA'),('Dolomite','AL'),('Dolores','CO'),('Dolph','AR'),('Dolphin','VA'),('Dolton','IL'),('Dona Ana','NM'),('Donahue','IA'),('Donald','OR'),('Donalds','SC'),('Donaldson','AR'),('Donaldson','IN'),('Donaldson','MN'),('Donaldsonville','LA'),('Donalsonville','GA'),('Donegal','PA'),('Dongola','IL'),('Donie','TX'),('Doniphan','MO'),('Doniphan','NE'),('Donna','TX'),('Donnellson','IA'),('Donnellson','IL'),('Donnelly','ID'),('Donnelly','MN'),('Donnelsville','OH'),('Donner','LA'),('Donnybrook','ND'),('Donora','PA'),('Donovan','IL'),('Doole','TX'),('Doon','IA'),('Dora','AL'),('Dora','MO'),('Dora','NM'),('Dorado','PR'),('Doran','VA'),('Dorchester','IA'),('Dorchester','NE'),('Dorchester','NJ'),('Dorchester','SC'),('Dorchester','WI'),('Dorena','OR'),('Dormansville','NY'),('Dornsife','PA'),('Dorothy','NJ'),('Dorothy','WV'),('Dorr','MI'),('Dorrance','KS'),('Dorris','CA'),('Dorset','OH'),('Dorset','VT'),('Dorsey','IL'),('Dorton','KY'),('Dos Palos','CA'),('Dos Rios','CA'),('Doss','TX'),('Doswell','VA'),('Dothan','AL'),('Dothan','WV'),('Doty','WA'),('Double Springs','AL'),('Doucette','TX'),('Douds','IA'),('Dougherty','IA'),('Dougherty','OK'),('Dougherty','TX'),('Douglas','AK'),('Douglas','AL'),('Douglas','AZ'),('Douglas','GA'),('Douglas','MA'),('Douglas','MI'),('Douglas','ND'),('Douglas','NE'),('Douglas','OK'),('Douglas','WY'),('Douglas City','CA'),('Douglas Flat','CA'),('Douglass','KS'),('Douglass','TX'),('Douglassville','PA'),('Douglassville','TX'),('Douglasville','GA'),('Dousman','WI'),('Dove Creek','CO'),('Dover','AR'),('Dover','DE'),('Dover','FL'),('Dover','GA'),('Dover','ID'),('Dover','IL'),('Dover','KS'),('Dover','KY'),('Dover','MA'),('Dover','MN'),('Dover','MO'),('Dover','NC'),('Dover','NH'),('Dover','NJ'),('Dover','OH'),('Dover','OK'),('Dover','PA'),('Dover','TN'),('Dover Afb','DE'),('Dover Foxcroft','ME'),('Dover Plains','NY'),('Dovray','MN'),('Dow','IL'),('Dow City','IA'),('Dowagiac','MI'),('Dowell','IL'),('Dowell','MD'),('Dowelltown','TN'),('Dowling','MI'),('Downers Grove','IL'),('Downey','CA'),('Downey','ID'),('Downieville','CA'),('Downing','MO'),('Downing','WI'),('Downingtown','PA'),('Downs','IL'),('Downs','KS'),('Downsville','LA'),('Downsville','NY'),('Downsville','WI'),('Dows','IA'),('Doyle','CA'),('Doyle','TN'),('Doylesburg','PA'),('Doylestown','OH'),('Doylestown','PA'),('Doylestown','WI'),('Doyline','LA'),('Dozier','AL'),('Dracut','MA'),('Dragoon','AZ'),('Drain','OR'),('Drake','CO'),('Drake','KY'),('Drake','ND'),('Drakes Branch','VA'),('Drakesboro','KY'),('Drakesville','IA'),('Draper','SD'),('Draper','UT'),('Draper','VA'),('Drasco','AR'),('Dravosburg','PA'),('Drayden','MD'),('Drayton','ND'),('Drayton','SC'),('Drayton Plains','MI'),('Drennen','WV'),('Dresden','KS'),('Dresden','ME'),('Dresden','NY'),('Dresden','OH'),('Dresden','TN'),('Dresher','PA'),('Dresser','WI'),('Drew','MS'),('Drewryville','VA'),('Drewsey','OR'),('Drewsville','NH'),('Drexel','MO'),('Drexel','NC'),('Drexel Hill','PA'),('Drift','KY'),('Drifting','PA'),('Drifton','PA'),('Driftwood','PA'),('Driftwood','TX'),('Driggs','ID'),('Dripping Springs','TX'),('Driscoll','ND'),('Driscoll','TX'),('Driver','AR'),('Drummond','MT'),('Drummond','OK'),('Drummond','WI'),('Drummond Island','MI'),('Drummonds','TN'),('Drumore','PA'),('Drumright','OK'),('Drums','PA'),('Drury','MA'),('Drury','MO'),('Dry Branch','GA'),('Dry Creek','LA'),('Dry Creek','WV'),('Dry Fork','VA'),('Dry Prong','LA'),('Dry Ridge','KY'),('Dry Run','PA'),('Drybranch','WV'),('Dryden','ME'),('Dryden','MI'),('Dryden','NY'),('Dryden','TX'),('Dryden','VA'),('Dryden','WA'),('Dryfork','WV'),('Drytown','CA'),('Du Bois','IL'),('Du Bois','NE'),('Du Bois','PA'),('Du Pont','GA'),('Du Quoin','IL'),('Duanesburg','NY'),('Duarte','CA'),('Dubach','LA'),('Dubberly','LA'),('Dublin','CA'),('Dublin','GA'),('Dublin','IN'),('Dublin','MS'),('Dublin','NC'),('Dublin','NH'),('Dublin','OH'),('Dublin','PA'),('Dublin','TX'),('Dublin','VA'),('Dubois','ID'),('Dubois','IN'),('Dubois','WY'),('Dubre','KY'),('Dubuque','IA'),('Duchesne','UT'),('Duck','WV'),('Duck Creek Village','UT'),('Duck Hill','MS'),('Duck River','TN'),('Ducktown','TN'),('Duckwater','NV'),('Ducor','CA'),('Dudley','GA'),('Dudley','MA'),('Dudley','MO'),('Dudley','NC'),('Dudley','PA'),('Due West','SC'),('Duenweg','MO'),('Duff','TN'),('Duffield','VA'),('Dufur','OR'),('Dugger','IN'),('Dugspur','VA'),('Dugway','UT'),('Duke','MO'),('Duke','OK'),('Duke Center','PA'),('Dukedom','TN'),('Dulac','LA'),('Dulce','NM'),('Dulles','VA'),('Duluth','GA'),('Duluth','MN'),('Dulzura','CA'),('Dumas','AR'),('Dumas','MS'),('Dumas','TX'),('Dumfries','VA'),('Dumont','CO'),('Dumont','IA'),('Dumont','MN'),('Dumont','NJ'),('Dunbar','KY'),('Dunbar','NE'),('Dunbar','PA'),('Dunbar','WI'),('Dunbar','WV'),('Dunbarton','NH'),('Dunbridge','OH'),('Duncan','AZ'),('Duncan','MS'),('Duncan','NE'),('Duncan','OK'),('Duncan','SC'),('Duncan Falls','OH'),('Duncannon','PA'),('Duncans Mills','CA'),('Duncansville','PA'),('Duncanville','AL'),('Duncanville','TX'),('Duncombe','IA'),('Dundalk','MD'),('Dundas','IL'),('Dundas','MN'),('Dundas','VA'),('Dundee','FL'),('Dundee','IA'),('Dundee','IL'),('Dundee','KY'),('Dundee','MI'),('Dundee','MS'),('Dundee','NY'),('Dundee','OH'),('Dundee','OR'),('Dunedin','FL'),('Dunellen','NJ'),('Dunfermline','IL'),('Dungannon','VA'),('Dunkerton','IA'),('Dunkirk','IN'),('Dunkirk','MD'),('Dunkirk','NY'),('Dunkirk','OH'),('Dunlap','CA'),('Dunlap','IA'),('Dunlap','IL'),('Dunlap','TN'),('Dunlevy','PA'),('Dunlo','PA'),('Dunlow','WV'),('Dunmor','KY'),('Dunmore','WV'),('Dunn','NC'),('Dunn','TX'),('Dunn Center','ND'),('Dunn Loring','VA'),('Dunnegan','MO'),('Dunnell','MN'),('Dunnellon','FL'),('Dunnigan','CA'),('Dunning','NE'),('Dunnsville','VA'),('Dunnville','KY'),('Dunreith','IN'),('Dunseith','ND'),('Dunsmuir','CA'),('Dunstable','MA'),('Duplessis','LA'),('Dupo','IL'),('Dupont','CO'),('Dupont','IN'),('Dupont','LA'),('Dupont','OH'),('Dupont','WA'),('Dupree','SD'),('Dupuyer','MT'),('Duquesne','PA'),('Durand','IL'),('Durand','MI'),('Durand','WI'),('Durango','CO'),('Durango','IA'),('Durant','FL'),('Durant','IA'),('Durant','MS'),('Durant','OK'),('Durants Neck','NC'),('Durbin','WV'),('Durham','CA'),('Durham','CT'),('Durham','KS'),('Durham','ME'),('Durham','MO'),('Durham','NC'),('Durham','NH'),('Durham','NY'),('Durham','OK'),('Durham','PA'),('Durhamville','NY'),('Durkee','OR'),('Duryea','PA'),('Dushore','PA'),('Duson','LA'),('Dustin','OK'),('Dutch Flat','CA'),('Dutch Harbor','AK'),('Dutch John','UT'),('Dutchtown','MO'),('Dutton','AL'),('Dutton','MT'),('Dutton','VA'),('Dutzow','MO'),('Duvall','WA'),('Duxbury','MA'),('Dwale','KY'),('Dwarf','KY'),('Dwight','IL'),('Dwight','KS'),('Dwight','NE'),('Dycusburg','KY'),('Dyer','AR'),('Dyer','IN'),('Dyer','NV'),('Dyer','TN'),('Dyersburg','TN'),('Dyersville','IA'),('Dyess','AR'),('Dyess Afb','TX'),('Dyke','VA'),('Dysart','IA'),('Dysart','PA'),('Eads','CO'),('Eads','TN'),('Eagan','TN'),('Eagar','AZ'),('Eagarville','IL'),('Eagle','AK'),('Eagle','CO'),('Eagle','ID'),('Eagle','MI'),('Eagle','NE'),('Eagle','WI'),('Eagle Bay','NY'),('Eagle Bend','MN'),('Eagle Bridge','NY'),('Eagle Butte','SD'),('Eagle Creek','OR'),('Eagle Grove','IA'),('Eagle Lake','FL'),('Eagle Lake','ME'),('Eagle Lake','MN'),('Eagle Lake','TX'),('Eagle Mountain','UT'),('Eagle Nest','NM'),('Eagle Pass','TX'),('Eagle Point','OR'),('Eagle River','AK'),('Eagle River','WI'),('Eagle Rock','MO'),('Eagle Rock','VA'),('Eagle Springs','NC'),('Eagles Mere','PA'),('Eagletown','OK'),('Eagleville','CA'),('Eagleville','MO'),('Eagleville','PA'),('Eagleville','TN'),('Eakly','OK'),('Earl','NC'),('Earl Park','IN'),('Earle','AR'),('Earleton','FL'),('Earleville','MD'),('Earlham','IA'),('Earlimart','CA'),('Earling','IA'),('Earlington','KY'),('Earlington','PA'),('Earlsboro','OK'),('Earlton','NY'),('Earlville','IA'),('Earlville','IL'),('Earlville','NY'),('Earlville','PA'),('Early','IA'),('Early','TX'),('Early Branch','SC'),('Earlysville','VA'),('Earp','CA'),('Earth','TX'),('Earth City','MO'),('Easley','SC'),('East Alton','IL'),('East Amherst','NY'),('East Andover','ME'),('East Andover','NH'),('East Arlington','VT'),('East Aurora','NY'),('East Baldwin','ME'),('East Bank','WV'),('East Barre','VT'),('East Bend','NC'),('East Berkshire','VT'),('East Berlin','CT'),('East Berlin','PA'),('East Bernard','TX'),('East Berne','NY'),('East Bernstadt','KY'),('East Bethany','NY'),('East Bloomfield','NY'),('East Blue Hill','ME'),('East Boothbay','ME'),('East Boston','MA'),('East Brady','PA'),('East Branch','NY'),('East Bridgewater','MA'),('East Brookfield','MA'),('East Brunswick','NJ'),('East Burke','VT'),('East Butler','PA'),('East Calais','VT'),('East Canaan','CT'),('East Candia','NH'),('East Canton','OH'),('East Carbon','UT'),('East Carondelet','IL'),('East Charleston','VT'),('East Chatham','NY'),('East Chicago','IN'),('East China','MI'),('East Claridon','OH'),('East Concord','NY'),('East Corinth','VT'),('East Dennis','MA'),('East Derry','NH'),('East Dixfield','ME'),('East Dorset','VT'),('East Dover','VT'),('East Dublin','GA'),('East Dubuque','IL'),('East Durham','NY'),('East Earl','PA'),('East Ellijay','GA'),('East Ellsworth','WI'),('East Elmhurst','NY'),('East Enterprise','IN'),('East Fairfield','VT'),('East Falmouth','MA'),('East Flat Rock','NC'),('East Freedom','PA'),('East Freetown','MA'),('East Fultonham','OH'),('East Galesburg','IL'),('East Glacier Park','MT'),('East Glastonbury','CT'),('East Granby','CT'),('East Grand Forks','MN'),('East Greenbush','NY'),('East Greenville','PA'),('East Greenwich','RI'),('East Haddam','CT'),('East Hampstead','NH'),('East Hampton','CT'),('East Hampton','NY'),('East Hanover','NJ'),('East Hardwick','VT'),('East Hartford','CT'),('East Hartland','CT'),('East Haven','CT'),('East Haven','VT'),('East Helena','MT'),('East Hickory','PA'),('East Homer','NY'),('East Irvine','CA'),('East Islip','NY'),('East Jewett','NY'),('East Jordan','MI'),('East Killingly','CT'),('East Lansing','MI'),('East Leroy','MI'),('East Liberty','OH'),('East Livermore','ME'),('East Liverpool','OH'),('East Longmeadow','MA'),('East Lyme','CT'),('East Lynn','IL'),('East Lynn','WV'),('East Lynne','MO'),('East Machias','ME'),('East Mansfield','MA'),('East Marion','NY'),('East Mc Keesport','PA'),('East Meadow','NY'),('East Meredith','NY'),('East Middlebury','VT'),('East Millinocket','ME'),('East Millsboro','PA'),('East Moline','IL'),('East Montpelier','VT'),('East Moriches','NY'),('East Nassau','NY'),('East New Market','MD'),('East Newport','ME'),('East Northport','NY'),('East Norwich','NY'),('East Olympia','WA'),('East Orange','NJ'),('East Orland','ME'),('East Orleans','MA'),('East Otis','MA'),('East Otto','NY'),('East Palatka','FL'),('East Palestine','OH'),('East Parsonsfield','ME'),('East Pembroke','NY'),('East Peoria','IL'),('East Petersburg','PA'),('East Pharsalia','NY'),('East Pittsburgh','PA'),('East Point','KY'),('East Poland','ME'),('East Poultney','VT'),('East Prairie','MO'),('East Princeton','MA'),('East Prospect','PA'),('East Providence','RI'),('East Quogue','NY'),('East Randolph','NY'),('East Randolph','VT'),('East Rochester','NY'),('East Rochester','OH'),('East Rockaway','NY'),('East Rutherford','NJ'),('East Ryegate','VT'),('East Saint Johnsbury','VT'),('East Saint Louis','IL'),('East Sandwich','MA'),('East Schodack','NY'),('East Setauket','NY'),('East Smethport','PA'),('East Smithfield','PA'),('East Sparta','OH'),('East Spencer','NC'),('East Springfield','NY'),('East Springfield','OH'),('East Springfield','PA'),('East Stone Gap','VA'),('East Stroudsburg','PA'),('East Syracuse','NY'),('East Tallassee','AL'),('East Taunton','MA'),('East Tawas','MI'),('East Templeton','MA'),('East Texas','PA'),('East Thetford','VT'),('East Troy','WI'),('East Vandergrift','PA'),('East Vassalboro','ME'),('East Wakefield','NH'),('East Wallingford','VT'),('East Walpole','MA'),('East Wareham','MA'),('East Waterboro','ME'),('East Waterford','PA'),('East Wenatchee','WA'),('East Weymouth','MA'),('East Williamson','NY'),('East Wilton','ME'),('East Windsor','CT'),('East Windsor Hill','CT'),('East Winthrop','ME'),('East Woodstock','CT'),('East Worcester','NY'),('Eastaboga','AL'),('Eastabuchie','MS'),('Eastanollee','GA'),('Eastchester','NY'),('Eastern','KY'),('Eastford','CT'),('Eastham','MA'),('Easthampton','MA'),('Eastlake','CO'),('Eastlake','MI'),('Eastlake','OH'),('Eastlake Weir','FL'),('Eastland','TX'),('Eastman','GA'),('Eastman','WI'),('Easton','CT'),('Easton','IL'),('Easton','KS'),('Easton','MA'),('Easton','MD'),('Easton','ME'),('Easton','MN'),('Easton','MO'),('Easton','PA'),('Easton','TX'),('Easton','WA'),('Eastover','SC'),('Eastpoint','FL'),('Eastpointe','MI'),('Eastport','ID'),('Eastport','ME'),('Eastport','MI'),('Eastport','NY'),('Eastsound','WA'),('Eastview','KY'),('Eastville','VA'),('Eastwood','KY'),('Eaton','CO'),('Eaton','IN'),('Eaton','NY'),('Eaton','OH'),('Eaton','TN'),('Eaton Center','NH'),('Eaton Park','FL'),('Eaton Rapids','MI'),('Eatonton','GA'),('Eatontown','NJ'),('Eatonville','WA'),('Eau Claire','MI'),('Eau Claire','PA'),('Eau Claire','WI'),('Eau Galle','WI'),('Eben Junction','MI'),('Ebensburg','PA'),('Ebervale','PA'),('Ebony','VA'),('Ebro','FL'),('Eccles','WV'),('Echo','LA'),('Echo','MN'),('Echo','OR'),('Echo','UT'),('Echo Lake','CA'),('Echola','AL'),('Eckerman','MI'),('Eckert','CO'),('Eckerty','IN'),('Eckhart Mines','MD'),('Eckley','CO'),('Eckman','WV'),('Eclectic','AL'),('Ecleto','TX'),('Economy','IN'),('Ecorse','MI'),('Ecru','MS'),('Ector','TX'),('Edcouch','TX'),('Eddington','ME'),('Eddy','TX'),('Eddyville','IA'),('Eddyville','IL'),('Eddyville','KY'),('Eddyville','NE'),('Eddyville','OR'),('Edelstein','IL'),('Eden','AZ'),('Eden','GA'),('Eden','ID'),('Eden','MD'),('Eden','NC'),('Eden','NY'),('Eden','SD'),('Eden','TX'),('Eden','UT'),('Eden','VT'),('Eden','WI'),('Eden Mills','VT'),('Eden Prairie','MN'),('Eden Valley','MN'),('Edenton','NC'),('Edenville','MI'),('Edgar','FL'),('Edgar','MT'),('Edgar','NE'),('Edgar','WI'),('Edgar Springs','MO'),('Edgard','LA'),('Edgarton','WV'),('Edgartown','MA'),('Edgecomb','ME'),('Edgefield','SC'),('Edgeley','ND'),('Edgemont','AR'),('Edgemont','PA'),('Edgemont','SD'),('Edgemoor','SC'),('Edgerton','KS'),('Edgerton','MN'),('Edgerton','MO'),('Edgerton','OH'),('Edgerton','WI'),('Edgerton','WY'),('Edgewater','FL'),('Edgewater','MD'),('Edgewater','NJ'),('Edgewater','WI'),('Edgewood','IA'),('Edgewood','IL'),('Edgewood','MD'),('Edgewood','NM'),('Edgewood','TX'),('Edina','MO'),('Edinboro','PA'),('Edinburg','IL'),('Edinburg','ND'),('Edinburg','PA'),('Edinburg','TX'),('Edinburg','VA'),('Edinburgh','IN'),('Edison','CA'),('Edison','GA'),('Edison','NE'),('Edison','NJ'),('Edison','OH'),('Edisto Island','SC'),('Edmeston','NY'),('Edmond','OK'),('Edmond','WV'),('Edmonds','WA'),('Edmondson','AR'),('Edmonson','TX'),('Edmonton','KY'),('Edmore','MI'),('Edmore','ND'),('Edmund','WI'),('Edna','KS'),('Edna','TX'),('Edneyville','NC'),('Edon','OH'),('Edroy','TX'),('Edson','KS'),('Edwall','WA'),('Edward','NC'),('Edwards','CA'),('Edwards','CO'),('Edwards','IL'),('Edwards','MO'),('Edwards','MS'),('Edwards','NY'),('Edwardsburg','MI'),('Edwardsport','IN'),('Edwardsville','AL'),('Edwardsville','IL'),('Edwardsville','KS'),('Edwardsville','VA'),('Eek','AK'),('Effie','LA'),('Effie','MN'),('Effingham','IL'),('Effingham','KS'),('Effingham','NH'),('Effingham','SC'),('Effort','PA'),('Efland','NC'),('Egan','LA'),('Egan','SD'),('Egegik','AK'),('Egeland','ND'),('Egg Harbor','WI'),('Egg Harbor City','NJ'),('Egg Harbor Township','NJ'),('Eggleston','VA'),('Eglin Afb','FL'),('Eglon','WV'),('Egnar','CO'),('Egypt','AR'),('Egypt','TX'),('Ehrenberg','AZ'),('Ehrhardt','SC'),('Eidson','TN'),('Eielson Afb','AK'),('Eight Mile','AL'),('Eighty Eight','KY'),('Eighty Four','PA'),('Eitzen','MN'),('Ekalaka','MT'),('Ekron','KY'),('Ekwok','AK'),('El Cajon','CA'),('El Campo','TX'),('El Centro','CA'),('El Cerrito','CA'),('El Dorado','AR'),('El Dorado','CA'),('El Dorado','KS'),('El Dorado Hills','CA'),('El Dorado Springs','MO'),('El Granada','CA'),('El Indio','TX'),('El Jobean','FL'),('El Mirage','AZ'),('El Monte','CA'),('El Nido','CA'),('El Paso','AR'),('El Paso','IL'),('El Paso','TX'),('El Portal','CA'),('El Prado','NM'),('El Reno','OK'),('El Rito','NM'),('El Segundo','CA'),('El Sobrante','CA'),('El Toro','CA'),('El Verano','CA'),('Elaine','AR'),('Eland','WI'),('Elba','AL'),('Elba','NE'),('Elba','NY'),('Elbe','WA'),('Elberfeld','IN'),('Elberon','IA'),('Elberon','VA'),('Elbert','CO'),('Elbert','WV'),('Elberta','AL'),('Elberta','MI'),('Elberta','UT'),('Elberton','GA'),('Elbing','KS'),('Elbow Lake','MN'),('Elbridge','NY'),('Elburn','IL'),('Elcho','WI'),('Elco','PA'),('Eldena','IL'),('Elderon','WI'),('Elderton','PA'),('Eldon','IA'),('Eldon','MO'),('Eldora','IA'),('Eldorado','IL'),('Eldorado','OH'),('Eldorado','OK'),('Eldorado','TX'),('Eldorado','WI'),('Eldorado Springs','CO'),('Eldred','IL'),('Eldred','NY'),('Eldred','PA'),('Eldridge','AL'),('Eldridge','CA'),('Eldridge','IA'),('Eldridge','MO'),('Eleanor','WV'),('Electra','TX'),('Electric City','WA'),('Eleele','HI'),('Elephant Butte','NM'),('Eleroy','IL'),('Eleva','WI'),('Elfers','FL'),('Elfin Cove','AK'),('Elfrida','AZ'),('Elgin','AZ'),('Elgin','IA'),('Elgin','IL'),('Elgin','MN'),('Elgin','ND'),('Elgin','NE'),('Elgin','OH'),('Elgin','OK'),('Elgin','OR'),('Elgin','PA'),('Elgin','SC'),('Elgin','TN'),('Elgin','TX'),('Elida','NM'),('Elim','AK'),('Eliot','ME'),('Elizabeth','AR'),('Elizabeth','CO'),('Elizabeth','IL'),('Elizabeth','IN'),('Elizabeth','LA'),('Elizabeth','MN'),('Elizabeth','NJ'),('Elizabeth','PA'),('Elizabeth','WV'),('Elizabeth City','NC'),('Elizabethton','TN'),('Elizabethtown','IL'),('Elizabethtown','IN'),('Elizabethtown','KY'),('Elizabethtown','NC'),('Elizabethtown','NY'),('Elizabethtown','PA'),('Elizabethville','PA'),('Elizaville','KY'),('Elizaville','NY'),('Elk','CA'),('Elk','WA'),('Elk City','ID'),('Elk City','KS'),('Elk City','OK'),('Elk Creek','CA'),('Elk Creek','MO'),('Elk Creek','NE'),('Elk Creek','VA'),('Elk Falls','KS'),('Elk Garden','WV'),('Elk Grove','CA'),('Elk Grove Village','IL'),('Elk Horn','IA'),('Elk Horn','KY'),('Elk Mills','MD'),('Elk Mound','WI'),('Elk Mountain','WY'),('Elk Park','NC'),('Elk Point','SD'),('Elk Rapids','MI'),('Elk River','ID'),('Elk River','MN'),('Elka Park','NY'),('Elkader','IA'),('Elkfork','KY'),('Elkhart','IA'),('Elkhart','IL'),('Elkhart','IN'),('Elkhart','KS'),('Elkhart','TX'),('Elkhart Lake','WI'),('Elkhorn','NE'),('Elkhorn','WI'),('Elkhorn','WV'),('Elkhorn City','KY'),('Elkin','NC'),('Elkins','AR'),('Elkins','NH'),('Elkins','WV'),('Elkins Park','PA'),('Elkland','MO'),('Elkland','PA'),('Elkmont','AL'),('Elko','GA'),('Elko','MN'),('Elko','NV'),('Elko','SC'),('Elkport','IA'),('Elkridge','MD'),('Elkton','FL'),('Elkton','KY'),('Elkton','MD'),('Elkton','MI'),('Elkton','MN'),('Elkton','OH'),('Elkton','OR'),('Elkton','SD'),('Elkton','TN'),('Elkton','VA'),('Elkview','WV'),('Elkville','IL'),('Elkwood','VA'),('Ellabell','GA'),('Ellamore','WV'),('Ellaville','GA'),('Ellenboro','NC'),('Ellenboro','WV'),('Ellenburg','NY'),('Ellenburg Center','NY'),('Ellenburg Depot','NY'),('Ellendale','DE'),('Ellendale','MN'),('Ellendale','ND'),('Ellendale','TN'),('Ellensburg','WA'),('Ellenton','FL'),('Ellenton','GA'),('Ellenville','NY'),('Ellenwood','GA'),('Ellerbe','NC'),('Ellerslie','GA'),('Ellerslie','MD'),('Ellery','IL'),('Ellettsville','IN'),('Ellicott City','MD'),('Ellicottville','NY'),('Ellijay','GA'),('Ellinger','TX'),('Ellington','CT'),('Ellington','MO'),('Ellington','NY'),('Ellinwood','KS'),('Elliott','IA'),('Elliott','IL'),('Elliott','MS'),('Elliott','SC'),('Elliottsburg','PA'),('Elliottville','KY'),('Ellis','ID'),('Ellis','KS'),('Ellis Grove','IL'),('Ellisburg','NY'),('Ellison Bay','WI'),('Elliston','MT'),('Elliston','VA'),('Ellisville','IL'),('Ellisville','MS'),('Elloree','SC'),('Ellsinore','MO'),('Ellston','IA'),('Ellsworth','IA'),('Ellsworth','IL'),('Ellsworth','KS'),('Ellsworth','ME'),('Ellsworth','MI'),('Ellsworth','MN'),('Ellsworth','NE'),('Ellsworth','OH'),('Ellsworth','PA'),('Ellsworth','WI'),('Ellsworth Afb','SD'),('Ellwood City','PA'),('Elm','PA'),('Elm City','NC'),('Elm Creek','NE'),('Elm Grove','LA'),('Elm Grove','WI'),('Elm Hall','MI'),('Elm Mott','TX'),('Elm Springs','AR'),('Elma','IA'),('Elma','NY'),('Elma','WA'),('Elmaton','TX'),('Elmdale','KS'),('Elmendorf','TX'),('Elmendorf Afb','AK'),('Elmer','LA'),('Elmer','MO'),('Elmer','NJ'),('Elmer','OK'),('Elmer City','WA'),('Elmhurst','IL'),('Elmhurst','NY'),('Elmhurst','PA'),('Elmira','CA'),('Elmira','MI'),('Elmira','NY'),('Elmira','OR'),('Elmo','MO'),('Elmo','MT'),('Elmo','TX'),('Elmo','UT'),('Elmont','NY'),('Elmora','PA'),('Elmore','AL'),('Elmore','MN'),('Elmore','OH'),('Elmore City','OK'),('Elmsford','NY'),('Elmwood','IL'),('Elmwood','MA'),('Elmwood','NE'),('Elmwood','TN'),('Elmwood','WI'),('Elmwood Park','IL'),('Elmwood Park','NJ'),('Elnora','IN'),('Elon','NC'),('Elora','TN'),('Eloy','AZ'),('Elrama','PA'),('Elrod','AL'),('Elrosa','MN'),('Elroy','WI'),('Elsa','TX'),('Elsah','IL'),('Elsberry','MO'),('Elsie','MI'),('Elsie','NE'),('Elsinore','UT'),('Elsmere','NE'),('Elsmore','KS'),('Elton','LA'),('Elton','PA'),('Elton','WI'),('Elton','WV'),('Eltopia','WA'),('Elvaston','IL'),('Elverson','PA'),('Elverta','CA'),('Elwell','MI'),('Elwin','IL'),('Elwood','IL'),('Elwood','IN'),('Elwood','KS'),('Elwood','NE'),('Elwood','NJ'),('Ely','IA'),('Ely','MN'),('Ely','NV'),('Elyria','NE'),('Elyria','OH'),('Elysburg','PA'),('Elysian','MN'),('Elysian Fields','TX'),('Embarrass','MN'),('Embarrass','WI'),('Emblem','WY'),('Embudo','NM'),('Emden','IL'),('Emden','MO'),('Emeigh','PA'),('Emelle','AL'),('Emerado','ND'),('Emerald Isle','NC'),('Emerson','AR'),('Emerson','GA'),('Emerson','IA'),('Emerson','KY'),('Emerson','NE'),('Emerson','NJ'),('Emery','SD'),('Emery','UT'),('Emeryville','CA'),('Emigrant','MT'),('Emigrant Gap','CA'),('Emigsville','PA'),('Emily','MN'),('Eminence','IN'),('Eminence','KY'),('Eminence','MO'),('Emington','IL'),('Emlenton','PA'),('Emlyn','KY'),('Emma','IL'),('Emma','MO'),('Emmalena','KY'),('Emmaus','PA'),('Emmet','AR'),('Emmet','NE'),('Emmetsburg','IA'),('Emmett','ID'),('Emmett','KS'),('Emmett','MI'),('Emmitsburg','MD'),('Emmonak','AK'),('Emmons','MN'),('Emory','TX'),('Emory','VA'),('Empire','AL'),('Empire','CA'),('Empire','CO'),('Empire','LA'),('Empire','MI'),('Empire','NV'),('Empire','OH'),('Emporia','KS'),('Emporia','VA'),('Emporium','PA'),('Encampment','WY'),('Encinal','TX'),('Encinitas','CA'),('Encino','CA'),('Encino','NM'),('Encino','TX'),('Endeavor','PA'),('Endeavor','WI'),('Enderlin','ND'),('Enders','NE'),('Endicott','NE'),('Endicott','NY'),('Endicott','WA'),('Endwell','NY'),('Energy','IL'),('Energy','TX'),('Enfield','CT'),('Enfield','IL'),('Enfield','NC'),('Enfield','NH'),('Enfield Center','NH'),('Engadine','MI'),('Engelhard','NC'),('England','AR'),('Englewood','CO'),('Englewood','FL'),('Englewood','KS'),('Englewood','NJ'),('Englewood','OH'),('Englewood','TN'),('Englewood Cliffs','NJ'),('English','IN'),('Englishtown','NJ'),('Enid','MS'),('Enid','OK'),('Enigma','GA'),('Enka','NC'),('Enloe','TX'),('Ennice','NC'),('Enning','SD'),('Ennis','MT'),('Ennis','TX'),('Enochs','TX'),('Enola','AR'),('Enola','PA'),('Enon','OH'),('Enon Valley','PA'),('Enoree','SC'),('Enosburg Falls','VT'),('Ensenada','PR'),('Ensign','KS'),('Enterprise','AL'),('Enterprise','KS'),('Enterprise','LA'),('Enterprise','MS'),('Enterprise','OR'),('Enterprise','UT'),('Enterprise','WV'),('Entiat','WA'),('Entriken','PA'),('Enumclaw','WA'),('Enville','TN'),('Eola','IL'),('Eola','TX'),('Eolia','KY'),('Eolia','MO'),('Epes','AL'),('Ephraim','UT'),('Ephraim','WI'),('Ephrata','PA'),('Ephrata','WA'),('Epping','ND'),('Epping','NH'),('Epps','LA'),('Epsom','NH'),('Epworth','GA'),('Epworth','IA'),('Equality','AL'),('Equality','IL'),('Equinunk','PA'),('Era','TX'),('Erath','LA'),('Erbacon','WV'),('Erhard','MN'),('Erick','OK'),('Ericson','NE'),('Erie','CO'),('Erie','IL'),('Erie','KS'),('Erie','MI'),('Erie','ND'),('Erie','PA'),('Erieville','NY'),('Eriline','KY'),('Erin','NY'),('Erin','TN'),('Erlanger','KY'),('Ermine','KY'),('Ernest','PA'),('Ernul','NC'),('Eros','LA'),('Errol','NH'),('Erskine','MN'),('Erving','MA'),('Erwin','NC'),('Erwin','SD'),('Erwin','TN'),('Erwinna','PA'),('Erwinville','LA'),('Esbon','KS'),('Escalante','UT'),('Escalon','CA'),('Escanaba','MI'),('Escatawpa','MS'),('Escondido','CA'),('Eskdale','WV'),('Esko','MN'),('Eskridge','KS'),('Esmond','IL'),('Esmond','ND'),('Esmont','VA'),('Esom Hill','GA'),('Esopus','NY'),('Espanola','NM'),('Esparto','CA'),('Esperance','NY'),('Essex','CA'),('Essex','CT'),('Essex','IA'),('Essex','IL'),('Essex','MA'),('Essex','MD'),('Essex','MO'),('Essex','MT'),('Essex','NY'),('Essex','VT'),('Essex Fells','NJ'),('Essex Junction','VT'),('Essexville','MI'),('Essie','KY'),('Essig','MN'),('Essington','PA'),('Estacada','OR'),('Estancia','NM'),('Estcourt Station','ME'),('Estell Manor','NJ'),('Estelline','SD'),('Estelline','TX'),('Ester','AK'),('Estero','FL'),('Estes Park','CO'),('Estherville','IA'),('Estherwood','LA'),('Estill','SC'),('Estill Springs','TN'),('Estillfork','AL'),('Ethan','SD'),('Ethel','AR'),('Ethel','LA'),('Ethel','MO'),('Ethel','MS'),('Ethel','WA'),('Ethel','WV'),('Ethelsville','AL'),('Ether','NC'),('Ethridge','MT'),('Ethridge','TN'),('Etlan','VA'),('Etna','CA'),('Etna','ME'),('Etna','NH'),('Etna','NY'),('Etna','OH'),('Etna','WY'),('Etna Green','IN'),('Etoile','KY'),('Etoile','TX'),('Eton','GA'),('Etowah','AR'),('Etowah','NC'),('Etowah','TN'),('Etta','MS'),('Etters','PA'),('Ettrick','WI'),('Eubank','KY'),('Eucha','OK'),('Euclid','MN'),('Euclid','OH'),('Eudora','AR'),('Eudora','KS'),('Eudora','MO'),('Eufaula','AL'),('Eufaula','OK'),('Eugene','MO'),('Eugene','OR'),('Euless','TX'),('Eunice','LA'),('Eunice','MO'),('Eunice','NM'),('Eupora','MS'),('Eure','NC'),('Eureka','CA'),('Eureka','IL'),('Eureka','KS'),('Eureka','MI'),('Eureka','MO'),('Eureka','MT'),('Eureka','NV'),('Eureka','SD'),('Eureka','UT'),('Eureka','WI'),('Eureka Springs','AR'),('Eustace','TX'),('Eustis','FL'),('Eustis','ME'),('Eustis','NE'),('Eutaw','AL'),('Eutawville','SC'),('Eva','AL'),('Eva','TN'),('Evadale','TX'),('Evangeline','LA'),('Evans','CO'),('Evans','GA'),('Evans','LA'),('Evans','WA'),('Evans','WV'),('Evans City','PA'),('Evans Mills','NY'),('Evansdale','IA'),('Evansport','OH'),('Evanston','IL'),('Evanston','IN'),('Evanston','WY'),('Evansville','AR'),('Evansville','IL'),('Evansville','IN'),('Evansville','MN'),('Evansville','WI'),('Evansville','WY'),('Evant','TX'),('Evart','MI'),('Evarts','KY'),('Eveleth','MN'),('Evening Shade','AR'),('Evensville','TN'),('Everest','KS'),('Everett','MA'),('Everett','PA'),('Everett','WA'),('Everetts','NC'),('Everglades City','FL'),('Evergreen','CO'),('Evergreen','LA'),('Evergreen','NC'),('Evergreen','VA'),('Evergreen Park','IL'),('Everly','IA'),('Everson','PA'),('Everson','WA'),('Everton','AR'),('Everton','MO'),('Evington','VA'),('Evinston','FL'),('Ewa Beach','HI'),('Ewan','NJ'),('Ewell','MD'),('Ewen','MI'),('Ewing','IL'),('Ewing','KY'),('Ewing','MO'),('Ewing','NE'),('Ewing','NJ'),('Ewing','VA'),('Excel','AL'),('Excello','MO'),('Excelsior','MN'),('Excelsior Springs','MO'),('Exchange','WV'),('Exeland','WI'),('Exeter','CA'),('Exeter','ME'),('Exeter','MO'),('Exeter','NE'),('Exeter','NH'),('Exeter','RI'),('Exira','IA'),('Exline','IA'),('Exmore','VA'),('Experiment','GA'),('Export','PA'),('Exton','PA'),('Eyota','MN'),('Ezel','KY'),('Fabens','TX'),('Faber','VA'),('Fabius','NY'),('Fabyan','CT'),('Fackler','AL'),('Factoryville','PA'),('Fagus','MO'),('Fair Bluff','NC'),('Fair Grove','MO'),('Fair Haven','MI'),('Fair Haven','NJ'),('Fair Haven','NY'),('Fair Haven','VT'),('Fair Lawn','NJ'),('Fair Oaks','CA'),('Fair Oaks','IN'),('Fair Play','MO'),('Fair Play','SC'),('Fairacres','NM'),('Fairbank','IA'),('Fairbank','PA'),('Fairbanks','AK'),('Fairbanks','IN'),('Fairbanks','LA'),('Fairborn','OH'),('Fairburn','GA'),('Fairburn','SD'),('Fairbury','IL'),('Fairbury','NE'),('Fairchance','PA'),('Fairchild','WI'),('Fairchild Air Force Base','WA'),('Fairdale','KY'),('Fairdale','ND'),('Fairdale','WV'),('Fairdealing','MO'),('Fairfax','CA'),('Fairfax','IA'),('Fairfax','MN'),('Fairfax','MO'),('Fairfax','OK'),('Fairfax','SC'),('Fairfax','SD'),('Fairfax','VA'),('Fairfax','VT'),('Fairfax Station','VA'),('Fairfield','AL'),('Fairfield','CA'),('Fairfield','CT'),('Fairfield','FL'),('Fairfield','IA'),('Fairfield','ID'),('Fairfield','IL'),('Fairfield','KY'),('Fairfield','ME'),('Fairfield','MT'),('Fairfield','NC'),('Fairfield','ND'),('Fairfield','NE'),('Fairfield','NJ'),('Fairfield','OH'),('Fairfield','PA'),('Fairfield','TX'),('Fairfield','VA'),('Fairfield','VT'),('Fairfield','WA'),('Fairfield Bay','AR'),('Fairforest','SC'),('Fairgrove','MI'),('Fairhaven','MA'),('Fairhope','AL'),('Fairhope','PA'),('Fairland','IN'),('Fairland','OK'),('Fairlawn','OH'),('Fairlea','WV'),('Fairlee','VT'),('Fairless Hills','PA'),('Fairmont','MN'),('Fairmont','NC'),('Fairmont','NE'),('Fairmont','OK'),('Fairmont','WV'),('Fairmount','GA'),('Fairmount','IL'),('Fairmount','IN'),('Fairmount','ND'),('Fairmount City','PA'),('Fairplay','CO'),('Fairplay','MD'),('Fairpoint','OH'),('Fairport','MO'),('Fairport','NY'),('Fairton','NJ'),('Fairview','IL'),('Fairview','KS'),('Fairview','KY'),('Fairview','MI'),('Fairview','MO'),('Fairview','MT'),('Fairview','NC'),('Fairview','NJ'),('Fairview','OH'),('Fairview','OK'),('Fairview','OR'),('Fairview','PA'),('Fairview','SD'),('Fairview','TN'),('Fairview','UT'),('Fairview','WV'),('Fairview','WY'),('Fairview Heights','IL'),('Fairview Village','PA'),('Fairwater','WI'),('Faison','NC'),('Faith','NC'),('Faith','SD'),('Fajardo','PR'),('Falcon','KY'),('Falcon','MO'),('Falcon','MS'),('Falcon','NC'),('Falcon Heights','TX'),('Falconer','NY'),('Falfurrias','TX'),('Falkland','NC'),('Falkner','MS'),('Falkville','AL'),('Fall Branch','TN'),('Fall City','WA'),('Fall Creek','OR'),('Fall Creek','WI'),('Fall River','KS'),('Fall River','MA'),('Fall River','WI'),('Fall River Mills','CA'),('Fall Rock','KY'),('Fallbrook','CA'),('Fallentimber','PA'),('Falling Rock','WV'),('Falling Waters','WV'),('Fallon','MT'),('Fallon','NV'),('Falls','PA'),('Falls Church','VA'),('Falls City','NE'),('Falls City','OR'),('Falls City','TX'),('Falls Creek','PA'),('Falls Mills','VA'),('Falls Of Rough','KY'),('Falls Village','CT'),('Fallsburg','NY'),('Fallston','MD'),('Fallston','NC'),('Falmouth','IN'),('Falmouth','KY'),('Falmouth','MA'),('Falmouth','ME'),('Falmouth','MI'),('False Pass','AK'),('Falun','KS'),('Fancher','NY'),('Fancy Farm','KY'),('Fancy Gap','VA'),('Fannettsburg','PA'),('Fannin','TX'),('Fanrock','WV'),('Fanshawe','OK'),('Fanwood','NJ'),('Far Hills','NJ'),('Far Rockaway','NY'),('Farber','MO'),('Fargo','GA'),('Fargo','ND'),('Fargo','OK'),('Faribault','MN'),('Farina','IL'),('Farley','IA'),('Farley','MO'),('Farlington','KS'),('Farmdale','OH'),('Farmer','OH'),('Farmer City','IL'),('Farmers','KY'),('Farmersburg','IA'),('Farmersburg','IN'),('Farmersville','CA'),('Farmersville','IL'),('Farmersville','OH'),('Farmersville','TX'),('Farmersville Station','NY'),('Farmerville','LA'),('Farmingdale','ME'),('Farmingdale','NJ'),('Farmingdale','NY'),('Farmington','AR'),('Farmington','CA'),('Farmington','CT'),('Farmington','GA'),('Farmington','IA'),('Farmington','IL'),('Farmington','KY'),('Farmington','ME'),('Farmington','MI'),('Farmington','MN'),('Farmington','MO'),('Farmington','NH'),('Farmington','NM'),('Farmington','NY'),('Farmington','PA'),('Farmington','UT'),('Farmington','WA'),('Farmington','WV'),('Farmington Falls','ME'),('Farmingville','NY'),('Farmland','IN'),('Farmville','NC'),('Farmville','VA'),('Farnam','NE'),('Farner','TN'),('Farnham','NY'),('Farnham','VA'),('Farnhamville','IA'),('Farnsworth','TX'),('Farragut','IA'),('Farrar','MO'),('Farrell','MS'),('Farrell','PA'),('Farris','OK'),('Farson','WY'),('Farwell','MI'),('Farwell','MN'),('Farwell','NE'),('Farwell','TX'),('Fate','TX'),('Faucett','MO'),('Faulkner','MD'),('Faulkton','SD'),('Faunsdale','AL'),('Fawn Grove','PA'),('Fawnskin','CA'),('Faxon','OK'),('Fay','OK'),('Fayette','AL'),('Fayette','IA'),('Fayette','MO'),('Fayette','MS'),('Fayette','NY'),('Fayette','OH'),('Fayette','UT'),('Fayette City','PA'),('Fayetteville','AR'),('Fayetteville','GA'),('Fayetteville','NC'),('Fayetteville','NY'),('Fayetteville','OH'),('Fayetteville','PA'),('Fayetteville','TN'),('Fayetteville','TX'),('Fayetteville','WV'),('Fayville','MA'),('Faywood','NM'),('Feasterville Trevose','PA'),('Feather Falls','CA'),('Federal Dam','MN'),('Federal Way','WA'),('Federalsburg','MD'),('Fedhaven','FL'),('Fedora','SD'),('Fedscreek','KY'),('Feeding Hills','MA'),('Feesburg','OH'),('Felch','MI'),('Felda','FL'),('Felicity','OH'),('Fellows','CA'),('Fellsmere','FL'),('Felt','ID'),('Felt','OK'),('Felton','CA'),('Felton','DE'),('Felton','GA'),('Felton','MN'),('Felton','PA'),('Felts Mills','NY'),('Fence','WI'),('Fence Lake','NM'),('Fenelton','PA'),('Fenn','ID'),('Fennimore','WI'),('Fennville','MI'),('Fenton','IA'),('Fenton','IL'),('Fenton','LA'),('Fenton','MI'),('Fenton','MO'),('Fentress','TX'),('Fenwick','MI'),('Fenwick','WV'),('Fenwick Island','DE'),('Ferdinand','ID'),('Ferdinand','IN'),('Fergus Falls','MN'),('Ferguson','IA'),('Ferguson','KY'),('Ferguson','NC'),('Fernandina Beach','FL'),('Ferndale','CA'),('Ferndale','FL'),('Ferndale','MI'),('Ferndale','NY'),('Ferndale','PA'),('Ferndale','WA'),('Ferney','SD'),('Fernley','NV'),('Fernwood','ID'),('Fernwood','MS'),('Ferriday','LA'),('Ferris','IL'),('Ferris','TX'),('Ferrisburgh','VT'),('Ferron','UT'),('Ferrum','VA'),('Ferrysburg','MI'),('Ferryville','WI'),('Fertile','IA'),('Fertile','MN'),('Fessenden','ND'),('Festus','MO'),('Feura Bush','NY'),('Fiatt','IL'),('Fiddletown','CA'),('Fidelity','IL'),('Fieldale','VA'),('Fielding','UT'),('Fieldon','IL'),('Fields','OR'),('Fields Landing','CA'),('Fieldton','TX'),('Fife Lake','MI'),('Fifield','WI'),('Fifty Lakes','MN'),('Fifty Six','AR'),('Filer','ID'),('Filer City','MI'),('Filion','MI'),('Filley','NE'),('Fillmore','CA'),('Fillmore','IL'),('Fillmore','IN'),('Fillmore','MO'),('Fillmore','NY'),('Fillmore','UT'),('Fincastle','VA'),('Finchville','KY'),('Findlay','IL'),('Findlay','OH'),('Findley Lake','NY'),('Fine','NY'),('Fingal','ND'),('Finger','TN'),('Fingerville','SC'),('Finksburg','MD'),('Finland','MN'),('Finlayson','MN'),('Finley','CA'),('Finley','ND'),('Finley','OK'),('Finley','TN'),('Finleyville','PA'),('Finly','IN'),('Firebaugh','CA'),('Firestone','CO'),('Firth','ID'),('Firth','NE'),('Fischer','TX'),('Fish Camp','CA'),('Fish Creek','WI'),('Fish Haven','ID'),('Fisher','AR'),('Fisher','IL'),('Fisher','LA'),('Fisher','MN'),('Fisher','PA'),('Fisher','WV'),('Fishers','IN'),('Fishers','NY'),('Fishers Hill','VA'),('Fishers Island','NY'),('Fishers Landing','NY'),('Fishersville','VA'),('Fishertown','PA'),('Fisherville','KY'),('Fishing Creek','MD'),('Fishkill','NY'),('Fishs Eddy','NY'),('Fishtail','MT'),('Fisk','MO'),('Fiskdale','MA'),('Fiskeville','RI'),('Fisty','KY'),('Fitchburg','MA'),('Fithian','IL'),('Fittstown','OK'),('Fitzgerald','GA'),('Fitzhugh','OK'),('Fitzpatrick','AL'),('Fitzwilliam','NH'),('Five Points','AL'),('Five Points','CA'),('Five Points','TN'),('Flag Pond','TN'),('Flagler','CO'),('Flagler Beach','FL'),('Flagstaff','AZ'),('Flagtown','NJ'),('Flanagan','IL'),('Flanders','NJ'),('Flandreau','SD'),('Flasher','ND'),('Flat','TX'),('Flat Lick','KY'),('Flat Rock','AL'),('Flat Rock','IL'),('Flat Rock','IN'),('Flat Rock','MI'),('Flat Rock','NC'),('Flat Rock','OH'),('Flat Top','WV'),('Flatgap','KY'),('Flatonia','TX'),('Flatwoods','KY'),('Flatwoods','LA'),('Flatwoods','WV'),('Flaxton','ND'),('Flaxville','MT'),('Fleetville','PA'),('Fleetwood','NC'),('Fleetwood','PA'),('Fleischmanns','NY'),('Fleming','CO'),('Fleming','GA'),('Fleming','OH'),('Fleming','PA'),('Fleming Island','FL'),('Flemingsburg','KY'),('Flemington','MO'),('Flemington','NJ'),('Flemington','WV'),('Flensburg','MN'),('Fletcher','MO'),('Fletcher','NC'),('Fletcher','OH'),('Fletcher','OK'),('Flicksville','PA'),('Flint','MI'),('Flint','TX'),('Flint Hill','VA'),('Flinthill','MO'),('Flinton','PA'),('Flintstone','GA'),('Flintstone','MD'),('Flintville','TN'),('Flippin','AR'),('Flom','MN'),('Flomaton','AL'),('Flomot','TX'),('Floodwood','MN'),('Flora','IL'),('Flora','IN'),('Flora','LA'),('Flora','MS'),('Flora Vista','NM'),('Florahome','FL'),('Floral','AR'),('Floral City','FL'),('Floral Park','NY'),('Florala','AL'),('Florence','AL'),('Florence','AZ'),('Florence','CO'),('Florence','IN'),('Florence','KS'),('Florence','KY'),('Florence','MO'),('Florence','MS'),('Florence','MT'),('Florence','NJ'),('Florence','OR'),('Florence','SC'),('Florence','SD'),('Florence','TX'),('Florence','VT'),('Florence','WI'),('Floresville','TX'),('Florham Park','NJ'),('Florida','NY'),('Florida','PR'),('Florien','LA'),('Floris','IA'),('Florissant','CO'),('Florissant','MO'),('Floriston','CA'),('Flossmoor','IL'),('Flournoy','CA'),('Flourtown','PA'),('Flovilla','GA'),('Flower Mound','TX'),('Floweree','MT'),('Flowery Branch','GA'),('Flowood','MS'),('Floyd','IA'),('Floyd','NM'),('Floyd','VA'),('Floydada','TX'),('Floyds Knobs','IN'),('Fluker','LA'),('Flushing','MI'),('Flushing','NY'),('Flushing','OH'),('Fluvanna','TX'),('Fly Creek','NY'),('Flynn','TX'),('Fogelsville','PA'),('Folcroft','PA'),('Foley','AL'),('Foley','MN'),('Foley','MO'),('Folkston','GA'),('Follansbee','WV'),('Follett','TX'),('Folly Beach','SC'),('Folsom','CA'),('Folsom','LA'),('Folsom','NM'),('Folsom','PA'),('Folsom','WV'),('Folsomville','IN'),('Fombell','PA'),('Fond Du Lac','WI'),('Fonda','IA'),('Fonda','NY'),('Fontana','CA'),('Fontana','KS'),('Fontana','WI'),('Fontana Dam','NC'),('Fontanelle','IA'),('Fontanet','IN'),('Foosland','IL'),('Foothill Ranch','CA'),('Footville','WI'),('Forbes','MN'),('Forbes','ND'),('Forbes Road','PA'),('Forbestown','CA'),('Force','PA'),('Ford','KS'),('Ford','VA'),('Ford','WA'),('Ford City','PA'),('Ford Cliff','PA'),('Fordland','MO'),('Fordoche','LA'),('Fords','NJ'),('Fords Branch','KY'),('Fordsville','KY'),('Fordville','ND'),('Fordyce','AR'),('Fordyce','NE'),('Foreman','AR'),('Forest','IN'),('Forest','LA'),('Forest','MS'),('Forest','OH'),('Forest','VA'),('Forest City','IA'),('Forest City','IL'),('Forest City','MO'),('Forest City','NC'),('Forest City','PA'),('Forest Dale','VT'),('Forest Falls','CA'),('Forest Grove','MT'),('Forest Grove','OR'),('Forest Grove','PA'),('Forest Hill','LA'),('Forest Hill','MD'),('Forest Hill','WV'),('Forest Hills','KY'),('Forest Hills','NY'),('Forest Home','AL'),('Forest Junction','WI'),('Forest Knolls','CA'),('Forest Lake','MN'),('Forest Lakes','AZ'),('Forest Park','GA'),('Forest Park','IL'),('Forest Ranch','CA'),('Forest River','ND'),('Forestburg','TX'),('Forestburgh','NY'),('Forestdale','MA'),('Forestdale','RI'),('Foresthill','CA'),('Foreston','MN'),('Forestport','NY'),('Forestville','CA'),('Forestville','MI'),('Forestville','NY'),('Forestville','PA'),('Forestville','WI'),('Forgan','OK'),('Foristell','MO'),('Fork','MD'),('Fork','SC'),('Fork Union','VA'),('Forked River','NJ'),('Forkland','AL'),('Forks','WA'),('Forks Of Salmon','CA'),('Forksville','PA'),('Forman','ND'),('Formoso','KS'),('Forney','TX'),('Forrest','IL'),('Forrest City','AR'),('Forreston','IL'),('Forreston','TX'),('Forsan','TX'),('Forsyth','GA'),('Forsyth','IL'),('Forsyth','MO'),('Forsyth','MT'),('Fort Ann','NY'),('Fort Apache','AZ'),('Fort Ashby','WV'),('Fort Atkinson','IA'),('Fort Atkinson','WI'),('Fort Bayard','NM'),('Fort Belvoir','VA'),('Fort Benning','GA'),('Fort Benton','MT'),('Fort Bidwell','CA'),('Fort Blackmore','VA'),('Fort Bliss','TX'),('Fort Bragg','CA'),('Fort Bragg','NC'),('Fort Branch','IN'),('Fort Bridger','WY'),('Fort Buchanan','PR'),('Fort Calhoun','NE'),('Fort Campbell','KY'),('Fort Cobb','OK'),('Fort Collins','CO'),('Fort Covington','NY'),('Fort Davis','AL'),('Fort Davis','TX'),('Fort Defiance','AZ'),('Fort Defiance','VA'),('Fort Deposit','AL'),('Fort Dick','CA'),('Fort Dix','NJ'),('Fort Dodge','IA'),('Fort Dodge','KS'),('Fort Drum','NY'),('Fort Duchesne','UT'),('Fort Edward','NY'),('Fort Eustis','VA'),('Fort Fairfield','ME'),('Fort Gaines','GA'),('Fort Garland','CO'),('Fort Gay','WV'),('Fort George G Meade','MD'),('Fort Gibson','OK'),('Fort Gratiot','MI'),('Fort Greely','AK'),('Fort Hall','ID'),('Fort Hancock','TX'),('Fort Harrison','MT'),('Fort Hill','PA'),('Fort Howard','MD'),('Fort Huachuca','AZ'),('Fort Hunter','NY'),('Fort Irwin','CA'),('Fort Jennings','OH'),('Fort Johnson','NY'),('Fort Jones','CA'),('Fort Kent','ME'),('Fort Kent Mills','ME'),('Fort Klamath','OR'),('Fort Knox','KY'),('Fort Laramie','WY'),('Fort Lauderdale','FL'),('Fort Lawn','SC'),('Fort Leavenworth','KS'),('Fort Lee','NJ'),('Fort Lee','VA'),('Fort Leonard Wood','MO'),('Fort Littleton','PA'),('Fort Loramie','OH'),('Fort Loudon','PA'),('Fort Lupton','CO'),('Fort Lyon','CO'),('Fort Madison','IA'),('Fort Mc Coy','FL'),('Fort Mc Kavett','TX'),('Fort Mcdowell','AZ'),('Fort Meade','FL'),('Fort Meade','SD'),('Fort Mill','SC'),('Fort Mitchell','AL'),('Fort Mitchell','VA'),('Fort Mohave','AZ'),('Fort Monmouth','NJ'),('Fort Monroe','VA'),('Fort Montgomery','NY'),('Fort Morgan','CO'),('Fort Myers','FL'),('Fort Myers Beach','FL'),('Fort Necessity','LA'),('Fort Ogden','FL'),('Fort Oglethorpe','GA'),('Fort Payne','AL'),('Fort Peck','MT'),('Fort Pierce','FL'),('Fort Pierre','SD'),('Fort Plain','NY'),('Fort Polk','LA'),('Fort Ransom','ND'),('Fort Recovery','OH'),('Fort Richardson','AK'),('Fort Riley','KS'),('Fort Ripley','MN'),('Fort Ritner','IN'),('Fort Rock','OR'),('Fort Rucker','AL'),('Fort Scott','KS'),('Fort Shafter','HI'),('Fort Shaw','MT'),('Fort Sheridan','IL'),('Fort Sill','OK'),('Fort Smith','AR'),('Fort Stanton','NM'),('Fort Stewart','GA'),('Fort Stockton','TX'),('Fort Sumner','NM'),('Fort Supply','OK'),('Fort Thomas','AZ'),('Fort Thomas','KY'),('Fort Thompson','SD'),('Fort Totten','ND'),('Fort Towson','OK'),('Fort Valley','GA'),('Fort Valley','VA'),('Fort Wainwright','AK'),('Fort Walton Beach','FL'),('Fort Washakie','WY'),('Fort Washington','MD'),('Fort Washington','PA'),('Fort Wayne','IN'),('Fort White','FL'),('Fort Wingate','NM'),('Fort Worth','TX'),('Fort Yates','ND'),('Fort Yukon','AK'),('Fortescue','NJ'),('Fortine','MT'),('Fortson','GA'),('Fortuna','CA'),('Fortuna','MO'),('Fortuna','ND'),('Fortville','IN'),('Foss','OK'),('Fossil','OR'),('Fosston','MN'),('Foster','KY'),('Foster','MO'),('Foster','OK'),('Foster','OR'),('Foster','RI'),('Foster','VA'),('Foster','WV'),('Foster City','MI'),('Fosters','AL'),('Fosterville','TN'),('Fostoria','IA'),('Fostoria','KS'),('Fostoria','MI'),('Fostoria','OH'),('Fouke','AR'),('Fountain','CO'),('Fountain','FL'),('Fountain','MI'),('Fountain','MN'),('Fountain','NC'),('Fountain City','IN'),('Fountain City','WI'),('Fountain Green','UT'),('Fountain Hill','AR'),('Fountain Hills','AZ'),('Fountain Inn','SC'),('Fountain Run','KY'),('Fountain Valley','CA'),('Fountaintown','IN'),('Fountainville','PA'),('Four Corners','WY'),('Four Lakes','WA'),('Four Oaks','NC'),('Four States','WV'),('Fourmile','KY'),('Fowler','CA'),('Fowler','CO'),('Fowler','IL'),('Fowler','IN'),('Fowler','KS'),('Fowler','MI'),('Fowler','OH'),('Fowlerton','IN'),('Fowlerton','TX'),('Fowlerville','MI'),('Fowlstown','GA'),('Fox','AR'),('Fox','OK'),('Fox Island','WA'),('Fox Lake','IL'),('Fox Lake','WI'),('Fox River Grove','IL'),('Fox Valley','IL'),('Foxboro','MA'),('Foxboro','WI'),('Foxburg','PA'),('Foxhome','MN'),('Foxworth','MS'),('Foyil','OK'),('Frackville','PA'),('Frakes','KY'),('Frametown','WV'),('Framingham','MA'),('Francestown','NH'),('Francesville','IN'),('Francis','OK'),('Francis Creek','WI'),('Francisco','IN'),('Francitas','TX'),('Franconia','NH'),('Franconia','PA'),('Frankenmuth','MI'),('Frankewing','TN'),('Frankford','DE'),('Frankford','MO'),('Frankford','WV'),('Frankfort','IL'),('Frankfort','IN'),('Frankfort','KS'),('Frankfort','KY'),('Frankfort','ME'),('Frankfort','MI'),('Frankfort','NY'),('Frankfort','OH'),('Frankfort','SD'),('Frankfort Heights','IL'),('Franklin','AL'),('Franklin','AR'),('Franklin','GA'),('Franklin','ID'),('Franklin','IL'),('Franklin','IN'),('Franklin','KS'),('Franklin','KY'),('Franklin','LA'),('Franklin','MA'),('Franklin','ME'),('Franklin','MI'),('Franklin','MN'),('Franklin','MO'),('Franklin','NC'),('Franklin','NE'),('Franklin','NH'),('Franklin','NJ'),('Franklin','NY'),('Franklin','OH'),('Franklin','PA'),('Franklin','TN'),('Franklin','TX'),('Franklin','VA'),('Franklin','VT'),('Franklin','WI'),('Franklin','WV'),('Franklin Furnace','OH'),('Franklin Grove','IL'),('Franklin Lakes','NJ'),('Franklin Park','IL'),('Franklin Park','NJ'),('Franklin Springs','GA'),('Franklin Springs','NY'),('Franklin Square','NY'),('Franklinton','LA'),('Franklinton','NC'),('Franklintown','PA'),('Franklinville','NC'),('Franklinville','NJ'),('Franklinville','NY'),('Frankston','TX'),('Franksville','WI'),('Frankton','IN'),('Franktown','CO'),('Franktown','VA'),('Frankville','AL'),('Frannie','WY'),('Fraser','CO'),('Fraser','MI'),('Frazee','MN'),('Frazer','MT'),('Frazeysburg','OH'),('Frazier Park','CA'),('Fraziers Bottom','WV'),('Fred','TX'),('Frederic','MI'),('Frederic','WI'),('Frederica','DE'),('Frederick','CO'),('Frederick','IL'),('Frederick','MD'),('Frederick','OK'),('Frederick','PA'),('Frederick','SD'),('Fredericksburg','IA'),('Fredericksburg','IN'),('Fredericksburg','OH'),('Fredericksburg','PA'),('Fredericksburg','TX'),('Fredericksburg','VA'),('Fredericktown','MO'),('Fredericktown','OH'),('Fredericktown','PA'),('Frederika','IA'),('Fredonia','AZ'),('Fredonia','KS'),('Fredonia','KY'),('Fredonia','ND'),('Fredonia','NY'),('Fredonia','PA'),('Fredonia','TX'),('Fredonia','WI'),('Free Soil','MI'),('Free Union','VA'),('Freeborn','MN'),('Freeburg','IL'),('Freeburg','MO'),('Freeburg','PA'),('Freeburn','KY'),('Freedom','CA'),('Freedom','IN'),('Freedom','ME'),('Freedom','NH'),('Freedom','NY'),('Freedom','OK'),('Freedom','PA'),('Freedom','WI'),('Freedom','WY'),('Freehold','NJ'),('Freehold','NY'),('Freeland','MD'),('Freeland','MI'),('Freeland','PA'),('Freeland','WA'),('Freelandville','IN'),('Freeman','MO'),('Freeman','SD'),('Freeman','VA'),('Freeman','WV'),('Freeman Spur','IL'),('Freeport','FL'),('Freeport','IL'),('Freeport','KS'),('Freeport','ME'),('Freeport','MI'),('Freeport','MN'),('Freeport','NY'),('Freeport','OH'),('Freeport','PA'),('Freeport','TX'),('Freer','TX'),('Freetown','IN'),('Freeville','NY'),('Freistatt','MO'),('Fremont','CA'),('Fremont','IA'),('Fremont','IN'),('Fremont','MI'),('Fremont','MO'),('Fremont','NC'),('Fremont','NE'),('Fremont','NH'),('Fremont','OH'),('Fremont','WI'),('Fremont Center','NY'),('French Camp','CA'),('French Camp','MS'),('French Creek','WV'),('French Gulch','CA'),('French Lick','IN'),('French Settlement','LA'),('French Village','MO'),('Frenchboro','ME'),('Frenchburg','KY'),('Frenchglen','OR'),('Frenchmans Bayou','AR'),('Frenchton','WV'),('Frenchtown','MT'),('Frenchtown','NJ'),('Frenchville','ME'),('Frenchville','PA'),('Fresh Meadows','NY'),('Fresno','CA'),('Fresno','OH'),('Fresno','TX'),('Frewsburg','NY'),('Friant','CA'),('Friars Point','MS'),('Friday Harbor','WA'),('Friedens','PA'),('Friedensburg','PA'),('Friedheim','MO'),('Friend','NE'),('Friendly','WV'),('Friendship','AR'),('Friendship','IN'),('Friendship','MD'),('Friendship','ME'),('Friendship','NY'),('Friendship','OH'),('Friendship','TN'),('Friendship','WI'),('Friendsville','MD'),('Friendsville','PA'),('Friendsville','TN'),('Friendswood','TX'),('Frierson','LA'),('Fries','VA'),('Friesland','WI'),('Friona','TX'),('Frisco','CO'),('Frisco','NC'),('Frisco','TX'),('Frisco City','AL'),('Fritch','TX'),('Frohna','MO'),('Froid','MT'),('Fromberg','MT'),('Front Royal','VA'),('Frontenac','KS'),('Frontenac','MN'),('Frontier','MI'),('Frontier','WY'),('Frost','MN'),('Frost','TX'),('Frostburg','MD'),('Frostburg','PA'),('Frostproof','FL'),('Fruita','CO'),('Fruitdale','AL'),('Fruithurst','AL'),('Fruitland','IA'),('Fruitland','ID'),('Fruitland','MD'),('Fruitland','NM'),('Fruitland','UT'),('Fruitland','WA'),('Fruitland Park','FL'),('Fruitport','MI'),('Fruitvale','TN'),('Fruitvale','TX'),('Fryburg','PA'),('Fryeburg','ME'),('Ft Mitchell','KY'),('Ft Myer','VA'),('Ft Warren Afb','WY'),('Fulda','IN'),('Fulda','MN'),('Fulks Run','VA'),('Fullerton','CA'),('Fullerton','ND'),('Fullerton','NE'),('Fulshear','TX'),('Fulton','AL'),('Fulton','AR'),('Fulton','CA'),('Fulton','IL'),('Fulton','IN'),('Fulton','KS'),('Fulton','KY'),('Fulton','MD'),('Fulton','MI'),('Fulton','MO'),('Fulton','MS'),('Fulton','NY'),('Fulton','OH'),('Fulton','SD'),('Fulton','TX'),('Fultondale','AL'),('Fultonham','NY'),('Fultonham','OH'),('Fultonville','NY'),('Fults','IL'),('Funk','NE'),('Funkstown','MD'),('Funston','GA'),('Fuquay Varina','NC'),('Furlong','PA'),('Furman','AL'),('Furman','SC'),('Fyffe','AL'),('Gaastra','MI'),('Gabbs','NV'),('Gable','SC'),('Gabriels','NY'),('Gackle','ND'),('Gadsden','AL'),('Gadsden','AZ'),('Gadsden','SC'),('Gadsden','TN'),('Gaffney','SC'),('Gage','OK'),('Gagetown','MI'),('Gail','TX'),('Gaines','MI'),('Gaines','PA'),('Gainesboro','TN'),('Gainestown','AL'),('Gainesville','AL'),('Gainesville','FL'),('Gainesville','GA'),('Gainesville','MO'),('Gainesville','NY'),('Gainesville','TX'),('Gainesville','VA'),('Gaithersburg','MD'),('Gakona','AK'),('Galata','MT'),('Galatia','IL'),('Galax','VA'),('Galena','AK'),('Galena','IL'),('Galena','KS'),('Galena','MD'),('Galena','MO'),('Galena','OH'),('Galena Park','TX'),('Gales Creek','OR'),('Gales Ferry','CT'),('Galesburg','IL'),('Galesburg','KS'),('Galesburg','MI'),('Galesburg','ND'),('Galesville','MD'),('Galesville','WI'),('Galeton','CO'),('Galeton','PA'),('Galien','MI'),('Galion','OH'),('Galivants Ferry','SC'),('Gallagher','WV'),('Gallant','AL'),('Gallatin','MO'),('Gallatin','TN'),('Gallatin','TX'),('Gallatin Gateway','MT'),('Gallaway','TN'),('Galliano','LA'),('Gallina','NM'),('Gallion','AL'),('Gallipolis','OH'),('Gallipolis Ferry','WV'),('Gallitzin','PA'),('Gallman','MS'),('Galloway','OH'),('Galloway','WI'),('Galloway','WV'),('Gallup','NM'),('Gallupville','NY'),('Galt','CA'),('Galt','IA'),('Galt','IL'),('Galt','MO'),('Galva','IA'),('Galva','IL'),('Galva','KS'),('Galveston','IN'),('Galveston','TX'),('Galvin','WA'),('Galway','NY'),('Gamaliel','AR'),('Gamaliel','KY'),('Gambell','AK'),('Gambier','OH'),('Gambrills','MD'),('Gamerco','NM'),('Ganado','AZ'),('Ganado','TX'),('Gandeeville','WV'),('Gann Valley','SD'),('Gans','OK'),('Gans','PA'),('Gansevoort','NY'),('Gantt','AL'),('Gap','PA'),('Gap Mills','WV'),('Gapville','KY'),('Garards Fort','PA'),('Garber','IA'),('Garber','OK'),('Garberville','CA'),('Garcia','CO'),('Garciasville','TX'),('Garden','MI'),('Garden City','AL'),('Garden City','IA'),('Garden City','ID'),('Garden City','KS'),('Garden City','LA'),('Garden City','MI'),('Garden City','MN'),('Garden City','MO'),('Garden City','NY'),('Garden City','SD'),('Garden City','TX'),('Garden City','UT'),('Garden Grove','CA'),('Garden Grove','IA'),('Garden Plain','KS'),('Garden Prairie','IL'),('Garden Valley','CA'),('Garden Valley','ID'),('Gardena','CA'),('Gardendale','AL'),('Gardendale','TX'),('Gardenville','PA'),('Gardiner','ME'),('Gardiner','MT'),('Gardiner','NY'),('Gardiner','OR'),('Gardner','CO'),('Gardner','IL'),('Gardner','KS'),('Gardner','LA'),('Gardner','MA'),('Gardner','ND'),('Gardners','PA'),('Gardnerville','NV'),('Garfield','AR'),('Garfield','GA'),('Garfield','KS'),('Garfield','KY'),('Garfield','MN'),('Garfield','NJ'),('Garfield','NM'),('Garfield','WA'),('Garibaldi','OR'),('Garita','NM'),('Garland','KS'),('Garland','ME'),('Garland','NC'),('Garland','NE'),('Garland','PA'),('Garland','TX'),('Garland','UT'),('Garland City','AR'),('Garnavillo','IA'),('Garner','AR'),('Garner','IA'),('Garner','KY'),('Garner','NC'),('Garnerville','NY'),('Garnett','KS'),('Garnett','SC'),('Garrard','KY'),('Garrattsville','NY'),('Garretson','SD'),('Garrett','IN'),('Garrett','KY'),('Garrett','PA'),('Garrett','WY'),('Garrett Park','MD'),('Garrettsville','OH'),('Garrison','IA'),('Garrison','KY'),('Garrison','MN'),('Garrison','MO'),('Garrison','MT'),('Garrison','ND'),('Garrison','NY'),('Garrison','TX'),('Garrison','UT'),('Garrisonville','VA'),('Garrochales','PR'),('Garryowen','MT'),('Garvin','MN'),('Garvin','OK'),('Garwin','IA'),('Garwood','NJ'),('Garwood','TX'),('Gary','IN'),('Gary','MN'),('Gary','SD'),('Gary','TX'),('Gary','WV'),('Garysburg','NC'),('Garyville','LA'),('Gas','KS'),('Gas City','IN'),('Gasburg','VA'),('Gasconade','MO'),('Gasport','NY'),('Gasquet','CA'),('Gassaway','WV'),('Gassville','AR'),('Gaston','IN'),('Gaston','NC'),('Gaston','OR'),('Gaston','SC'),('Gastonia','NC'),('Gastonville','PA'),('Gate','OK'),('Gate City','VA'),('Gates','NC'),('Gates','OR'),('Gates','TN'),('Gates Mills','OH'),('Gatesville','NC'),('Gatesville','TX'),('Gateway','AR'),('Gateway','CO'),('Gatewood','MO'),('Gatlinburg','TN'),('Gattman','MS'),('Gatzke','MN'),('Gauley Bridge','WV'),('Gause','TX'),('Gautier','MS'),('Gay','GA'),('Gay','WV'),('Gaylesville','AL'),('Gaylord','KS'),('Gaylord','MI'),('Gaylord','MN'),('Gaylordsville','CT'),('Gays','IL'),('Gays Creek','KY'),('Gays Mills','WI'),('Gaysville','VT'),('Gayville','SD'),('Gazelle','CA'),('Geary','OK'),('Geddes','SD'),('Geff','IL'),('Geigertown','PA'),('Geismar','LA'),('Gem','KS'),('Gene Autry','OK'),('Genesee','ID'),('Genesee','MI'),('Genesee','PA'),('Genesee Depot','WI'),('Geneseo','IL'),('Geneseo','KS'),('Geneseo','NY'),('Geneva','AL'),('Geneva','FL'),('Geneva','GA'),('Geneva','IA'),('Geneva','ID'),('Geneva','IL'),('Geneva','IN'),('Geneva','MN'),('Geneva','NE'),('Geneva','NY'),('Geneva','OH'),('Genoa','AR'),('Genoa','CO'),('Genoa','IL'),('Genoa','NE'),('Genoa','NV'),('Genoa','NY'),('Genoa','OH'),('Genoa','WI'),('Genoa','WV'),('Genoa City','WI'),('Gentry','AR'),('Gentry','MO'),('Gentryville','IN'),('George','IA'),('George','WA'),('George West','TX'),('Georges Mills','NH'),('Georgetown','CA'),('Georgetown','CO'),('Georgetown','CT'),('Georgetown','DE'),('Georgetown','FL'),('Georgetown','GA'),('Georgetown','ID'),('Georgetown','IL'),('Georgetown','IN'),('Georgetown','KY'),('Georgetown','LA'),('Georgetown','MA'),('Georgetown','MD'),('Georgetown','ME'),('Georgetown','MN'),('Georgetown','MS'),('Georgetown','NY'),('Georgetown','OH'),('Georgetown','PA'),('Georgetown','SC'),('Georgetown','TN'),('Georgetown','TX'),('Georgiana','AL'),('Gepp','AR'),('Gerald','MO'),('Geraldine','AL'),('Geraldine','MT'),('Gerber','CA'),('Gering','NE'),('Gerlach','NV'),('Gerlaw','IL'),('German Valley','IL'),('Germansville','PA'),('Germanton','NC'),('Germantown','IL'),('Germantown','KY'),('Germantown','MD'),('Germantown','NY'),('Germantown','OH'),('Germantown','TN'),('Germantown','WI'),('Germfask','MI'),('Geronimo','OK'),('Geronimo','TX'),('Gerrardstown','WV'),('Gerry','NY'),('Gerton','NC'),('Gervais','OR'),('Gettysburg','OH'),('Gettysburg','PA'),('Gettysburg','SD'),('Getzville','NY'),('Geuda Springs','KS'),('Geyser','MT'),('Geyserville','CA'),('Gheens','LA'),('Ghent','KY'),('Ghent','MN'),('Ghent','NY'),('Ghent','WV'),('Gibbon','MN'),('Gibbon','NE'),('Gibbon Glade','PA'),('Gibbonsville','ID'),('Gibbs','MO'),('Gibbsboro','NJ'),('Gibbstown','NJ'),('Gibsland','LA'),('Gibson','GA'),('Gibson','IA'),('Gibson','LA'),('Gibson','MO'),('Gibson','NC'),('Gibson','PA'),('Gibson','TN'),('Gibson City','IL'),('Gibson Island','MD'),('Gibsonburg','OH'),('Gibsonia','PA'),('Gibsonton','FL'),('Gibsonville','NC'),('Giddings','TX'),('Gideon','MO'),('Gifford','IA'),('Gifford','IL'),('Gifford','PA'),('Gifford','SC'),('Gifford','WA'),('Gig Harbor','WA'),('Gila','NM'),('Gila Bend','AZ'),('Gilbert','AR'),('Gilbert','AZ'),('Gilbert','IA'),('Gilbert','LA'),('Gilbert','MN'),('Gilbert','PA'),('Gilbert','SC'),('Gilbert','WV'),('Gilberton','PA'),('Gilbertown','AL'),('Gilberts','IL'),('Gilbertsville','KY'),('Gilbertsville','NY'),('Gilbertsville','PA'),('Gilbertville','IA'),('Gilbertville','MA'),('Gilboa','NY'),('Gilboa','WV'),('Gilby','ND'),('Gilchrist','OR'),('Gilchrist','TX'),('Gilcrest','CO'),('Gildford','MT'),('Gile','WI'),('Gilead','NE'),('Gilford','MI'),('Gilford','NH'),('Gill','CO'),('Gill','MA'),('Gillespie','IL'),('Gillett','AR'),('Gillett','PA'),('Gillett','TX'),('Gillett','WI'),('Gillett Grove','IA'),('Gillette','NJ'),('Gillette','WY'),('Gillham','AR'),('Gilliam','LA'),('Gilliam','MO'),('Gillsville','GA'),('Gilman','CT'),('Gilman','IA'),('Gilman','IL'),('Gilman','MN'),('Gilman','VT'),('Gilman','WI'),('Gilman City','MO'),('Gilmanton','NH'),('Gilmanton','WI'),('Gilmanton Iron Works','NH'),('Gilmer','TX'),('Gilmore','AR'),('Gilmore City','IA'),('Gilroy','CA'),('Gilson','IL'),('Gilsum','NH'),('Giltner','NE'),('Gipsy','MO'),('Gipsy','PA'),('Girard','GA'),('Girard','IL'),('Girard','KS'),('Girard','OH'),('Girard','PA'),('Girard','TX'),('Girardville','PA'),('Girdler','KY'),('Girdletree','MD'),('Girdwood','AK'),('Girvin','TX'),('Given','WV'),('Gladbrook','IA'),('Glade','KS'),('Glade Hill','VA'),('Glade Park','CO'),('Glade Spring','VA'),('Glade Valley','NC'),('Gladeville','TN'),('Gladewater','TX'),('Gladstone','IL'),('Gladstone','MI'),('Gladstone','ND'),('Gladstone','NJ'),('Gladstone','NM'),('Gladstone','OR'),('Gladstone','VA'),('Gladwin','MI'),('Gladwyne','PA'),('Glady','WV'),('Gladys','VA'),('Glandorf','OH'),('Glasco','KS'),('Glasco','NY'),('Glasford','IL'),('Glasgow','KY'),('Glasgow','MO'),('Glasgow','MT'),('Glasgow','PA'),('Glasgow','VA'),('Glasgow','WV'),('Glassboro','NJ'),('Glasser','NJ'),('Glassport','PA'),('Glasston','ND'),('Glastonbury','CT'),('Gleason','TN'),('Gleason','WI'),('Glen','MS'),('Glen','MT'),('Glen','NH'),('Glen','WV'),('Glen Allan','MS'),('Glen Allen','AL'),('Glen Allen','VA'),('Glen Alpine','NC'),('Glen Arbor','MI'),('Glen Arm','MD'),('Glen Aubrey','NY'),('Glen Burnie','MD'),('Glen Campbell','PA'),('Glen Carbon','IL'),('Glen Cove','ME'),('Glen Cove','NY'),('Glen Dale','WV'),('Glen Daniel','WV'),('Glen Easton','WV'),('Glen Echo','MD'),('Glen Elder','KS'),('Glen Ellen','CA'),('Glen Ellyn','IL'),('Glen Ferris','WV'),('Glen Flora','TX'),('Glen Flora','WI'),('Glen Fork','WV'),('Glen Gardner','NJ'),('Glen Haven','CO'),('Glen Haven','WI'),('Glen Head','NY'),('Glen Hope','PA'),('Glen Jean','WV'),('Glen Lyn','VA'),('Glen Lyon','PA'),('Glen Mills','PA'),('Glen Oaks','NY'),('Glen Richey','PA'),('Glen Riddle Lima','PA'),('Glen Ridge','NJ'),('Glen Rock','NJ'),('Glen Rock','PA'),('Glen Rogers','WV'),('Glen Rose','TX'),('Glen Saint Mary','FL'),('Glen Spey','NY'),('Glen Ullin','ND'),('Glen White','WV'),('Glen Wild','NY'),('Glen Wilton','VA'),('Glenallen','MO'),('Glenarm','IL'),('Glenbeulah','WI'),('Glenbrook','NV'),('Glenburn','ND'),('Glencliff','NH'),('Glencoe','AR'),('Glencoe','CA'),('Glencoe','IL'),('Glencoe','KY'),('Glencoe','MN'),('Glencoe','MO'),('Glencoe','NM'),('Glencoe','OH'),('Glencoe','OK'),('Glencross','SD'),('Glendale','AZ'),('Glendale','CA'),('Glendale','KY'),('Glendale','MA'),('Glendale','OR'),('Glendale','RI'),('Glendale','SC'),('Glendale','UT'),('Glendale Heights','IL'),('Glendale Luke Afb','AZ'),('Glendale Springs','NC'),('Glendive','MT'),('Glendo','WY'),('Glendora','CA'),('Glendora','MS'),('Glendora','NJ'),('Gleneden Beach','OR'),('Glenelg','MD'),('Glenfield','ND'),('Glenfield','NY'),('Glenford','NY'),('Glenford','OH'),('Glengary','WV'),('Glenham','NY'),('Glenham','SD'),('Glenhaven','CA'),('Glenmont','NY'),('Glenmont','OH'),('Glenmoore','PA'),('Glenmora','LA'),('Glenn','CA'),('Glenn','GA'),('Glenn','MI'),('Glenn Dale','MD'),('Glennallen','AK'),('Glennie','MI'),('Glenns Ferry','ID'),('Glennville','CA'),('Glennville','GA'),('Glenolden','PA'),('Glenoma','WA'),('Glenpool','OK'),('Glenrock','WY'),('Glens Falls','NY'),('Glens Fork','KY'),('Glenshaw','PA'),('Glenside','PA'),('Glentana','MT'),('Glenview','IL'),('Glenview','KY'),('Glenvil','NE'),('Glenville','MN'),('Glenville','NC'),('Glenville','PA'),('Glenville','WV'),('Glenwood','AL'),('Glenwood','AR'),('Glenwood','FL'),('Glenwood','GA'),('Glenwood','IA'),('Glenwood','IL'),('Glenwood','IN'),('Glenwood','MD'),('Glenwood','MN'),('Glenwood','MO'),('Glenwood','NC'),('Glenwood','NJ'),('Glenwood','NM'),('Glenwood','NY'),('Glenwood','UT'),('Glenwood','WA'),('Glenwood','WV'),('Glenwood City','WI'),('Glenwood Landing','NY'),('Glenwood Springs','CO'),('Glidden','IA'),('Glidden','TX'),('Glidden','WI'),('Glide','OR'),('Globe','AZ'),('Glorieta','NM'),('Gloster','LA'),('Gloster','MS'),('Gloucester','MA'),('Gloucester','NC'),('Gloucester','VA'),('Gloucester City','NJ'),('Gloucester Point','VA'),('Glouster','OH'),('Glover','VT'),('Gloversville','NY'),('Gloverville','SC'),('Glyndon','MD'),('Glyndon','MN'),('Glynn','LA'),('Gnadenhutten','OH'),('Gober','TX'),('Gobler','MO'),('Gobles','MI'),('Goddard','KS'),('Godfrey','IL'),('Godley','TX'),('Godwin','NC'),('Goehner','NE'),('Goessel','KS'),('Goetzville','MI'),('Goff','KS'),('Goffstown','NH'),('Golconda','IL'),('Golconda','NV'),('Gold Bar','WA'),('Gold Beach','OR'),('Gold Creek','MT'),('Gold Hill','NC'),('Gold Hill','OR'),('Gold Run','CA'),('Golden','CO'),('Golden','IL'),('Golden','MO'),('Golden','MS'),('Golden','OK'),('Golden','TX'),('Golden City','MO'),('Golden Eagle','IL'),('Golden Gate','IL'),('Golden Meadow','LA'),('Golden Valley','AZ'),('Golden Valley','ND'),('Goldendale','WA'),('Goldenrod','FL'),('Goldens Bridge','NY'),('Goldfield','IA'),('Goldfield','NV'),('Goldonna','LA'),('Goldsboro','MD'),('Goldsboro','NC'),('Goldsboro','TX'),('Goldsmith','IN'),('Goldsmith','TX'),('Goldston','NC'),('Goldthwaite','TX'),('Goldvein','VA'),('Goleta','CA'),('Golf','IL'),('Goliad','TX'),('Goltry','OK'),('Golva','ND'),('Gomer','OH'),('Gonvick','MN'),('Gonzales','CA'),('Gonzales','LA'),('Gonzales','TX'),('Gonzalez','FL'),('Goochland','VA'),('Good Hart','MI'),('Good Hope','GA'),('Good Hope','IL'),('Good Thunder','MN'),('Goode','VA'),('Goodell','IA'),('Goodells','MI'),('Goodfellow Afb','TX'),('Goodfield','IL'),('Goodhue','MN'),('Gooding','ID'),('Goodland','FL'),('Goodland','IN'),('Goodland','KS'),('Goodland','MN'),('Goodlettsville','TN'),('Goodman','MO'),('Goodman','MS'),('Goodman','WI'),('Goodnews Bay','AK'),('Goodrich','MI'),('Goodrich','ND'),('Goodrich','TX'),('Goodridge','MN'),('Goodspring','TN'),('Goodsprings','AL'),('Goodview','VA'),('Goodville','PA'),('Goodwater','AL'),('Goodway','AL'),('Goodwell','OK'),('Goodwin','AR'),('Goodwin','SD'),('Goodwine','IL'),('Goodyear','AZ'),('Goodyears Bar','CA'),('Goose Creek','SC'),('Goose Lake','IA'),('Goose Prairie','WA'),('Goose Rock','KY'),('Gordo','AL'),('Gordon','AL'),('Gordon','GA'),('Gordon','KY'),('Gordon','NE'),('Gordon','PA'),('Gordon','TX'),('Gordon','WI'),('Gordon','WV'),('Gordonsville','TN'),('Gordonsville','VA'),('Gordonville','MO'),('Gordonville','PA'),('Gordonville','TX'),('Gore','OK'),('Gore','VA'),('Gore Springs','MS'),('Goree','TX'),('Goreville','IL'),('Gorham','IL'),('Gorham','KS'),('Gorham','ME'),('Gorham','NH'),('Gorham','NY'),('Gorin','MO'),('Gorman','TX'),('Gormania','WV'),('Gorum','LA'),('Goshen','AL'),('Goshen','AR'),('Goshen','CA'),('Goshen','CT'),('Goshen','IN'),('Goshen','KY'),('Goshen','MA'),('Goshen','NH'),('Goshen','NJ'),('Goshen','NY'),('Goshen','OH'),('Goshen','UT'),('Goshen','VA'),('Gosnell','AR'),('Gosport','IN'),('Gotebo','OK'),('Gotha','FL'),('Gotham','WI'),('Gothenburg','NE'),('Gough','GA'),('Gould','AR'),('Gould','OK'),('Gould City','MI'),('Gouldbusk','TX'),('Gouldsboro','ME'),('Gouldsboro','PA'),('Gouverneur','NY'),('Gove','KS'),('Government Camp','OR'),('Gowanda','NY'),('Gowen','MI'),('Gowen','OK'),('Gower','MO'),('Gowrie','IA'),('Grabill','IN'),('Grace','ID'),('Grace','MS'),('Grace City','ND'),('Gracemont','OK'),('Graceville','FL'),('Graceville','MN'),('Gracewood','GA'),('Gracey','KY'),('Grady','AL'),('Grady','AR'),('Grady','NM'),('Gradyville','KY'),('Gradyville','PA'),('Graettinger','IA'),('Graff','MO'),('Graford','TX'),('Grafton','IA'),('Grafton','IL'),('Grafton','MA'),('Grafton','ND'),('Grafton','NE'),('Grafton','NH'),('Grafton','NY'),('Grafton','OH'),('Grafton','VT'),('Grafton','WI'),('Grafton','WV'),('Graham','AL'),('Graham','FL'),('Graham','KY'),('Graham','MO'),('Graham','NC'),('Graham','OK'),('Graham','TX'),('Graham','WA'),('Grahamsville','NY'),('Grahn','KY'),('Grain Valley','MO'),('Grainfield','KS'),('Grambling','LA'),('Gramercy','LA'),('Gramling','SC'),('Grammer','IN'),('Grampian','PA'),('Granada','CO'),('Granada','MN'),('Granada Hills','CA'),('Granbury','TX'),('Granby','CO'),('Granby','CT'),('Granby','MA'),('Granby','MO'),('Granby','VT'),('Grand Bay','AL'),('Grand Blanc','MI'),('Grand Cane','LA'),('Grand Canyon','AZ'),('Grand Chain','IL'),('Grand Chenier','LA'),('Grand Coteau','LA'),('Grand Coulee','WA'),('Grand Forks','ND'),('Grand Forks Afb','ND'),('Grand Gorge','NY'),('Grand Haven','MI'),('Grand Island','FL'),('Grand Island','NE'),('Grand Island','NY'),('Grand Isle','LA'),('Grand Isle','ME'),('Grand Isle','VT'),('Grand Junction','CO'),('Grand Junction','IA'),('Grand Junction','MI'),('Grand Junction','TN'),('Grand Lake','CO'),('Grand Lake Stream','ME'),('Grand Ledge','MI'),('Grand Marais','MI'),('Grand Marais','MN'),('Grand Marsh','WI'),('Grand Meadow','MN'),('Grand Mound','IA'),('Grand Portage','MN'),('Grand Prairie','TX'),('Grand Rapids','MI'),('Grand Rapids','MN'),('Grand Rapids','OH'),('Grand Ridge','FL'),('Grand Ridge','IL'),('Grand River','IA'),('Grand River','OH'),('Grand Rivers','KY'),('Grand Ronde','OR'),('Grand Saline','TX'),('Grand Terrace','CA'),('Grand Tower','IL'),('Grand Valley','PA'),('Grand View','ID'),('Grand View','WI'),('Grandfalls','TX'),('Grandfield','OK'),('Grandin','FL'),('Grandin','MO'),('Grandin','ND'),('Grandview','IA'),('Grandview','IN'),('Grandview','MO'),('Grandview','TN'),('Grandview','TX'),('Grandview','WA'),('Grandville','MI'),('Grandy','MN'),('Grandy','NC'),('Granger','IA'),('Granger','IN'),('Granger','MO'),('Granger','TX'),('Granger','WA'),('Granger','WY'),('Grangeville','ID'),('Granite','CO'),('Granite','OK'),('Granite Bay','CA'),('Granite Canon','WY'),('Granite City','IL'),('Granite Falls','MN'),('Granite Falls','NC'),('Granite Falls','WA'),('Granite Quarry','NC'),('Granite Springs','NY'),('Graniteville','SC'),('Graniteville','VT'),('Grannis','AR'),('Grant','AL'),('Grant','CO'),('Grant','FL'),('Grant','IA'),('Grant','LA'),('Grant','MI'),('Grant','NE'),('Grant','OK'),('Grant City','MO'),('Grant Park','IL'),('Grant Town','WV'),('Grantham','NH'),('Grantham','PA'),('Granton','WI'),('Grants','NM'),('Grants Pass','OR'),('Grantsboro','NC'),('Grantsburg','IL'),('Grantsburg','IN'),('Grantsburg','WI'),('Grantsdale','MT'),('Grantsville','MD'),('Grantsville','UT'),('Grantsville','WV'),('Grantville','GA'),('Grantville','KS'),('Grantville','PA'),('Granville','IA'),('Granville','IL'),('Granville','MA'),('Granville','ND'),('Granville','NY'),('Granville','OH'),('Granville','PA'),('Granville','TN'),('Granville','VT'),('Granville','WV'),('Granville Summit','PA'),('Grapeland','TX'),('Grapeview','WA'),('Grapeville','PA'),('Grapevine','AR'),('Grapevine','TX'),('Grasonville','MD'),('Grass Creek','IN'),('Grass Lake','MI'),('Grass Range','MT'),('Grass Valley','CA'),('Grass Valley','OR'),('Grassflat','PA'),('Grasston','MN'),('Grassy Butte','ND'),('Grassy Creek','NC'),('Grassy Meadows','WV'),('Gratiot','OH'),('Gratiot','WI'),('Gratis','OH'),('Graton','CA'),('Gratz','PA'),('Gravel Switch','KY'),('Gravelly','AR'),('Graves Mill','VA'),('Gravette','AR'),('Gravity','IA'),('Gravois Mills','MO'),('Grawn','MI'),('Gray','GA'),('Gray','IA'),('Gray','KY'),('Gray','LA'),('Gray','ME'),('Gray','PA'),('Gray Court','SC'),('Gray Hawk','KY'),('Gray Mountain','AZ'),('Gray Summit','MO'),('Grayland','WA'),('Grayling','AK'),('Grayling','MI'),('Graymont','IL'),('Grayridge','MO'),('Grays Knob','KY'),('Grays River','WA'),('Grayslake','IL'),('Grayson','GA'),('Grayson','KY'),('Grayson','LA'),('Graysville','AL'),('Graysville','GA'),('Graysville','IN'),('Graysville','OH'),('Graysville','PA'),('Graysville','TN'),('Graytown','OH'),('Grayville','IL'),('Great Barrington','MA'),('Great Bend','KS'),('Great Bend','NY'),('Great Bend','PA'),('Great Cacapon','WV'),('Great Falls','MT'),('Great Falls','SC'),('Great Falls','VA'),('Great Lakes','IL'),('Great Meadows','NJ'),('Great Mills','MD'),('Great Neck','NY'),('Great River','NY'),('Great Valley','NY'),('Greeley','CO'),('Greeley','IA'),('Greeley','KS'),('Greeley','NE'),('Greeley','PA'),('Greeleyville','SC'),('Green','KS'),('Green','OH'),('Green Bank','WV'),('Green Bay','VA'),('Green Bay','WI'),('Green Camp','OH'),('Green Castle','MO'),('Green City','MO'),('Green Cove Springs','FL'),('Green Creek','NJ'),('Green Forest','AR'),('Green Harbor','MA'),('Green Isle','MN'),('Green Lake','WI'),('Green Lane','PA'),('Green Mountain','NC'),('Green Mountain Falls','CO'),('Green Pond','AL'),('Green Pond','SC'),('Green Ridge','MO'),('Green River','UT'),('Green River','WY'),('Green Road','KY'),('Green Sea','SC'),('Green Spring','WV'),('Green Springs','OH'),('Green Sulphur Springs','WV'),('Green Valley','AZ'),('Green Valley','IL'),('Green Valley','WI'),('Green Valley Lake','CA'),('Green Village','NJ'),('Greenacres','WA'),('Greenback','TN'),('Greenbackville','VA'),('Greenbank','WA'),('Greenbelt','MD'),('Greenbrae','CA'),('Greenbrier','AR'),('Greenbrier','TN'),('Greenbush','MA'),('Greenbush','ME'),('Greenbush','MI'),('Greenbush','MN'),('Greenbush','VA'),('Greenbush','WI'),('Greencastle','IN'),('Greencastle','PA'),('Greencreek','ID'),('Greendale','WI'),('Greendell','NJ'),('Greene','IA'),('Greene','ME'),('Greene','NY'),('Greene','RI'),('Greeneville','TN'),('Greenfield','CA'),('Greenfield','IA'),('Greenfield','IL'),('Greenfield','IN'),('Greenfield','MA'),('Greenfield','MO'),('Greenfield','NH'),('Greenfield','OH'),('Greenfield','OK'),('Greenfield','TN'),('Greenfield Center','NY'),('Greenfield Park','NY'),('Greenford','OH'),('Greenhurst','NY'),('Greenland','AR'),('Greenland','MI'),('Greenland','NH'),('Greenlawn','NY'),('Greenleaf','ID'),('Greenleaf','KS'),('Greenleaf','WI'),('Greenock','PA'),('Greenport','NY'),('Greens Farms','CT'),('Greens Fork','IN'),('Greensboro','AL'),('Greensboro','FL'),('Greensboro','GA'),('Greensboro','IN'),('Greensboro','MD'),('Greensboro','NC'),('Greensboro','PA'),('Greensboro','VT'),('Greensboro Bend','VT'),('Greensburg','IN'),('Greensburg','KS'),('Greensburg','KY'),('Greensburg','LA'),('Greensburg','PA'),('Greentop','MO'),('Greentown','IN'),('Greentown','OH'),('Greentown','PA'),('Greenup','IL'),('Greenup','KY'),('Greenvale','NY'),('Greenview','CA'),('Greenview','IL'),('Greenville','AL'),('Greenville','CA'),('Greenville','FL'),('Greenville','GA'),('Greenville','IA'),('Greenville','IL'),('Greenville','IN'),('Greenville','KY'),('Greenville','ME'),('Greenville','MI'),('Greenville','MO'),('Greenville','MS'),('Greenville','NC'),('Greenville','NH'),('Greenville','NY'),('Greenville','OH'),('Greenville','PA'),('Greenville','RI'),('Greenville','SC'),('Greenville','TX'),('Greenville','UT'),('Greenville','VA'),('Greenville','WI'),('Greenville','WV'),('Greenville Junction','ME'),('Greenwald','MN'),('Greenway','AR'),('Greenway','VA'),('Greenwell Springs','LA'),('Greenwich','CT'),('Greenwich','KS'),('Greenwich','NJ'),('Greenwich','NY'),('Greenwich','OH'),('Greenwich','UT'),('Greenwood','AR'),('Greenwood','CA'),('Greenwood','DE'),('Greenwood','FL'),('Greenwood','IN'),('Greenwood','LA'),('Greenwood','ME'),('Greenwood','MO'),('Greenwood','MS'),('Greenwood','NE'),('Greenwood','NY'),('Greenwood','SC'),('Greenwood','TX'),('Greenwood','VA'),('Greenwood','WI'),('Greenwood Lake','NY'),('Greenwood Springs','MS'),('Greer','AZ'),('Greer','SC'),('Gregory','AR'),('Gregory','MI'),('Gregory','SD'),('Gregory','TX'),('Greig','NY'),('Grelton','OH'),('Grenada','CA'),('Grenada','MS'),('Grenloch','NJ'),('Grenola','KS'),('Grenora','ND'),('Grenville','NM'),('Grenville','SD'),('Gresham','NE'),('Gresham','OR'),('Gresham','SC'),('Gresham','WI'),('Grethel','KY'),('Gretna','FL'),('Gretna','LA'),('Gretna','NE'),('Gretna','VA'),('Grey Eagle','MN'),('Greybull','WY'),('Greycliff','MT'),('Greystone Park','NJ'),('Gridley','CA'),('Gridley','IL'),('Gridley','KS'),('Griffin','GA'),('Griffin','IN'),('Griffith','IN'),('Griffithsville','WV'),('Griffithville','AR'),('Grifton','NC'),('Griggsville','IL'),('Grimes','CA'),('Grimes','IA'),('Grimesland','NC'),('Grimsley','TN'),('Grimstead','VA'),('Grindstone','PA'),('Grinnell','IA'),('Grinnell','KS'),('Grissom Arb','IN'),('Griswold','IA'),('Grizzly Flats','CA'),('Groesbeck','TX'),('Groom','TX'),('Grosse Ile','MI'),('Grosse Pointe','MI'),('Grosse Tete','LA'),('Grosvenor Dale','CT'),('Groton','CT'),('Groton','MA'),('Groton','NY'),('Groton','SD'),('Groton','VT'),('Grottoes','VA'),('Grouse Creek','UT'),('Grove','OK'),('Grove City','MN'),('Grove City','OH'),('Grove City','PA'),('Grove Hill','AL'),('Groveland','CA'),('Groveland','FL'),('Groveland','IL'),('Groveland','MA'),('Groveland','NY'),('Groveoak','AL'),('Groveport','OH'),('Grover','CO'),('Grover','MO'),('Grover','NC'),('Grover','PA'),('Grover','SC'),('Grover','WY'),('Grover Beach','CA'),('Grover Hill','OH'),('Grovertown','IN'),('Groves','TX'),('Grovespring','MO'),('Groveton','TX'),('Grovetown','GA'),('Grubbs','AR'),('Grubville','MO'),('Gruetli Laager','TN'),('Grulla','TX'),('Grundy','VA'),('Grundy Center','IA'),('Gruver','IA'),('Gruver','TX'),('Grygla','MN'),('Guadalupe','CA'),('Guadalupita','NM'),('Gualala','CA'),('Guanica','PR'),('Guasti','CA'),('Guatay','CA'),('Guayama','PR'),('Guayanilla','PR'),('Guaynabo','PR'),('Guerneville','CA'),('Guernsey','IA'),('Guernsey','WY'),('Guerra','TX'),('Gueydan','LA'),('Guffey','CO'),('Guide Rock','NE'),('Guild','NH'),('Guild','TN'),('Guilderland','NY'),('Guilderland Center','NY'),('Guildhall','VT'),('Guilford','CT'),('Guilford','IN'),('Guilford','ME'),('Guilford','MO'),('Guilford','NY'),('Guin','AL'),('Guinda','CA'),('Guion','AR'),('Gulf','NC'),('Gulf Breeze','FL'),('Gulf Hammock','FL'),('Gulf Shores','AL'),('Gulfport','MS'),('Gulliver','MI'),('Gully','MN'),('Gulston','KY'),('Gum Spring','VA'),('Gunlock','KY'),('Gunlock','UT'),('Gunnison','CO'),('Gunnison','MS'),('Gunnison','UT'),('Gunpowder','MD'),('Gunter','TX'),('Guntersville','AL'),('Guntown','MS'),('Gurabo','PR'),('Gurdon','AR'),('Gurley','AL'),('Gurley','NE'),('Gurnee','IL'),('Gustavus','AK'),('Gustine','CA'),('Gustine','TX'),('Guston','KY'),('Guthrie','KY'),('Guthrie','OK'),('Guthrie','TX'),('Guthrie Center','IA'),('Guttenberg','IA'),('Guy','AR'),('Guy','TX'),('Guymon','OK'),('Guys','TN'),('Guys Mills','PA'),('Guysville','OH'),('Guyton','GA'),('Gwinn','MI'),('Gwinner','ND'),('Gwynedd','PA'),('Gwynedd Valley','PA'),('Gwynn','VA'),('Gwynn Oak','MD'),('Gwynneville','IN'),('Gypsum','CO'),('Gypsum','KS'),('Gypsum','OH'),('Gypsy','WV'),('Hachita','NM'),('Hacienda Heights','CA'),('Hackberry','AZ'),('Hackberry','LA'),('Hackensack','MN'),('Hackensack','NJ'),('Hacker Valley','WV'),('Hackett','AR'),('Hackettstown','NJ'),('Hackleburg','AL'),('Hacksneck','VA'),('Hadar','NE'),('Haddam','CT'),('Haddam','KS'),('Haddock','GA'),('Haddon Heights','NJ'),('Haddon Township','NJ'),('Haddonfield','NJ'),('Hadensville','VA'),('Hadley','MA'),('Hadley','MI'),('Hadley','NY'),('Hadley','PA'),('Hadlyme','CT'),('Hagaman','NY'),('Hagan','GA'),('Hagar Shores','MI'),('Hagarstown','IL'),('Hagarville','AR'),('Hager City','WI'),('Hagerhill','KY'),('Hagerman','ID'),('Hagerman','NM'),('Hagerstown','IN'),('Hagerstown','MD'),('Hague','ND'),('Hague','NY'),('Hague','VA'),('Hahira','GA'),('Hahnville','LA'),('Haigler','NE'),('Haiku','HI'),('Hailesboro','NY'),('Hailey','ID'),('Haileyville','OK'),('Haines','AK'),('Haines','OR'),('Haines City','FL'),('Haines Falls','NY'),('Hainesport','NJ'),('Hakalau','HI'),('Halbur','IA'),('Halcottsville','NY'),('Hale','MI'),('Hale','MO'),('Hale Center','TX'),('Haledon','NJ'),('Haleiwa','HI'),('Hales Corners','WI'),('Halethorpe','MD'),('Haleyville','AL'),('Half Moon Bay','CA'),('Half Way','MO'),('Halfway','OR'),('Halifax','MA'),('Halifax','NC'),('Halifax','PA'),('Halifax','VA'),('Hall','MT'),('Hall','NY'),('Hall Summit','LA'),('Hallam','NE'),('Hallandale','FL'),('Halleck','NV'),('Hallett','OK'),('Hallettsville','TX'),('Halliday','ND'),('Hallie','KY'),('Hallieford','VA'),('Hallock','MN'),('Hallowell','ME'),('Halls','TN'),('Hallsboro','NC'),('Hallstead','PA'),('Hallsville','MO'),('Hallsville','OH'),('Hallsville','TX'),('Halltown','MO'),('Halltown','WV'),('Hallwood','VA'),('Halma','MN'),('Halsey','NE'),('Halsey','OR'),('Halstad','MN'),('Halstead','KS'),('Haltom City','TX'),('Hambleton','WV'),('Hamburg','AR'),('Hamburg','IA'),('Hamburg','IL'),('Hamburg','LA'),('Hamburg','MI'),('Hamburg','MN'),('Hamburg','NJ'),('Hamburg','NY'),('Hamburg','PA'),('Hamden','CT'),('Hamden','NY'),('Hamden','OH'),('Hamel','IL'),('Hamel','MN'),('Hamer','ID'),('Hamer','SC'),('Hamersville','OH'),('Hamill','SD'),('Hamilton','AL'),('Hamilton','CO'),('Hamilton','GA'),('Hamilton','IA'),('Hamilton','IL'),('Hamilton','IN'),('Hamilton','KS'),('Hamilton','MA'),('Hamilton','MI'),('Hamilton','MO'),('Hamilton','MS'),('Hamilton','MT'),('Hamilton','NC'),('Hamilton','ND'),('Hamilton','NY'),('Hamilton','OH'),('Hamilton','PA'),('Hamilton','TX'),('Hamilton','VA'),('Hamilton','WA'),('Hamilton City','CA'),('Hamler','OH'),('Hamlet','IN'),('Hamlet','NC'),('Hamlin','IA'),('Hamlin','NY'),('Hamlin','PA'),('Hamlin','TX'),('Hamlin','WV'),('Hammett','ID'),('Hammon','OK'),('Hammond','IL'),('Hammond','IN'),('Hammond','LA'),('Hammond','MT'),('Hammond','NY'),('Hammond','OR'),('Hammond','WI'),('Hammondsport','NY'),('Hammondsville','OH'),('Hammonton','NJ'),('Hampden','MA'),('Hampden','ME'),('Hampden','ND'),('Hampden Sydney','VA'),('Hampshire','IL'),('Hampshire','TN'),('Hampstead','MD'),('Hampstead','NC'),('Hampstead','NH'),('Hampton','AR'),('Hampton','CT'),('Hampton','FL'),('Hampton','GA'),('Hampton','IA'),('Hampton','IL'),('Hampton','KY'),('Hampton','MN'),('Hampton','NE'),('Hampton','NH'),('Hampton','NJ'),('Hampton','NY'),('Hampton','SC'),('Hampton','TN'),('Hampton','VA'),('Hampton Bays','NY'),('Hampton Falls','NH'),('Hamptonville','NC'),('Hamshire','TX'),('Hamtramck','MI'),('Hana','HI'),('Hanalei','HI'),('Hanamaulu','HI'),('Hanapepe','HI'),('Hanceville','AL'),('Hancock','IA'),('Hancock','MD'),('Hancock','ME'),('Hancock','MI'),('Hancock','MN'),('Hancock','NH'),('Hancock','NY'),('Hancock','VT'),('Hancock','WI'),('Hancocks Bridge','NJ'),('Handley','WV'),('Hanford','CA'),('Hankamer','TX'),('Hankins','NY'),('Hankinson','ND'),('Hanksville','UT'),('Hanley Falls','MN'),('Hanlontown','IA'),('Hanna','IN'),('Hanna','OK'),('Hanna','UT'),('Hanna','WY'),('Hanna City','IL'),('Hannacroix','NY'),('Hannaford','ND'),('Hannah','ND'),('Hannastown','PA'),('Hannawa Falls','NY'),('Hannibal','MO'),('Hannibal','NY'),('Hannibal','OH'),('Hannibal','WI'),('Hanover','CT'),('Hanover','IL'),('Hanover','IN'),('Hanover','KS'),('Hanover','MA'),('Hanover','MD'),('Hanover','ME'),('Hanover','MI'),('Hanover','MN'),('Hanover','NH'),('Hanover','NM'),('Hanover','PA'),('Hanover','VA'),('Hanover','WI'),('Hanover','WV'),('Hanover Park','IL'),('Hanoverton','OH'),('Hansboro','ND'),('Hanscom Afb','MA'),('Hansen','ID'),('Hansford','WV'),('Hanska','MN'),('Hanson','KY'),('Hanson','MA'),('Hanston','KS'),('Hansville','WA'),('Happy','KY'),('Happy','TX'),('Happy Camp','CA'),('Happy Jack','AZ'),('Happy Valley','OR'),('Haralson','GA'),('Harbert','MI'),('Harbeson','DE'),('Harbinger','NC'),('Harbor Beach','MI'),('Harbor City','CA'),('Harbor Springs','MI'),('Harbor View','OH'),('Harborcreek','PA'),('Harborside','ME'),('Harborton','VA'),('Harcourt','IA'),('Hardaway','AL'),('Hardburly','KY'),('Hardeeville','SC'),('Hardenville','MO'),('Hardesty','OK'),('Hardin','IL'),('Hardin','KY'),('Hardin','MO'),('Hardin','MT'),('Hardin','TX'),('Hardinsburg','IN'),('Hardinsburg','KY'),('Hardtner','KS'),('Hardwick','GA'),('Hardwick','MA'),('Hardwick','MN'),('Hardwick','VT'),('Hardy','AR'),('Hardy','IA'),('Hardy','KY'),('Hardy','NE'),('Hardy','VA'),('Hardyville','KY'),('Hardyville','VA'),('Harford','NY'),('Harford','PA'),('Hargill','TX'),('Harker Heights','TX'),('Harkers Island','NC'),('Harlan','IA'),('Harlan','IN'),('Harlan','KY'),('Harleigh','PA'),('Harlem','GA'),('Harlem','MT'),('Harlem Springs','OH'),('Harleton','TX'),('Harleysville','PA'),('Harleyville','SC'),('Harlingen','TX'),('Harlowton','MT'),('Harman','WV'),('Harmans','MD'),('Harmon','IL'),('Harmonsburg','PA'),('Harmony','CA'),('Harmony','IN'),('Harmony','ME'),('Harmony','MN'),('Harmony','NC'),('Harmony','PA'),('Harmony','RI'),('Harned','KY'),('Harold','KY'),('Harper','IA'),('Harper','KS'),('Harper','OR'),('Harper','TX'),('Harper','WV'),('Harper Woods','MI'),('Harpers Ferry','IA'),('Harpers Ferry','WV'),('Harpersfield','NY'),('Harpersville','AL'),('Harperville','MS'),('Harpster','OH'),('Harpswell','ME'),('Harpursville','NY'),('Harrah','OK'),('Harrah','WA'),('Harrell','AR'),('Harrells','NC'),('Harrellsville','NC'),('Harriet','AR'),('Harrietta','MI'),('Harriman','NY'),('Harriman','TN'),('Harrington','DE'),('Harrington','ME'),('Harrington','WA'),('Harrington Park','NJ'),('Harris','IA'),('Harris','MI'),('Harris','MN'),('Harris','MO'),('Harris','NC'),('Harris','NY'),('Harrisburg','AR'),('Harrisburg','IL'),('Harrisburg','MO'),('Harrisburg','NC'),('Harrisburg','NE'),('Harrisburg','OH'),('Harrisburg','OR'),('Harrisburg','PA'),('Harrisburg','SD'),('Harrison','AR'),('Harrison','GA'),('Harrison','ID'),('Harrison','ME'),('Harrison','MI'),('Harrison','MT'),('Harrison','NE'),('Harrison','NJ'),('Harrison','NY'),('Harrison','OH'),('Harrison','SD'),('Harrison','TN'),('Harrison City','PA'),('Harrison Township','MI'),('Harrison Valley','PA'),('Harrisonburg','LA'),('Harrisonburg','VA'),('Harrisonville','MO'),('Harrisonville','NJ'),('Harrisonville','PA'),('Harriston','MS'),('Harristown','IL'),('Harrisville','MI'),('Harrisville','MS'),('Harrisville','NH'),('Harrisville','NY'),('Harrisville','OH'),('Harrisville','PA'),('Harrisville','RI'),('Harrisville','WV'),('Harrod','OH'),('Harrods Creek','KY'),('Harrodsburg','IN'),('Harrodsburg','KY'),('Harrogate','TN'),('Harrold','SD'),('Harrold','TX'),('Harsens Island','MI'),('Harshaw','WI'),('Hart','MI'),('Hart','TX'),('Hartfield','VA'),('Hartford','AL'),('Hartford','AR'),('Hartford','CT'),('Hartford','IA'),('Hartford','IL'),('Hartford','KS'),('Hartford','KY'),('Hartford','MI'),('Hartford','NY'),('Hartford','OH'),('Hartford','SD'),('Hartford','TN'),('Hartford','VT'),('Hartford','WI'),('Hartford','WV'),('Hartford City','IN'),('Hartington','NE'),('Hartland','ME'),('Hartland','MI'),('Hartland','MN'),('Hartland','VT'),('Hartland','WI'),('Hartland Four Corners','VT'),('Hartleton','PA'),('Hartley','IA'),('Hartley','TX'),('Hartline','WA'),('Hartly','DE'),('Hartman','AR'),('Hartman','CO'),('Harts','WV'),('Hartsburg','IL'),('Hartsburg','MO'),('Hartsdale','NY'),('Hartsel','CO'),('Hartselle','AL'),('Hartsfield','GA'),('Hartshorn','MO'),('Hartshorne','OK'),('Hartstown','PA'),('Hartsville','IN'),('Hartsville','SC'),('Hartsville','TN'),('Hartville','MO'),('Hartville','OH'),('Hartville','WY'),('Hartwell','GA'),('Hartwick','IA'),('Hartwick','NY'),('Hartwood','VA'),('Harvard','ID'),('Harvard','IL'),('Harvard','MA'),('Harvard','NE'),('Harvel','IL'),('Harvest','AL'),('Harvey','AR'),('Harvey','IA'),('Harvey','IL'),('Harvey','LA'),('Harvey','ND'),('Harveys Lake','PA'),('Harveysburg','OH'),('Harveyville','KS'),('Harviell','MO'),('Harwich','MA'),('Harwich Port','MA'),('Harwick','PA'),('Harwinton','CT'),('Harwood','MD'),('Harwood','MO'),('Harwood','ND'),('Harwood','TX'),('Harwood Heights','IL'),('Hasbrouck Heights','NJ'),('Haskell','NJ'),('Haskell','OK'),('Haskell','TX'),('Haskins','OH'),('Haslet','TX'),('Haslett','MI'),('Hassell','NC'),('Hastings','FL'),('Hastings','IA'),('Hastings','MI'),('Hastings','MN'),('Hastings','NE'),('Hastings','NY'),('Hastings','OK'),('Hastings','PA'),('Hastings On Hudson','NY'),('Hasty','AR'),('Hasty','CO'),('Haswell','CO'),('Hat Creek','CA'),('Hatboro','PA'),('Hatch','NM'),('Hatch','UT'),('Hatchechubbee','AL'),('Hatfield','AR'),('Hatfield','IN'),('Hatfield','MA'),('Hatfield','MO'),('Hatfield','PA'),('Hathaway','MT'),('Hathaway Pines','CA'),('Hathorne','MA'),('Hatillo','PR'),('Hatley','WI'),('Hatteras','NC'),('Hattiesburg','MS'),('Hattieville','AR'),('Hatton','ND'),('Haubstadt','IN'),('Haugan','MT'),('Haugen','WI'),('Haughton','LA'),('Hauppauge','NY'),('Hauula','HI'),('Havana','AR'),('Havana','FL'),('Havana','IL'),('Havana','KS'),('Havana','ND'),('Havelock','IA'),('Havelock','NC'),('Haven','KS'),('Havensville','KS'),('Haverford','PA'),('Haverhill','IA'),('Haverhill','MA'),('Haverhill','NH'),('Haverhill','OH'),('Haverstraw','NY'),('Havertown','PA'),('Haviland','KS'),('Haviland','OH'),('Havre','MT'),('Havre De Grace','MD'),('Haw River','NC'),('Hawaii National Park','HI'),('Hawaiian Gardens','CA'),('Hawarden','IA'),('Hawesville','KY'),('Hawi','HI'),('Hawk Point','MO'),('Hawk Run','PA'),('Hawk Springs','WY'),('Hawkeye','IA'),('Hawkins','TX'),('Hawkins','WI'),('Hawkinsville','GA'),('Hawks','MI'),('Hawley','MN'),('Hawley','PA'),('Hawley','TX'),('Hawleyville','CT'),('Haworth','NJ'),('Haworth','OK'),('Hawthorn','PA'),('Hawthorne','CA'),('Hawthorne','FL'),('Hawthorne','NJ'),('Hawthorne','NV'),('Hawthorne','NY'),('Hawthorne','WI'),('Haxtun','CO'),('Hay','WA'),('Hay Springs','NE'),('Hayden','AL'),('Hayden','AZ'),('Hayden','CO'),('Hayden','ID'),('Hayden','IN'),('Haydenville','MA'),('Haydenville','OH'),('Hayes','LA'),('Hayes','SD'),('Hayes','VA'),('Hayes Center','NE'),('Hayesville','IA'),('Hayesville','NC'),('Hayesville','OH'),('Hayfield','MN'),('Hayfork','CA'),('Haymarket','VA'),('Haynes','AR'),('Haynesville','LA'),('Haynesville','VA'),('Hayneville','AL'),('Hays','KS'),('Hays','MT'),('Hays','NC'),('Haysi','VA'),('Haysville','KS'),('Hayti','MO'),('Hayti','SD'),('Hayward','CA'),('Hayward','MN'),('Hayward','WI'),('Haywood','VA'),('Haywood','WV'),('Hazard','KY'),('Hazard','NE'),('Hazel','KY'),('Hazel','SD'),('Hazel Crest','IL'),('Hazel Green','AL'),('Hazel Green','KY'),('Hazel Green','WI'),('Hazel Hurst','PA'),('Hazel Park','MI'),('Hazelhurst','WI'),('Hazelton','ID'),('Hazelton','KS'),('Hazelton','ND'),('Hazelwood','MO'),('Hazelwood','NC'),('Hazen','AR'),('Hazen','ND'),('Hazlehurst','GA'),('Hazlehurst','MS'),('Hazlet','NJ'),('Hazleton','IA'),('Hazleton','IN'),('Hazleton','PA'),('Head Waters','VA'),('Headland','AL'),('Headrick','OK'),('Heafford Junction','WI'),('Healdsburg','CA'),('Healdton','OK'),('Healy','AK'),('Healy','KS'),('Hearne','TX'),('Heart Butte','MT'),('Heartwell','NE'),('Heaters','WV'),('Heath','MA'),('Heath','OH'),('Heath Springs','SC'),('Heathsville','VA'),('Heavener','OK'),('Hebbronville','TX'),('Heber','AZ'),('Heber','CA'),('Heber City','UT'),('Heber Springs','AR'),('Hebo','OR'),('Hebron','CT'),('Hebron','IL'),('Hebron','IN'),('Hebron','KY'),('Hebron','MD'),('Hebron','ME'),('Hebron','ND'),('Hebron','NE'),('Hebron','NH'),('Hebron','OH'),('Hecker','IL'),('Hecla','SD'),('Hector','AR'),('Hector','MN'),('Hector','NY'),('Hedgesville','WV'),('Hedley','TX'),('Hedrick','IA'),('Heflin','AL'),('Heflin','LA'),('Hegins','PA'),('Heidelberg','KY'),('Heidelberg','MS'),('Heidenheimer','TX'),('Heidrick','KY'),('Heilwood','PA'),('Heiskell','TN'),('Heislerville','NJ'),('Heisson','WA'),('Helen','GA'),('Helen','MD'),('Helen','WV'),('Helena','AL'),('Helena','AR'),('Helena','GA'),('Helena','MO'),('Helena','MT'),('Helena','NY'),('Helena','OH'),('Helena','OK'),('Helendale','CA'),('Helenville','WI'),('Helenwood','TN'),('Helix','OR'),('Hellertown','PA'),('Hellier','KY'),('Helm','CA'),('Helmetta','NJ'),('Helmsburg','IN'),('Helmville','MT'),('Helotes','TX'),('Helper','UT'),('Helton','KY'),('Heltonville','IN'),('Helvetia','WV'),('Hematite','MO'),('Hemet','CA'),('Hemingford','NE'),('Hemingway','SC'),('Hemlock','IN'),('Hemlock','MI'),('Hemlock','NY'),('Hemphill','TX'),('Hemphill','WV'),('Hempstead','NY'),('Hempstead','TX'),('Henagar','AL'),('Henderson','AR'),('Henderson','CO'),('Henderson','IA'),('Henderson','IL'),('Henderson','KY'),('Henderson','MD'),('Henderson','MI'),('Henderson','MN'),('Henderson','NC'),('Henderson','NE'),('Henderson','NV'),('Henderson','NY'),('Henderson','TN'),('Henderson','TX'),('Henderson','WV'),('Henderson Harbor','NY'),('Hendersonville','NC'),('Hendersonville','PA'),('Hendersonville','TN'),('Hendley','NE'),('Hendricks','MN'),('Hendricks','WV'),('Hendrix','OK'),('Hendrum','MN'),('Henefer','UT'),('Henlawson','WV'),('Henley','MO'),('Hennepin','IL'),('Hennepin','OK'),('Hennessey','OK'),('Henniker','NH'),('Henning','IL'),('Henning','MN'),('Henning','TN'),('Henrico','NC'),('Henrietta','MO'),('Henrietta','NC'),('Henrietta','NY'),('Henrietta','TX'),('Henriette','MN'),('Henrieville','UT'),('Henry','IL'),('Henry','SD'),('Henry','TN'),('Henry','VA'),('Henryetta','OK'),('Henryville','IN'),('Henryville','PA'),('Hensel','ND'),('Hensley','AR'),('Hensley','WV'),('Hensonville','NY'),('Hephzibah','GA'),('Hepler','KS'),('Heppner','OR'),('Hepzibah','WV'),('Herald','CA'),('Herbster','WI'),('Herculaneum','MO'),('Hercules','CA'),('Hereford','AZ'),('Hereford','CO'),('Hereford','OR'),('Hereford','PA'),('Hereford','TX'),('Herington','KS'),('Herkimer','NY'),('Herlong','CA'),('Herman','MN'),('Herman','NE'),('Herman','PA'),('Hermann','MO'),('Hermansville','MI'),('Hermanville','MS'),('Herminie','PA'),('Hermiston','OR'),('Hermitage','AR'),('Hermitage','MO'),('Hermitage','PA'),('Hermitage','TN'),('Hermleigh','TX'),('Hermon','NY'),('Hermosa','SD'),('Hermosa Beach','CA'),('Hernandez','NM'),('Hernando','FL'),('Hernando','MS'),('Herndon','KS'),('Herndon','KY'),('Herndon','PA'),('Herndon','VA'),('Herndon','WV'),('Hernshaw','WV'),('Herod','IL'),('Heron','MT'),('Heron Lake','MN'),('Herreid','SD'),('Herrick','IL'),('Herrick','SD'),('Herrick Center','PA'),('Herriman','UT'),('Herrin','IL'),('Herron','MI'),('Herscher','IL'),('Hersey','MI'),('Hershey','NE'),('Hershey','PA'),('Hertel','WI'),('Hertford','NC'),('Hesperia','CA'),('Hesperia','MI'),('Hesperus','CO'),('Hessel','MI'),('Hessmer','LA'),('Hesston','KS'),('Hesston','PA'),('Hestand','KY'),('Hester','LA'),('Heth','AR'),('Hettick','IL'),('Hettinger','ND'),('Heuvelton','NY'),('Hewett','WV'),('Hewitt','MN'),('Hewitt','NJ'),('Hewitt','TX'),('Hewitt','WI'),('Hewlett','NY'),('Hext','TX'),('Heyburn','ID'),('Heyworth','IL'),('Hi Hat','KY'),('Hialeah','FL'),('Hiawassee','GA'),('Hiawatha','IA'),('Hiawatha','KS'),('Hiawatha','WV'),('Hibbing','MN'),('Hibbs','PA'),('Hibernia','NJ'),('Hickam Afb','HI'),('Hickman','CA'),('Hickman','KY'),('Hickman','NE'),('Hickman','TN'),('Hickory','KY'),('Hickory','MS'),('Hickory','NC'),('Hickory','PA'),('Hickory Corners','MI'),('Hickory Flat','MS'),('Hickory Grove','SC'),('Hickory Hills','IL'),('Hickory Plains','AR'),('Hickory Ridge','AR'),('Hickory Valley','TN'),('Hicksville','NY'),('Hicksville','OH'),('Hico','TX'),('Hico','WV'),('Hidalgo','IL'),('Hidalgo','TX'),('Hidden Valley','PA'),('Hidden Valley Lake','CA'),('Hiddenite','NC'),('Hideaway Hls','OH'),('Higbee','MO'),('Higden','AR'),('Higdon','AL'),('Higganum','CT'),('Higgins','TX'),('Higgins Lake','MI'),('Higginson','AR'),('Higginsport','OH'),('Higginsville','MO'),('High Bridge','NJ'),('High Bridge','WI'),('High Falls','NY'),('High Hill','MO'),('High Island','TX'),('High Point','MO'),('High Point','NC'),('High Ridge','MO'),('High Rolls Mountain Park','NM'),('High Shoals','GA'),('High Shoals','NC'),('High Springs','FL'),('High View','WV'),('Highfalls','NC'),('Highgate Center','VT'),('Highgate Springs','VT'),('Highland','CA'),('Highland','IL'),('Highland','IN'),('Highland','KS'),('Highland','MD'),('Highland','MI'),('Highland','NY'),('Highland','OH'),('Highland','WI'),('Highland City','FL'),('Highland Falls','NY'),('Highland Home','AL'),('Highland Lake','NY'),('Highland Lakes','NJ'),('Highland Mills','NY'),('Highland Park','IL'),('Highland Park','MI'),('Highland Park','NJ'),('Highland Springs','VA'),('Highlands','NC'),('Highlands','NJ'),('Highlands','TX'),('Highlandville','IA'),('Highlandville','MO'),('Highmore','SD'),('Highmount','NY'),('Highspire','PA'),('Hightstown','NJ'),('Highwood','IL'),('Highwood','MT'),('Higley','AZ'),('Hiko','NV'),('Hiland','WY'),('Hilbert','WI'),('Hilda','SC'),('Hildale','UT'),('Hildebran','NC'),('Hildreth','NE'),('Hilger','MT'),('Hilham','TN'),('Hill','NH'),('Hill Afb','UT'),('Hill City','ID'),('Hill City','KS'),('Hill City','MN'),('Hill City','SD'),('Hillburn','NY'),('Hiller','PA'),('Hilliard','FL'),('Hilliard','OH'),('Hilliards','PA'),('Hillister','TX'),('Hillman','MI'),('Hillman','MN'),('Hillpoint','WI'),('Hillrose','CO'),('Hills','IA'),('Hills','MN'),('Hillsboro','AL'),('Hillsboro','GA'),('Hillsboro','IA'),('Hillsboro','IL'),('Hillsboro','IN'),('Hillsboro','KS'),('Hillsboro','KY'),('Hillsboro','MD'),('Hillsboro','MO'),('Hillsboro','MS'),('Hillsboro','ND'),('Hillsboro','NM'),('Hillsboro','OH'),('Hillsboro','OR'),('Hillsboro','TN'),('Hillsboro','TX'),('Hillsboro','WI'),('Hillsboro','WV'),('Hillsborough','NC'),('Hillsborough','NH'),('Hillsborough','NJ'),('Hillsdale','IL'),('Hillsdale','IN'),('Hillsdale','KS'),('Hillsdale','MI'),('Hillsdale','NJ'),('Hillsdale','NY'),('Hillsdale','OK'),('Hillsdale','PA'),('Hillsdale','WY'),('Hillsgrove','PA'),('Hillside','CO'),('Hillside','IL'),('Hillside','NJ'),('Hillsville','PA'),('Hillsville','VA'),('Hilltop','WV'),('Hilltown','PA'),('Hillview','IL'),('Hillview','KY'),('Hilmar','CA'),('Hilo','HI'),('Hilton','NY'),('Hilton Head Island','SC'),('Hiltons','VA'),('Hima','KY'),('Himrod','NY'),('Hinckley','IL'),('Hinckley','ME'),('Hinckley','MN'),('Hinckley','NY'),('Hinckley','OH'),('Hinckley','UT'),('Hindman','KY'),('Hindsboro','IL'),('Hindsville','AR'),('Hines','IL'),('Hines','MN'),('Hines','OR'),('Hinesburg','VT'),('Hineston','LA'),('Hinesville','GA'),('Hingham','MA'),('Hingham','MT'),('Hingham','WI'),('Hinkle','KY'),('Hinkley','CA'),('Hinsdale','IL'),('Hinsdale','MA'),('Hinsdale','MT'),('Hinsdale','NH'),('Hinsdale','NY'),('Hinton','IA'),('Hinton','OK'),('Hinton','VA'),('Hinton','WV'),('Hiram','GA'),('Hiram','ME'),('Hiram','OH'),('Hiseville','KY'),('Hitchcock','OK'),('Hitchcock','SD'),('Hitchcock','TX'),('Hitchins','KY'),('Hitchita','OK'),('Hitterdal','MN'),('Hiwasse','AR'),('Hiwassee','VA'),('Hixson','TN'),('Hixton','WI'),('Ho Ho Kus','NJ'),('Hoagland','IN'),('Hobart','IN'),('Hobart','NY'),('Hobart','OK'),('Hobart','WA'),('Hobbs','IN'),('Hobbs','NM'),('Hobbsville','NC'),('Hobe Sound','FL'),('Hobgood','NC'),('Hoboken','GA'),('Hoboken','NJ'),('Hobson','MT'),('Hobson','TX'),('Hobucken','NC'),('Hochheim','TX'),('Hockessin','DE'),('Hockingport','OH'),('Hockley','TX'),('Hodge','LA'),('Hodgen','OK'),('Hodgenville','KY'),('Hodges','AL'),('Hodges','SC'),('Hoehne','CO'),('Hoffman','IL'),('Hoffman','MN'),('Hoffman','NC'),('Hoffman Estates','IL'),('Hoffmeister','NY'),('Hogansburg','NY'),('Hogansville','GA'),('Hogeland','MT'),('Hohenwald','TN'),('Hoisington','KS'),('Hokah','MN'),('Holabird','SD'),('Holbrook','AZ'),('Holbrook','ID'),('Holbrook','MA'),('Holbrook','NE'),('Holbrook','NY'),('Holbrook','PA'),('Holcomb','IL'),('Holcomb','KS'),('Holcomb','MO'),('Holcomb','MS'),('Holcombe','WI'),('Holden','LA'),('Holden','MA'),('Holden','ME'),('Holden','MO'),('Holden','UT'),('Holden','WV'),('Holdenville','OK'),('Holder','FL'),('Holderness','NH'),('Holdingford','MN'),('Holdrege','NE'),('Holgate','OH'),('Holicong','PA'),('Holiday','FL'),('Holladay','TN'),('Holland','IA'),('Holland','IN'),('Holland','KY'),('Holland','MA'),('Holland','MI'),('Holland','MN'),('Holland','MO'),('Holland','NY'),('Holland','OH'),('Holland','TX'),('Holland Patent','NY'),('Hollandale','MN'),('Hollandale','MS'),('Hollandale','WI'),('Hollansburg','OH'),('Hollenberg','KS'),('Holley','NY'),('Holliday','MO'),('Holliday','TX'),('Hollidaysburg','PA'),('Hollins','AL'),('Hollis','NH'),('Hollis','NY'),('Hollis','OK'),('Hollis Center','ME'),('Hollister','CA'),('Hollister','FL'),('Hollister','MO'),('Hollister','NC'),('Hollister','OK'),('Holliston','MA'),('Holloman Air Force Base','NM'),('Hollow Rock','TN'),('Holloway','MN'),('Holloway','OH'),('Hollowville','NY'),('Hollsopple','PA'),('Holly','CO'),('Holly','MI'),('Holly Bluff','MS'),('Holly Grove','AR'),('Holly Hill','SC'),('Holly Pond','AL'),('Holly Ridge','MS'),('Holly Ridge','NC'),('Holly Springs','GA'),('Holly Springs','MS'),('Holly Springs','NC'),('Hollytree','AL'),('Hollywood','AL'),('Hollywood','FL'),('Hollywood','MD'),('Hollywood','SC'),('Holman','NM'),('Holmdel','NJ'),('Holmen','WI'),('Holmes','NY'),('Holmes','PA'),('Holmes Beach','FL'),('Holmes City','MN'),('Holmes Mill','KY'),('Holmesville','OH'),('Holstein','IA'),('Holstein','NE'),('Holt','CA'),('Holt','FL'),('Holt','MI'),('Holt','MO'),('Holton','IN'),('Holton','KS'),('Holton','MI'),('Holts Summit','MO'),('Holtsville','NY'),('Holtville','CA'),('Holtwood','PA'),('Holualoa','HI'),('Holy City','CA'),('Holy Cross','AK'),('Holy Cross','IA'),('Holy Trinity','AL'),('Holyoke','CO'),('Holyoke','MA'),('Holyoke','MN'),('Holyrood','KS'),('Home','KS'),('Home','PA'),('Homedale','ID'),('Homelake','CO'),('Homeland','CA'),('Homeland','FL'),('Homer','AK'),('Homer','GA'),('Homer','IL'),('Homer','IN'),('Homer','LA'),('Homer','MI'),('Homer','MN'),('Homer','NE'),('Homer','NY'),('Homer','OH'),('Homer City','PA'),('Homer Glen','IL'),('Homerville','GA'),('Homerville','OH'),('Homestead','FL'),('Homestead','IA'),('Homestead','MT'),('Homestead','PA'),('Hometown','IL'),('Hometown','WV'),('Homewood','CA'),('Homewood','IL'),('Homeworth','OH'),('Hominy','OK'),('Homosassa','FL'),('Homosassa Springs','FL'),('Honaker','VA'),('Honaunau','HI'),('Hondo','NM'),('Hondo','TX'),('Honea Path','SC'),('Honeoye','NY'),('Honeoye Falls','NY'),('Honesdale','PA'),('Honey Brook','PA'),('Honey Creek','IA'),('Honey Creek','WI'),('Honey Grove','PA'),('Honey Grove','TX'),('Honeydew','CA'),('Honeyville','UT'),('Honokaa','HI'),('Honolulu','HI'),('Honomu','HI'),('Honor','MI'),('Honoraville','AL'),('Hood','CA'),('Hood','VA'),('Hood River','OR'),('Hoodsport','WA'),('Hooker','OK'),('Hookerton','NC'),('Hooks','TX'),('Hooksett','NH'),('Hookstown','PA'),('Hoolehua','HI'),('Hoonah','AK'),('Hoopa','CA'),('Hooper','CO'),('Hooper','NE'),('Hooper','UT'),('Hooper','WA'),('Hooper Bay','AK'),('Hoopeston','IL'),('Hoople','ND'),('Hooppole','IL'),('Hoosick','NY'),('Hoosick Falls','NY'),('Hooven','OH'),('Hooversville','PA'),('Hop Bottom','PA'),('Hopatcong','NJ'),('Hope','AK'),('Hope','AR'),('Hope','ID'),('Hope','IN'),('Hope','KS'),('Hope','KY'),('Hope','ME'),('Hope','MI'),('Hope','MN'),('Hope','ND'),('Hope','NJ'),('Hope','NM'),('Hope','RI'),('Hope Hull','AL'),('Hope Mills','NC'),('Hope Valley','RI'),('Hopedale','IL'),('Hopedale','MA'),('Hopedale','OH'),('Hopeland','PA'),('Hopeton','OK'),('Hopewell','NJ'),('Hopewell','OH'),('Hopewell','PA'),('Hopewell','VA'),('Hopewell Junction','NY'),('Hopkins','MI'),('Hopkins','MN'),('Hopkins','MO'),('Hopkins','SC'),('Hopkins Park','IL'),('Hopkinsville','KY'),('Hopkinton','IA'),('Hopkinton','MA'),('Hopkinton','RI'),('Hopland','CA'),('Hopwood','PA'),('Hoquiam','WA'),('Horace','ND'),('Horatio','AR'),('Horatio','SC'),('Hordville','NE'),('Horicon','WI'),('Hormigueros','PR'),('Horn Lake','MS'),('Hornbeak','TN'),('Hornbeck','LA'),('Hornbrook','CA'),('Hornell','NY'),('Horner','WV'),('Hornersville','MO'),('Hornick','IA'),('Hornitos','CA'),('Hornsby','TN'),('Horntown','VA'),('Horse Branch','KY'),('Horse Cave','KY'),('Horse Creek','WY'),('Horse Shoe','NC'),('Horseheads','NY'),('Horsepen','VA'),('Horseshoe Bay','TX'),('Horseshoe Beach','FL'),('Horseshoe Bend','AR'),('Horseshoe Bend','ID'),('Horsham','PA'),('Hortense','GA'),('Horton','AL'),('Horton','KS'),('Horton','MI'),('Hortonville','NY'),('Hortonville','WI'),('Hoschton','GA'),('Hosford','FL'),('Hoskins','NE'),('Hoskinston','KY'),('Hosmer','SD'),('Hospers','IA'),('Hosston','LA'),('Hostetter','PA'),('Hot Springs','MT'),('Hot Springs','NC'),('Hot Springs','SD'),('Hot Springs','VA'),('Hot Springs National Park','AR'),('Hot Springs Village','AR'),('Hot Sulphur Springs','CO'),('Hotchkiss','CO'),('Hotevilla','AZ'),('Houck','AZ'),('Houghton','IA'),('Houghton','MI'),('Houghton','NY'),('Houghton','SD'),('Houghton Lake','MI'),('Houghton Lake Heights','MI'),('Houlka','MS'),('Houlton','ME'),('Houlton','WI'),('Houma','LA'),('Housatonic','MA'),('House','NM'),('House Springs','MO'),('Houston','AK'),('Houston','AL'),('Houston','AR'),('Houston','DE'),('Houston','MN'),('Houston','MO'),('Houston','MS'),('Houston','OH'),('Houston','PA'),('Houston','TX'),('Houstonia','MO'),('Houtzdale','PA'),('Hoven','SD'),('Hovland','MN'),('Howard','CO'),('Howard','GA'),('Howard','KS'),('Howard','OH'),('Howard','PA'),('Howard','SD'),('Howard Beach','NY'),('Howard City','MI'),('Howard Lake','MN'),('Howardsville','VA'),('Howe','ID'),('Howe','IN'),('Howe','OK'),('Howe','TX'),('Howell','MI'),('Howell','NJ'),('Howell','UT'),('Howells','NE'),('Howells','NY'),('Howes','SD'),('Howes Cave','NY'),('Howey In The Hills','FL'),('Howland','ME'),('Hoxie','AR'),('Hoxie','KS'),('Hoyleton','IL'),('Hoyt','KS'),('Hoyt','OK'),('Hoyt Lakes','MN'),('Hoytville','OH'),('Huachuca City','AZ'),('Hualapai','AZ'),('Hubbard','IA'),('Hubbard','NE'),('Hubbard','OH'),('Hubbard','OR'),('Hubbard','TX'),('Hubbard Lake','MI'),('Hubbardston','MA'),('Hubbardston','MI'),('Hubbardsville','NY'),('Hubbell','MI'),('Hubbell','NE'),('Hubert','NC'),('Hubertus','WI'),('Huddleston','VA'),('Huddy','KY'),('Hudgins','VA'),('Hudson','CO'),('Hudson','FL'),('Hudson','IA'),('Hudson','IL'),('Hudson','IN'),('Hudson','KS'),('Hudson','KY'),('Hudson','MA'),('Hudson','ME'),('Hudson','MI'),('Hudson','NC'),('Hudson','NH'),('Hudson','NY'),('Hudson','OH'),('Hudson','SD'),('Hudson','WI'),('Hudson','WY'),('Hudson Falls','NY'),('Hudsonville','MI'),('Huey','IL'),('Hueysville','KY'),('Huffman','TX'),('Hufsmith','TX'),('Huger','SC'),('Huggins','MO'),('Hughes','AK'),('Hughes','AR'),('Hughes Springs','TX'),('Hugheston','WV'),('Hughesville','MD'),('Hughesville','MO'),('Hughesville','PA'),('Hughson','CA'),('Hughsonville','NY'),('Hugo','CO'),('Hugo','MN'),('Hugo','OK'),('Hugoton','KS'),('Huguenot','NY'),('Hulbert','MI'),('Hulbert','OK'),('Hulen','KY'),('Hulett','WY'),('Huletts Landing','NY'),('Hull','GA'),('Hull','IA'),('Hull','IL'),('Hull','MA'),('Hull','TX'),('Hulls Cove','ME'),('Humacao','PR'),('Humansville','MO'),('Humarock','MA'),('Humbird','WI'),('Humble','TX'),('Humboldt','AZ'),('Humboldt','IA'),('Humboldt','IL'),('Humboldt','KS'),('Humboldt','MN'),('Humboldt','NE'),('Humboldt','SD'),('Humboldt','TN'),('Hume','CA'),('Hume','IL'),('Hume','MO'),('Hume','NY'),('Hume','VA'),('Humeston','IA'),('Hummels Wharf','PA'),('Hummelstown','PA'),('Humnoke','AR'),('Humphrey','AR'),('Humphrey','NE'),('Humphreys','MO'),('Humptulips','WA'),('Hundred','WV'),('Hungerford','TX'),('Hungry Horse','MT'),('Hunker','PA'),('Hunlock Creek','PA'),('Hunnewell','MO'),('Hunt','NY'),('Hunt','TX'),('Hunt Valley','MD'),('Hunter','AR'),('Hunter','KS'),('Hunter','ND'),('Hunter','NY'),('Hunter','OK'),('Hunters','WA'),('Huntersville','NC'),('Huntertown','IN'),('Huntingburg','IN'),('Huntingdon','PA'),('Huntingdon','TN'),('Huntingdon Valley','PA'),('Huntington','AR'),('Huntington','IN'),('Huntington','MA'),('Huntington','NY'),('Huntington','OR'),('Huntington','TX'),('Huntington','UT'),('Huntington','VT'),('Huntington','WV'),('Huntington Beach','CA'),('Huntington Mills','PA'),('Huntington Park','CA'),('Huntington Station','NY'),('Huntington Woods','MI'),('Huntingtown','MD'),('Huntland','TN'),('Huntley','IL'),('Huntley','MN'),('Huntley','MT'),('Huntley','WY'),('Huntly','VA'),('Huntsburg','OH'),('Huntsville','AL'),('Huntsville','AR'),('Huntsville','IL'),('Huntsville','MO'),('Huntsville','OH'),('Huntsville','TN'),('Huntsville','TX'),('Huntsville','UT'),('Hurdland','MO'),('Hurdle Mills','NC'),('Hurdsfield','ND'),('Hurlburt Field','FL'),('Hurley','MO'),('Hurley','MS'),('Hurley','NM'),('Hurley','NY'),('Hurley','SD'),('Hurley','VA'),('Hurley','WI'),('Hurleyville','NY'),('Hurlock','MD'),('Huron','CA'),('Huron','IN'),('Huron','OH'),('Huron','SD'),('Huron','TN'),('Hurricane','UT'),('Hurricane','WV'),('Hurricane Mills','TN'),('Hurst','IL'),('Hurst','TX'),('Hurt','VA'),('Hurtsboro','AL'),('Huslia','AK'),('Huson','MT'),('Husser','LA'),('Hustisford','WI'),('Hustle','VA'),('Hustler','WI'),('Huston','ID'),('Hustontown','PA'),('Hustonville','KY'),('Husum','WA'),('Hutchins','TX'),('Hutchinson','KS'),('Hutchinson','MN'),('Hutchinson','PA'),('Hutsonville','IL'),('Huttig','AR'),('Hutto','TX'),('Huttonsville','WV'),('Huxford','AL'),('Huxley','IA'),('Hyampom','CA'),('Hyannis','MA'),('Hyannis','NE'),('Hyannis Port','MA'),('Hyattsville','MD'),('Hyattville','WY'),('Hydaburg','AK'),('Hyde','PA'),('Hyde Park','MA'),('Hyde Park','NY'),('Hyde Park','PA'),('Hyde Park','UT'),('Hyde Park','VT'),('Hyden','KY'),('Hyder','AK'),('Hydes','MD'),('Hydesville','CA'),('Hydetown','PA'),('Hydeville','VT'),('Hydro','OK'),('Hye','TX'),('Hygiene','CO'),('Hymera','IN'),('Hyndman','PA'),('Hyner','PA'),('Hyrum','UT'),('Hysham','MT'),('Iaeger','WV'),('Ibapah','UT'),('Iberia','MO'),('Iberia','OH'),('Icard','NC'),('Ickesburg','PA'),('Ida','AR'),('Ida','LA'),('Ida','MI'),('Ida Grove','IA'),('Idabel','OK'),('Idaho City','ID'),('Idaho Falls','ID'),('Idaho Springs','CO'),('Idalia','CO'),('Idalou','TX'),('Idamay','WV'),('Idanha','OR'),('Idaville','IN'),('Idaville','PA'),('Ideal','GA'),('Ideal','SD'),('Ider','AL'),('Idledale','CO'),('Idlewild','MI'),('Idlewild','TN'),('Idleyld Park','OR'),('Idyllwild','CA'),('Ignacio','CO'),('Igo','CA'),('Ihlen','MN'),('Ijamsville','MD'),('Ikes Fork','WV'),('Ila','GA'),('Ilfeld','NM'),('Iliamna','AK'),('Iliff','CO'),('Ilion','NY'),('Illinois City','IL'),('Illiopolis','IL'),('Ilwaco','WA'),('Imbler','OR'),('Imboden','AR'),('Imlay','NV'),('Imlay City','MI'),('Imlaystown','NJ'),('Imler','PA'),('Immaculata','PA'),('Immokalee','FL'),('Imnaha','OR'),('Imogene','IA'),('Imperial','CA'),('Imperial','MO'),('Imperial','NE'),('Imperial','PA'),('Imperial','TX'),('Imperial Beach','CA'),('Ina','IL'),('Inavale','NE'),('Inchelium','WA'),('Incline Village','NV'),('Independence','CA'),('Independence','IA'),('Independence','KS'),('Independence','KY'),('Independence','LA'),('Independence','MO'),('Independence','MS'),('Independence','OH'),('Independence','OR'),('Independence','VA'),('Independence','WI'),('Independence','WV'),('Index','WA'),('Indiahoma','OK'),('Indialantic','FL'),('Indian','AK'),('Indian Head','MD'),('Indian Head','PA'),('Indian Hills','CO'),('Indian Lake','NY'),('Indian Lake Estates','FL'),('Indian Mound','TN'),('Indian River','MI'),('Indian Rocks Beach','FL'),('Indian Springs','NV'),('Indian Trail','NC'),('Indian Valley','ID'),('Indian Valley','VA'),('Indian Wells','AZ'),('Indian Wells','CA'),('Indiana','PA'),('Indianapolis','IN'),('Indianola','IA'),('Indianola','IL'),('Indianola','MS'),('Indianola','NE'),('Indianola','OK'),('Indianola','PA'),('Indianola','WA'),('Indiantown','FL'),('Indio','CA'),('Indore','WV'),('Industry','IL'),('Industry','PA'),('Industry','TX'),('Inez','KY'),('Inez','TX'),('Ingalls','IN'),('Ingalls','KS'),('Ingalls','MI'),('Inglefield','IN'),('Ingleside','IL'),('Ingleside','MD'),('Ingleside','TX'),('Inglewood','CA'),('Inglis','FL'),('Ingomar','MT'),('Ingomar','PA'),('Ingraham','IL'),('Ingram','KY'),('Ingram','TX'),('Inkom','ID'),('Inkster','MI'),('Inkster','ND'),('Inland','NE'),('Inlet','NY'),('Inman','KS'),('Inman','NE'),('Inman','SC'),('Innis','LA'),('Inola','OK'),('Institute','WV'),('Intercession City','FL'),('Intercourse','PA'),('Interior','SD'),('Interlachen','FL'),('Interlaken','NY'),('Interlochen','MI'),('International Falls','MN'),('Intervale','NH'),('Inver Grove Heights','MN'),('Inverness','CA'),('Inverness','FL'),('Inverness','MS'),('Inverness','MT'),('Inwood','IA'),('Inwood','NY'),('Inwood','WV'),('Inyokern','CA'),('Iola','KS'),('Iola','TX'),('Iola','WI'),('Iona','ID'),('Iona','MN'),('Ione','CA'),('Ione','OR'),('Ione','WA'),('Ionia','IA'),('Ionia','MI'),('Ionia','MO'),('Ionia','NY'),('Iota','LA'),('Iowa','LA'),('Iowa City','IA'),('Iowa Falls','IA'),('Iowa Park','TX'),('Ipava','IL'),('Ipswich','MA'),('Ipswich','SD'),('Ira','IA'),('Ira','TX'),('Iraan','TX'),('Irasburg','VT'),('Iredell','TX'),('Ireland','IN'),('Ireland','WV'),('Irene','SD'),('Irene','TX'),('Ireton','IA'),('Irma','WI'),('Irmo','SC'),('Iron','MN'),('Iron Belt','WI'),('Iron City','GA'),('Iron City','TN'),('Iron Gate','VA'),('Iron Mountain','MI'),('Iron Ridge','WI'),('Iron River','MI'),('Iron River','WI'),('Iron Springs','AZ'),('Iron Station','NC'),('Irondale','MO'),('Irondale','OH'),('Ironia','NJ'),('Irons','MI'),('Ironside','OR'),('Ironsides','MD'),('Ironton','MN'),('Ironton','MO'),('Ironton','OH'),('Ironwood','MI'),('Iroquois','IL'),('Iroquois','SD'),('Irrigon','OR'),('Irvine','CA'),('Irvine','KY'),('Irvine','PA'),('Irving','IL'),('Irving','NY'),('Irving','TX'),('Irvington','AL'),('Irvington','IL'),('Irvington','KY'),('Irvington','NJ'),('Irvington','NY'),('Irvington','VA'),('Irvona','PA'),('Irwin','IA'),('Irwin','ID'),('Irwin','OH'),('Irwin','PA'),('Irwinton','GA'),('Irwinville','GA'),('Isaban','WV'),('Isabel','KS'),('Isabel','SD'),('Isabela','PR'),('Isabella','MN'),('Isabella','MO'),('Isabella','OK'),('Isabella','PA'),('Isanti','MN'),('Iselin','NJ'),('Ishpeming','MI'),('Islamorada','FL'),('Island','KY'),('Island City','KY'),('Island Falls','ME'),('Island Grove','FL'),('Island Heights','NJ'),('Island Lake','IL'),('Island Park','ID'),('Island Park','NY'),('Island Pond','VT'),('Islandia','NY'),('Islandton','SC'),('Isle','MN'),('Isle Au Haut','ME'),('Isle La Motte','VT'),('Isle Of Palms','SC'),('Isle Of Springs','ME'),('Isle Of Wight','VA'),('Isle Saint George','OH'),('Islesboro','ME'),('Islesford','ME'),('Isleta','NM'),('Isleton','CA'),('Islip','NY'),('Islip Terrace','NY'),('Ismay','MT'),('Isola','MS'),('Isom','KY'),('Isonville','KY'),('Issaquah','WA'),('Issue','MD'),('Istachatta','FL'),('Italy','TX'),('Itasca','IL'),('Itasca','TX'),('Ithaca','MI'),('Ithaca','NE'),('Ithaca','NY'),('Itmann','WV'),('Itta Bena','MS'),('Iuka','IL'),('Iuka','KS'),('Iuka','MS'),('Iva','SC'),('Ivan','AR'),('Ivanhoe','CA'),('Ivanhoe','MN'),('Ivanhoe','NC'),('Ivanhoe','TX'),('Ivanhoe','VA'),('Ivel','KY'),('Ivesdale','IL'),('Ivins','UT'),('Ivor','VA'),('Ivoryton','CT'),('Ivy','VA'),('Ivydale','WV'),('Ixonia','WI'),('Jachin','AL'),('Jack','AL'),('Jackhorn','KY'),('Jackman','ME'),('Jackpot','NV'),('Jacks Creek','TN'),('Jacksboro','TN'),('Jacksboro','TX'),('Jackson','AL'),('Jackson','CA'),('Jackson','GA'),('Jackson','KY'),('Jackson','LA'),('Jackson','MI'),('Jackson','MN'),('Jackson','MO'),('Jackson','MS'),('Jackson','MT'),('Jackson','NC'),('Jackson','NE'),('Jackson','NH'),('Jackson','NJ'),('Jackson','OH'),('Jackson','PA'),('Jackson','SC'),('Jackson','TN'),('Jackson','WI'),('Jackson','WY'),('Jackson Center','OH'),('Jackson Center','PA'),('Jackson Heights','NY'),('Jackson Springs','NC'),('Jacksonboro','SC'),('Jacksonburg','WV'),('Jacksonport','AR'),('Jacksons Gap','AL'),('Jacksontown','OH'),('Jacksonville','AL'),('Jacksonville','AR'),('Jacksonville','FL'),('Jacksonville','GA'),('Jacksonville','IL'),('Jacksonville','MO'),('Jacksonville','NC'),('Jacksonville','NY'),('Jacksonville','OH'),('Jacksonville','OR'),('Jacksonville','TX'),('Jacksonville','VT'),('Jacksonville Beach','FL'),('Jacob','IL'),('Jacobs Creek','PA'),('Jacobsburg','OH'),('Jacobson','MN'),('Jacumba','CA'),('Jadwin','MO'),('Jaffrey','NH'),('Jakin','GA'),('Jal','NM'),('Jamaica','IA'),('Jamaica','NY'),('Jamaica','VA'),('Jamaica','VT'),('Jamaica Plain','MA'),('James City','PA'),('James Creek','PA'),('Jameson','MO'),('Jamesport','MO'),('Jamesport','NY'),('Jamestown','CA'),('Jamestown','CO'),('Jamestown','IN'),('Jamestown','KS'),('Jamestown','KY'),('Jamestown','LA'),('Jamestown','MI'),('Jamestown','MO'),('Jamestown','NC'),('Jamestown','ND'),('Jamestown','NM'),('Jamestown','NY'),('Jamestown','OH'),('Jamestown','PA'),('Jamestown','RI'),('Jamestown','SC'),('Jamestown','TN'),('Jamestown','VA'),('Jamesville','NC'),('Jamesville','NY'),('Jamesville','VA'),('Jamieson','OR'),('Jamison','PA'),('Jamul','CA'),('Jane Lew','WV'),('Janesville','CA'),('Janesville','IA'),('Janesville','IL'),('Janesville','MN'),('Janesville','WI'),('Jansen','NE'),('Jarales','NM'),('Jarbidge','NV'),('Jaroso','CO'),('Jarratt','VA'),('Jarreau','LA'),('Jarrell','TX'),('Jarrettsville','MD'),('Jarvisburg','NC'),('Jasonville','IN'),('Jasper','AL'),('Jasper','AR'),('Jasper','FL'),('Jasper','GA'),('Jasper','IN'),('Jasper','MI'),('Jasper','MN'),('Jasper','MO'),('Jasper','NY'),('Jasper','OH'),('Jasper','TN'),('Jasper','TX'),('Java','SD'),('Java','VA'),('Java Center','NY'),('Java Village','NY'),('Jay','FL'),('Jay','ME'),('Jay','NY'),('Jay','OK'),('Jay Em','WY'),('Jayess','MS'),('Jayton','TX'),('Jayuya','PR'),('Jean','NV'),('Jeanerette','LA'),('Jeannette','PA'),('Jeddo','MI'),('Jeff','KY'),('Jeffers','MN'),('Jefferson','AL'),('Jefferson','AR'),('Jefferson','CO'),('Jefferson','GA'),('Jefferson','IA'),('Jefferson','MA'),('Jefferson','MD'),('Jefferson','ME'),('Jefferson','NC'),('Jefferson','NH'),('Jefferson','NY'),('Jefferson','OH'),('Jefferson','OR'),('Jefferson','PA'),('Jefferson','SC'),('Jefferson','SD'),('Jefferson','TX'),('Jefferson','WI'),('Jefferson City','MO'),('Jefferson City','MT'),('Jefferson City','TN'),('Jefferson Valley','NY'),('Jeffersonton','VA'),('Jeffersonville','GA'),('Jeffersonville','IN'),('Jeffersonville','KY'),('Jeffersonville','NY'),('Jeffersonville','OH'),('Jeffersonville','VT'),('Jeffrey','WV'),('Jeffrey City','WY'),('Jekyll Island','GA'),('Jellico','TN'),('Jelm','WY'),('Jemez Pueblo','NM'),('Jemez Springs','NM'),('Jemison','AL'),('Jena','LA'),('Jenera','OH'),('Jenison','MI'),('Jenkinjones','WV'),('Jenkins','KY'),('Jenkins','MN'),('Jenkins Bridge','VA'),('Jenkinsburg','GA'),('Jenkinsville','SC'),('Jenkintown','PA'),('Jenks','OK'),('Jenner','CA'),('Jenners','PA'),('Jennerstown','PA'),('Jennings','FL'),('Jennings','KS'),('Jennings','LA'),('Jennings','OK'),('Jensen','UT'),('Jensen Beach','FL'),('Jeremiah','KY'),('Jericho','NY'),('Jericho','VT'),('Jerico Springs','MO'),('Jermyn','PA'),('Jermyn','TX'),('Jerome','AZ'),('Jerome','ID'),('Jerome','MI'),('Jerome','MO'),('Jerome','PA'),('Jeromesville','OH'),('Jerry City','OH'),('Jersey','AR'),('Jersey','GA'),('Jersey','VA'),('Jersey City','NJ'),('Jersey Mills','PA'),('Jersey Shore','PA'),('Jerseyville','IL'),('Jerusalem','AR'),('Jerusalem','OH'),('Jesse','WV'),('Jessie','ND'),('Jessieville','AR'),('Jessup','MD'),('Jessup','PA'),('Jesup','GA'),('Jesup','IA'),('Jet','OK'),('Jetersville','VA'),('Jetmore','KS'),('Jetson','KY'),('Jewell','GA'),('Jewell','IA'),('Jewell','KS'),('Jewell','OH'),('Jewell Ridge','VA'),('Jewett','IL'),('Jewett','NY'),('Jewett','OH'),('Jewett','TX'),('Jewett City','CT'),('Jigger','LA'),('Jim Falls','WI'),('Jim Thorpe','PA'),('Joanna','SC'),('Joaquin','TX'),('Jobstown','NJ'),('Joelton','TN'),('Joes','CO'),('Joffre','PA'),('Johannesburg','CA'),('Johannesburg','MI'),('John Day','OR'),('Johns Island','SC'),('Johnsburg','NY'),('Johnson','AR'),('Johnson','KS'),('Johnson','NE'),('Johnson','NY'),('Johnson','VT'),('Johnson City','NY'),('Johnson City','TN'),('Johnson City','TX'),('Johnson Creek','WI'),('Johnsonburg','NJ'),('Johnsonburg','PA'),('Johnsonville','IL'),('Johnsonville','NY'),('Johnsonville','SC'),('Johnston','IA'),('Johnston','RI'),('Johnston','SC'),('Johnston City','IL'),('Johnstown','CO'),('Johnstown','NE'),('Johnstown','NY'),('Johnstown','OH'),('Johnstown','PA'),('Joice','IA'),('Joiner','AR'),('Joinerville','TX'),('Joliet','IL'),('Joliet','MT'),('Jolley','IA'),('Jolo','WV'),('Jolon','CA'),('Jonancy','KY'),('Jonas Ridge','NC'),('Jones','AL'),('Jones','LA'),('Jones','MI'),('Jones','OK'),('Jones Mill','AR'),('Jones Mills','PA'),('Jonesboro','AR'),('Jonesboro','GA'),('Jonesboro','IL'),('Jonesboro','IN'),('Jonesboro','LA'),('Jonesboro','ME'),('Jonesboro','TX'),('Jonesborough','TN'),('Jonesburg','MO'),('Jonesport','ME'),('Jonestown','MS'),('Jonestown','PA'),('Jonesville','IN'),('Jonesville','KY'),('Jonesville','LA'),('Jonesville','MI'),('Jonesville','NC'),('Jonesville','SC'),('Jonesville','TX'),('Jonesville','VA'),('Jonesville','VT'),('Joplin','MO'),('Joplin','MT'),('Joppa','AL'),('Joppa','IL'),('Joppa','MD'),('Jordan','MN'),('Jordan','MT'),('Jordan','NY'),('Jordan Valley','OR'),('Jordanville','NY'),('Joseph','OR'),('Joseph','UT'),('Joseph City','AZ'),('Josephine','PA'),('Josephine','TX'),('Josephine','WV'),('Joshua','TX'),('Joshua Tree','CA'),('Jourdanton','TX'),('Joy','IL'),('Joyce','LA'),('Joyce','WA'),('Juana Diaz','PR'),('Jud','ND'),('Juda','WI'),('Judith Gap','MT'),('Judson','IN'),('Judson','TX'),('Judsonia','AR'),('Julesburg','CO'),('Juliaetta','ID'),('Julian','CA'),('Julian','NC'),('Julian','NE'),('Julian','PA'),('Julian','WV'),('Juliette','GA'),('Juliustown','NJ'),('Jump River','WI'),('Jumping Branch','WV'),('Juncos','PR'),('Junction','IL'),('Junction','TX'),('Junction','UT'),('Junction City','AR'),('Junction City','CA'),('Junction City','GA'),('Junction City','KS'),('Junction City','KY'),('Junction City','OH'),('Junction City','OR'),('Junction City','WI'),('June Lake','CA'),('Juneau','AK'),('Juneau','WI'),('Junedale','PA'),('Juniata','NE'),('Junior','WV'),('Juntura','OR'),('Jupiter','FL'),('Justice','IL'),('Justice','WV'),('Justiceburg','TX'),('Justin','TX'),('Kaaawa','HI'),('Kabetogama','MN'),('Kadoka','SD'),('Kahlotus','WA'),('Kahoka','MO'),('Kahuku','HI'),('Kahului','HI'),('Kaibeto','AZ'),('Kailua','HI'),('Kailua Kona','HI'),('Kaiser','MO'),('Kake','AK'),('Kaktovik','AK'),('Kalaheo','HI'),('Kalama','WA'),('Kalamazoo','MI'),('Kalaupapa','HI'),('Kaleva','MI'),('Kalida','OH'),('Kalispell','MT'),('Kalkaska','MI'),('Kalona','IA'),('Kalskag','AK'),('Kaltag','AK'),('Kamas','UT'),('Kamay','TX'),('Kamiah','ID'),('Kampsville','IL'),('Kamrar','IA'),('Kamuela','HI'),('Kanab','UT'),('Kanaranzi','MN'),('Kanarraville','UT'),('Kanawha','IA'),('Kanawha Falls','WV'),('Kanawha Head','WV'),('Kandiyohi','MN'),('Kane','IL'),('Kane','PA'),('Kaneohe','HI'),('Kaneville','IL'),('Kankakee','IL'),('Kannapolis','NC'),('Kanona','NY'),('Kanopolis','KS'),('Kanorado','KS'),('Kanosh','UT'),('Kansas','AL'),('Kansas','IL'),('Kansas','OH'),('Kansas','OK'),('Kansas City','KS'),('Kansas City','MO'),('Kansasville','WI'),('Kantner','PA'),('Kapaa','HI'),('Kapaau','HI'),('Kaplan','LA'),('Kapolei','HI'),('Kapowsin','WA'),('Karbers Ridge','IL'),('Karlsruhe','ND'),('Karlstad','MN'),('Karluk','AK'),('Karnack','TX'),('Karnak','IL'),('Karnes City','TX'),('Karns City','PA'),('Karthaus','PA'),('Karval','CO'),('Kasbeer','IL'),('Kasigluk','AK'),('Kasilof','AK'),('Kasota','MN'),('Kasson','MN'),('Kathleen','FL'),('Kathleen','GA'),('Kathryn','ND'),('Katonah','NY'),('Kattskill Bay','NY'),('Katy','TX'),('Kaufman','TX'),('Kaukauna','WI'),('Kaumakani','HI'),('Kaunakakai','HI'),('Kauneonga Lake','NY'),('Kaw City','OK'),('Kaweah','CA'),('Kawkawlin','MI'),('Kaycee','WY'),('Kayenta','AZ'),('Kaylor','SD'),('Kaysville','UT'),('Keaau','HI'),('Kealakekua','HI'),('Kealia','HI'),('Keams Canyon','AZ'),('Keansburg','NJ'),('Kearney','MO'),('Kearney','NE'),('Kearneysville','WV'),('Kearny','AZ'),('Kearny','NJ'),('Kearsarge','MI'),('Kearsarge','NH'),('Keasbey','NJ'),('Keatchie','LA'),('Keaton','KY'),('Keauhou','HI'),('Keavy','KY'),('Kechi','KS'),('Keedysville','MD'),('Keego Harbor','MI'),('Keeler','CA'),('Keeling','VA'),('Keen Mountain','VA'),('Keene','CA'),('Keene','KY'),('Keene','ND'),('Keene','NH'),('Keene','NY'),('Keene','OH'),('Keene','TX'),('Keene','VA'),('Keene Valley','NY'),('Keenes','IL'),('Keenesburg','CO'),('Keensburg','IL'),('Keeseville','NY'),('Keewatin','MN'),('Keezletown','VA'),('Kegley','WV'),('Keiser','AR'),('Keisterville','PA'),('Keithsburg','IL'),('Keithville','LA'),('Keizer','OR'),('Kekaha','HI'),('Kelayres','PA'),('Keldron','SD'),('Kelford','NC'),('Kell','IL'),('Keller','TX'),('Keller','VA'),('Keller','WA'),('Kellerman','AL'),('Kellerton','IA'),('Kelley','IA'),('Kelleys Island','OH'),('Kelliher','MN'),('Kellnersville','WI'),('Kellogg','IA'),('Kellogg','ID'),('Kellogg','MN'),('Kelly','LA'),('Kelly','NC'),('Kelly','WY'),('Kellysville','WV'),('Kellyton','AL'),('Kellyville','OK'),('Kelseyville','CA'),('Kelso','MO'),('Kelso','TN'),('Kelso','WA'),('Kelton','PA'),('Kemah','TX'),('Kemblesville','PA'),('Kemmerer','WY'),('Kemp','OK'),('Kemp','TX'),('Kempner','TX'),('Kempton','IL'),('Kempton','IN'),('Kempton','PA'),('Kenai','AK'),('Kenansville','FL'),('Kenansville','NC'),('Kenbridge','VA'),('Kendalia','TX'),('Kendall','KS'),('Kendall','MI'),('Kendall','NY'),('Kendall','WI'),('Kendall Park','NJ'),('Kendallville','IN'),('Kendleton','TX'),('Kendrick','ID'),('Kenduskeag','ME'),('Kenedy','TX'),('Kenefic','OK'),('Kenesaw','NE'),('Kenilworth','IL'),('Kenilworth','NJ'),('Kenilworth','UT'),('Kenly','NC'),('Kenmare','ND'),('Kenmore','WA'),('Kenna','NM'),('Kenna','WV'),('Kennan','WI'),('Kennard','IN'),('Kennard','NE'),('Kennard','TX'),('Kennebec','SD'),('Kennebunk','ME'),('Kennebunkport','ME'),('Kennedale','TX'),('Kennedy','AL'),('Kennedy','MN'),('Kennedy','NY'),('Kennedyville','MD'),('Kenner','LA'),('Kennerdell','PA'),('Kennesaw','GA'),('Kenneth','MN'),('Kennett','MO'),('Kennett Square','PA'),('Kennewick','WA'),('Kenney','IL'),('Kenney','TX'),('Keno','OR'),('Kenosha','WI'),('Kenova','WV'),('Kenoza Lake','NY'),('Kensal','ND'),('Kensett','AR'),('Kensett','IA'),('Kensington','KS'),('Kensington','MD'),('Kensington','MN'),('Kensington','OH'),('Kent','AL'),('Kent','CT'),('Kent','IL'),('Kent','MN'),('Kent','NY'),('Kent','OH'),('Kent','OR'),('Kent','PA'),('Kent','WA'),('Kent City','MI'),('Kentfield','CA'),('Kentland','IN'),('Kenton','DE'),('Kenton','KY'),('Kenton','OH'),('Kenton','OK'),('Kenton','TN'),('Kents Hill','ME'),('Kents Store','VA'),('Kentwood','LA'),('Kenvil','NJ'),('Kenvir','KY'),('Kenwood','CA'),('Kenyon','MN'),('Kenyon','RI'),('Keo','AR'),('Keokee','VA'),('Keokuk','IA'),('Keosauqua','IA'),('Keota','IA'),('Keota','OK'),('Kerby','OR'),('Kerens','TX'),('Kerens','WV'),('Kerhonkson','NY'),('Kerkhoven','MN'),('Kerman','CA'),('Kermit','TX'),('Kermit','WV'),('Kernersville','NC'),('Kernville','CA'),('Kerr','OH'),('Kerrick','MN'),('Kerrick','TX'),('Kerrville','TX'),('Kersey','CO'),('Kersey','PA'),('Kershaw','SC'),('Keshena','WI'),('Keslers Cross Lanes','WV'),('Kesley','IA'),('Keswick','IA'),('Keswick','VA'),('Ketchikan','AK'),('Ketchum','ID'),('Ketchum','OK'),('Kettle Falls','WA'),('Kettle Island','KY'),('Kettle River','MN'),('Kettleman City','CA'),('Kettlersville','OH'),('Keuka Park','NY'),('Kevil','KY'),('Kevin','MT'),('Kew Gardens','NY'),('Kewadin','MI'),('Kewanee','IL'),('Kewanee','MO'),('Kewanna','IN'),('Kewaskum','WI'),('Kewaunee','WI'),('Key Biscayne','FL'),('Key Colony Beach','FL'),('Key Largo','FL'),('Key West','FL'),('Keyes','CA'),('Keyes','OK'),('Keyesport','IL'),('Keymar','MD'),('Keyport','NJ'),('Keyport','WA'),('Keyser','WV'),('Keystone','IA'),('Keystone','IN'),('Keystone','NE'),('Keystone','SD'),('Keystone Heights','FL'),('Keysville','GA'),('Keysville','VA'),('Keytesville','MO'),('Kiahsville','WV'),('Kiamesha Lake','NY'),('Kiamichi Christian Mission','OK'),('Kiana','AK'),('Kidder','MO'),('Kidron','OH'),('Kiefer','OK'),('Kiel','WI'),('Kieler','WI'),('Kiester','MN'),('Kihei','HI'),('Kila','MT'),('Kilauea','HI'),('Kilbourne','IL'),('Kilbourne','LA'),('Kilbourne','OH'),('Kildare','TX'),('Kilgore','NE'),('Kilgore','TX'),('Kilkenny','MN'),('Kill Buck','NY'),('Kill Devil Hills','NC'),('Killarney','FL'),('Killawog','NY'),('Killbuck','OH'),('Killdeer','ND'),('Killduff','IA'),('Killeen','TX'),('Killen','AL'),('Killington','VT'),('Killingworth','CT'),('Kilmarnock','VA'),('Kilmichael','MS'),('Kiln','MS'),('Kim','CO'),('Kimball','MN'),('Kimball','NE'),('Kimball','SD'),('Kimball','WV'),('Kimballton','IA'),('Kimberling City','MO'),('Kimberly','AL'),('Kimberly','ID'),('Kimberly','OR'),('Kimberly','WI'),('Kimberly','WV'),('Kimberton','PA'),('Kimbolton','OH'),('Kimmell','IN'),('Kimmswick','MO'),('Kimper','KY'),('Kinards','SC'),('Kincaid','IL'),('Kincaid','KS'),('Kincaid','WV'),('Kincheloe','MI'),('Kinde','MI'),('Kinder','LA'),('Kinderhook','IL'),('Kinderhook','NY'),('Kindred','ND'),('King','NC'),('King','WI'),('King And Queen Court House','VA'),('King City','CA'),('King City','MO'),('King Cove','AK'),('King Ferry','NY'),('King George','VA'),('King Hill','ID'),('King Of Prussia','PA'),('King Salmon','AK'),('King William','VA'),('Kingdom City','MO'),('Kingfield','ME'),('Kingfisher','OK'),('Kingman','AZ'),('Kingman','IN'),('Kingman','KS'),('Kingman','ME'),('Kingmont','WV'),('Kings Bay','GA'),('Kings Beach','CA'),('Kings Canyon National Pk','CA'),('Kings Mills','OH'),('Kings Mountain','KY'),('Kings Mountain','NC'),('Kings Park','NY'),('Kingsburg','CA'),('Kingsbury','IN'),('Kingsbury','TX'),('Kingsford','MI'),('Kingsford Heights','IN'),('Kingsland','AR'),('Kingsland','GA'),('Kingsland','TX'),('Kingsley','IA'),('Kingsley','MI'),('Kingsley','PA'),('Kingsport','TN'),('Kingston','AR'),('Kingston','GA'),('Kingston','ID'),('Kingston','IL'),('Kingston','MA'),('Kingston','MI'),('Kingston','MO'),('Kingston','NH'),('Kingston','NJ'),('Kingston','NY'),('Kingston','OH'),('Kingston','OK'),('Kingston','PA'),('Kingston','RI'),('Kingston','TN'),('Kingston','UT'),('Kingston','WA'),('Kingston','WI'),('Kingston Mines','IL'),('Kingston Springs','TN'),('Kingstree','SC'),('Kingsville','MD'),('Kingsville','MO'),('Kingsville','OH'),('Kingsville','TX'),('Kingwood','TX'),('Kingwood','WV'),('Kinmundy','IL'),('Kinnear','WY'),('Kinney','MN'),('Kinross','MI'),('Kinsale','VA'),('Kinsey','MT'),('Kinsley','KS'),('Kinsman','IL'),('Kinsman','OH'),('Kinston','AL'),('Kinston','NC'),('Kinta','OK'),('Kintnersville','PA'),('Kintyre','ND'),('Kinzers','PA'),('Kiowa','CO'),('Kiowa','KS'),('Kiowa','OK'),('Kipling','NC'),('Kipling','OH'),('Kipnuk','AK'),('Kipton','OH'),('Kirby','AR'),('Kirby','OH'),('Kirby','WY'),('Kirbyville','MO'),('Kirbyville','TX'),('Kirk','CO'),('Kirkersville','OH'),('Kirkland','AZ'),('Kirkland','IL'),('Kirkland','WA'),('Kirklin','IN'),('Kirkman','IA'),('Kirksey','KY'),('Kirksville','MO'),('Kirkville','IA'),('Kirkville','NY'),('Kirkwood','CA'),('Kirkwood','DE'),('Kirkwood','IL'),('Kirkwood','NY'),('Kirkwood','PA'),('Kiron','IA'),('Kirtland','NM'),('Kirtland Afb','NM'),('Kirvin','TX'),('Kirwin','KS'),('Kismet','KS'),('Kissee Mills','MO'),('Kissimmee','FL'),('Kistler','WV'),('Kit Carson','CA'),('Kit Carson','CO'),('Kite','GA'),('Kite','KY'),('Kittanning','PA'),('Kittery','ME'),('Kittery Point','ME'),('Kittitas','WA'),('Kittredge','CO'),('Kittrell','NC'),('Kitts Hill','OH'),('Kitty Hawk','NC'),('Kitzmiller','MD'),('Kivalina','AK'),('Klamath','CA'),('Klamath Falls','OR'),('Klamath River','CA'),('Klawock','AK'),('Kleinfeltersville','PA'),('Klemme','IA'),('Klickitat','WA'),('Klingerstown','PA'),('Klondike','TX'),('Knapp','WI'),('Kneeland','CA'),('Knickerbocker','TX'),('Knierim','IA'),('Knife River','MN'),('Knifley','KY'),('Knightdale','NC'),('Knights Landing','CA'),('Knightsen','CA'),('Knightstown','IN'),('Knightsville','IN'),('Knippa','TX'),('Knob Lick','KY'),('Knob Lick','MO'),('Knob Noster','MO'),('Knobel','AR'),('Knott','TX'),('Knotts Island','NC'),('Knowlesville','NY'),('Knox','IN'),('Knox','ND'),('Knox','NY'),('Knox','PA'),('Knox City','MO'),('Knox City','TX'),('Knox Dale','PA'),('Knoxboro','NY'),('Knoxville','AL'),('Knoxville','AR'),('Knoxville','GA'),('Knoxville','IA'),('Knoxville','IL'),('Knoxville','MD'),('Knoxville','PA'),('Knoxville','TN'),('Kobuk','AK'),('Kodak','TN'),('Kodiak','AK'),('Koeltztown','MO'),('Kohler','WI'),('Kokomo','IN'),('Kokomo','MS'),('Koleen','IN'),('Koloa','HI'),('Konawa','OK'),('Kongiganak','AK'),('Koosharem','UT'),('Kooskia','ID'),('Kootenai','ID'),('Koppel','PA'),('Kopperl','TX'),('Kopperston','WV'),('Korbel','CA'),('Kosciusko','MS'),('Koshkonong','MO'),('Kosse','TX'),('Kossuth','PA'),('Kotlik','AK'),('Kotzebue','AK'),('Kountze','TX'),('Kouts','IN'),('Koyuk','AK'),('Koyukuk','AK'),('Kraemer','LA'),('Krakow','WI'),('Kramer','ND'),('Kranzburg','SD'),('Kreamer','PA'),('Krebs','OK'),('Kremlin','MT'),('Kremlin','OK'),('Kremmling','CO'),('Kresgeville','PA'),('Kress','TX'),('Krotz Springs','LA'),('Krum','TX'),('Krypton','KY'),('Kualapuu','HI'),('Kula','HI'),('Kulm','ND'),('Kulpmont','PA'),('Kulpsville','PA'),('Kuna','ID'),('Kunia','HI'),('Kunkle','OH'),('Kunkletown','PA'),('Kure Beach','NC'),('Kurten','TX'),('Kurthwood','LA'),('Kurtistown','HI'),('Kurtz','IN'),('Kuttawa','KY'),('Kutztown','PA'),('Kwethluk','AK'),('Kwigillingok','AK'),('Kyburz','CA'),('Kykotsmovi Village','AZ'),('Kyle','SD'),('Kyle','TX'),('Kyle','WV'),('Kylertown','PA'),('Kyles Ford','TN'),('La Barge','WY'),('La Belle','MO'),('La Belle','PA'),('La Blanca','TX'),('La Canada Flintridge','CA'),('La Center','KY'),('La Center','WA'),('La Conner','WA'),('La Coste','TX'),('La Crescent','MN'),('La Crescenta','CA'),('La Crosse','FL'),('La Crosse','IN'),('La Crosse','KS'),('La Crosse','VA'),('La Crosse','WI'),('La Farge','WI'),('La Fargeville','NY'),('La Fayette','GA'),('La Fayette','IL'),('La Fayette','KY'),('La Fayette','NY'),('La Feria','TX'),('La Follette','TN'),('La Fontaine','IN'),('La France','SC'),('La Grande','OR'),('La Grande','WA'),('La Grange','AR'),('La Grange','CA'),('La Grange','IL'),('La Grange','KY'),('La Grange','MO'),('La Grange','NC'),('La Grange','TN'),('La Grange','TX'),('La Grange Park','IL'),('La Habra','CA'),('La Harpe','IL'),('La Harpe','KS'),('La Honda','CA'),('La Jara','CO'),('La Jara','NM'),('La Jolla','CA'),('La Jose','PA'),('La Joya','NM'),('La Joya','TX'),('La Junta','CO'),('La Loma','NM'),('La Luz','NM'),('La Madera','NM'),('La Marque','TX'),('La Mesa','CA'),('La Mesa','NM'),('La Mirada','CA'),('La Moille','IL'),('La Monte','MO'),('La Motte','IA'),('La Palma','CA'),('La Pine','OR'),('La Place','IL'),('La Place','LA'),('La Plata','MD'),('La Plata','MO'),('La Plata','NM'),('La Plata','PR'),('La Plume','PA'),('La Pointe','WI'),('La Porte','IN'),('La Porte','TX'),('La Porte City','IA'),('La Prairie','IL'),('La Pryor','TX'),('La Puente','CA'),('La Push','WA'),('La Quinta','CA'),('La Rose','IL'),('La Rue','OH'),('La Russell','MO'),('La Sal','UT'),('La Salle','CO'),('La Salle','IL'),('La Salle','MI'),('La Salle','MN'),('La Salle','TX'),('La Valle','WI'),('La Vergne','TN'),('La Verkin','UT'),('La Verne','CA'),('La Vernia','TX'),('La Veta','CO'),('La Villa','TX'),('La Vista','NE'),('La Ward','TX'),('Labadie','MO'),('Labadieville','LA'),('Labelle','FL'),('Labolt','SD'),('Lac Du Flambeau','WI'),('Lacarne','OH'),('Lacassine','LA'),('Lacey','WA'),('Lacey Spring','VA'),('Laceys Spring','AL'),('Laceyville','PA'),('Lachine','MI'),('Lackawaxen','PA'),('Lackey','KY'),('Lackey','VA'),('Laclede','ID'),('Laclede','MO'),('Lacombe','LA'),('Lacon','IL'),('Lacona','IA'),('Lacona','NY'),('Laconia','IN'),('Laconia','NH'),('Laconia','TN'),('Lacoochee','FL'),('Lacota','MI'),('Lacrosse','WA'),('Lacygne','KS'),('Ladd','IL'),('Laddonia','MO'),('Ladera Ranch','CA'),('Ladiesburg','MD'),('Ladoga','IN'),('Ladonia','TX'),('Ladora','IA'),('Ladson','SC'),('Lady Lake','FL'),('Ladys Island','SC'),('Ladysmith','VA'),('Ladysmith','WI'),('Lafayette','AL'),('Lafayette','CA'),('Lafayette','CO'),('Lafayette','IN'),('Lafayette','LA'),('Lafayette','MN'),('Lafayette','NJ'),('Lafayette','OH'),('Lafayette','OR'),('Lafayette','TN'),('Lafayette Hill','PA'),('Lafe','AR'),('Lafferty','OH'),('Lafitte','LA'),('Lafox','IL'),('Lagrange','GA'),('Lagrange','IN'),('Lagrange','ME'),('Lagrange','OH'),('Lagrange','WY'),('Lagrangeville','NY'),('Lagro','IN'),('Laguna','NM'),('Laguna Beach','CA'),('Laguna Hills','CA'),('Laguna Niguel','CA'),('Laguna Park','TX'),('Laguna Woods','CA'),('Lagunitas','CA'),('Lahaina','HI'),('Lahaska','PA'),('Lahmansville','WV'),('Lahoma','OK'),('Laie','HI'),('Laings','OH'),('Laingsburg','MI'),('Laird Hill','TX'),('Lairdsville','PA'),('Lajas','PR'),('Lake','MI'),('Lake','MS'),('Lake','WV'),('Lake Alfred','FL'),('Lake Andes','SD'),('Lake Ann','MI'),('Lake Ariel','PA'),('Lake Arrowhead','CA'),('Lake Arthur','LA'),('Lake Arthur','NM'),('Lake Benton','MN'),('Lake Bluff','IL'),('Lake Bronson','MN'),('Lake Butler','FL'),('Lake Charles','LA'),('Lake Cicott','IN'),('Lake City','AR'),('Lake City','CA'),('Lake City','CO'),('Lake City','FL'),('Lake City','IA'),('Lake City','KS'),('Lake City','MI'),('Lake City','MN'),('Lake City','PA'),('Lake City','SC'),('Lake City','SD'),('Lake City','TN'),('Lake Clear','NY'),('Lake Como','FL'),('Lake Como','PA'),('Lake Cormorant','MS'),('Lake Creek','TX'),('Lake Crystal','MN'),('Lake Dallas','TX'),('Lake Delton','WI'),('Lake Elmo','MN'),('Lake Elmore','VT'),('Lake Elsinore','CA'),('Lake Forest','CA'),('Lake Forest','IL'),('Lake Fork','ID'),('Lake Fork','IL'),('Lake Geneva','FL'),('Lake Geneva','WI'),('Lake George','CO'),('Lake George','MI'),('Lake George','MN'),('Lake George','NY'),('Lake Grove','NY'),('Lake Hamilton','FL'),('Lake Harbor','FL'),('Lake Harmony','PA'),('Lake Havasu City','AZ'),('Lake Helen','FL'),('Lake Hiawatha','NJ'),('Lake Hill','NY'),('Lake Hopatcong','NJ'),('Lake Hubert','MN'),('Lake Hughes','CA'),('Lake Huntington','NY'),('Lake In The Hills','IL'),('Lake Isabella','CA'),('Lake Jackson','TX'),('Lake Junaluska','NC'),('Lake Katrine','NY'),('Lake Leelanau','MI'),('Lake Lillian','MN'),('Lake Linden','MI'),('Lake Lure','NC'),('Lake Luzerne','NY'),('Lake Lynn','PA'),('Lake Mary','FL'),('Lake Mc Donald','MT'),('Lake Mills','IA'),('Lake Mills','WI'),('Lake Milton','OH'),('Lake Minchumina','AK'),('Lake Monroe','FL'),('Lake Montezuma','AZ'),('Lake Nebagamon','WI'),('Lake Norden','SD'),('Lake Odessa','MI'),('Lake Orion','MI'),('Lake Oswego','OR'),('Lake Ozark','MO'),('Lake Panasoffkee','FL'),('Lake Park','GA'),('Lake Park','IA'),('Lake Park','MN'),('Lake Peekskill','NY'),('Lake Placid','FL'),('Lake Placid','NY'),('Lake Pleasant','MA'),('Lake Pleasant','NY'),('Lake Powell','UT'),('Lake Preston','SD'),('Lake Providence','LA'),('Lake Saint Louis','MO'),('Lake Spring','MO'),('Lake Station','IN'),('Lake Stevens','WA'),('Lake Tomahawk','WI'),('Lake Toxaway','NC'),('Lake View','IA'),('Lake View','NY'),('Lake View','SC'),('Lake Villa','IL'),('Lake Village','AR'),('Lake Village','IN'),('Lake Waccamaw','NC'),('Lake Wales','FL'),('Lake Wilson','MN'),('Lake Winola','PA'),('Lake Worth','FL'),('Lake Zurich','IL'),('Lakebay','WA'),('Lakefield','MN'),('Lakehead','CA'),('Lakehills','TX'),('Lakehurst','NJ'),('Lakeland','FL'),('Lakeland','GA'),('Lakeland','LA'),('Lakeland','MI'),('Lakeland','MN'),('Lakemont','GA'),('Lakemont','NY'),('Lakemore','OH'),('Lakeport','CA'),('Lakeshore','CA'),('Lakeshore','MS'),('Lakeside','AZ'),('Lakeside','CA'),('Lakeside','CT'),('Lakeside','MI'),('Lakeside','MT'),('Lakeside','NE'),('Lakeside','OR'),('Lakeside Marblehead','OH'),('Laketon','IN'),('Laketown','UT'),('Lakeview','AR'),('Lakeview','MI'),('Lakeview','NC'),('Lakeview','OH'),('Lakeview','OR'),('Lakeview','TX'),('Lakeville','CT'),('Lakeville','IN'),('Lakeville','MA'),('Lakeville','MI'),('Lakeville','MN'),('Lakeville','NY'),('Lakeville','OH'),('Lakeville','PA'),('Lakewood','CA'),('Lakewood','IL'),('Lakewood','NJ'),('Lakewood','NM'),('Lakewood','NY'),('Lakewood','OH'),('Lakewood','PA'),('Lakewood','WA'),('Lakewood','WI'),('Lakin','KS'),('Lakota','IA'),('Lakota','ND'),('Lamar','AR'),('Lamar','CO'),('Lamar','IN'),('Lamar','MO'),('Lamar','MS'),('Lamar','OK'),('Lamar','PA'),('Lamar','SC'),('Lamartine','PA'),('Lambert','MS'),('Lambert','MT'),('Lambert Lake','ME'),('Lamberton','MN'),('Lambertville','MI'),('Lambertville','NJ'),('Lambrook','AR'),('Lambsburg','VA'),('Lame Deer','MT'),('Lamesa','TX'),('Lamoille','NV'),('Lamona','WA'),('Lamoni','IA'),('Lamont','CA'),('Lamont','FL'),('Lamont','IA'),('Lamont','KS'),('Lamont','MI'),('Lamont','OK'),('Lamont','WA'),('Lamoure','ND'),('Lampasas','TX'),('Lampe','MO'),('Lampeter','PA'),('Lamy','NM'),('Lanagan','MO'),('Lanai City','HI'),('Lanark','IL'),('Lanark','WV'),('Lanark Village','FL'),('Lancaster','CA'),('Lancaster','IL'),('Lancaster','KS'),('Lancaster','KY'),('Lancaster','MA'),('Lancaster','MN'),('Lancaster','MO'),('Lancaster','NH'),('Lancaster','NY'),('Lancaster','OH'),('Lancaster','PA'),('Lancaster','SC'),('Lancaster','TN'),('Lancaster','TX'),('Lancaster','VA'),('Lancaster','WI'),('Lance Creek','WY'),('Lancing','TN'),('Land O Lakes','FL'),('Land O Lakes','WI'),('Landenberg','PA'),('Lander','WY'),('Landers','CA'),('Landing','NJ'),('Landingville','PA'),('Landis','NC'),('Landisburg','PA'),('Landisville','NJ'),('Landisville','PA'),('Lando','SC'),('Landrum','SC'),('Lane','IL'),('Lane','KS'),('Lane','OK'),('Lane','SC'),('Lane','SD'),('Lane City','TX'),('Laneburg','AR'),('Lanesboro','IA'),('Lanesboro','MA'),('Lanesboro','MN'),('Lanesboro','PA'),('Lanesville','IN'),('Lanesville','NY'),('Lanett','AL'),('Laneview','VA'),('Laneville','TX'),('Lanexa','VA'),('Langdon','ND'),('Langeloth','PA'),('Langford','SD'),('Langhorne','PA'),('Langley','AR'),('Langley','KY'),('Langley','OK'),('Langley','SC'),('Langley','WA'),('Langlois','OR'),('Langston','AL'),('Langston','OK'),('Langsville','OH'),('Langtry','TX'),('Langworthy','IA'),('Lanham','MD'),('Lankin','ND'),('Lannon','WI'),('Lanoka Harbor','NJ'),('Lansdale','PA'),('Lansdowne','PA'),('Lanse','MI'),('Lanse','PA'),('Lansford','ND'),('Lansford','PA'),('Lansing','IA'),('Lansing','IL'),('Lansing','KS'),('Lansing','MI'),('Lansing','MN'),('Lansing','NC'),('Lansing','NY'),('Lansing','OH'),('Lansing','WV'),('Lantry','SD'),('Laona','WI'),('Laotto','IN'),('Lapaz','IN'),('Lapeer','MI'),('Lapel','IN'),('Lapine','AL'),('Lapoint','UT'),('Laporte','CO'),('Laporte','MN'),('Laporte','PA'),('Lapwai','ID'),('Laquey','MO'),('Laramie','WY'),('Larchmont','NY'),('Larchwood','IA'),('Laredo','MO'),('Laredo','TX'),('Lares','PR'),('Largo','FL'),('Larimer','PA'),('Larimore','ND'),('Larkspur','CA'),('Larkspur','CO'),('Larned','KS'),('Larose','LA'),('Larrabee','IA'),('Larsen','WI'),('Larsen Bay','AK'),('Larslan','MT'),('Larue','TX'),('Larwill','IN'),('Las Animas','CO'),('Las Cruces','NM'),('Las Marias','PR'),('Las Piedras','PR'),('Las Vegas','NM'),('Las Vegas','NV'),('Lasara','TX'),('Lascassas','TN'),('Lashmeet','WV'),('Lastrup','MN'),('Latah','WA'),('Latexo','TX'),('Latham','IL'),('Latham','KS'),('Latham','MO'),('Latham','NY'),('Latham','OH'),('Lathrop','CA'),('Lathrop','MO'),('Latimer','IA'),('Laton','CA'),('Latonia','KY'),('Latrobe','PA'),('Latta','SC'),('Lattimer Mines','PA'),('Lattimore','NC'),('Latty','OH'),('Lauderdale','MS'),('Laughlin','NV'),('Laughlin A F B','TX'),('Laughlintown','PA'),('Laupahoehoe','HI'),('Laura','IL'),('Laura','OH'),('Laurel','DE'),('Laurel','FL'),('Laurel','IA'),('Laurel','IN'),('Laurel','MD'),('Laurel','MS'),('Laurel','MT'),('Laurel','NE'),('Laurel','NY'),('Laurel Bloomery','TN'),('Laurel Fork','VA'),('Laurel Hill','FL'),('Laurel Hill','NC'),('Laurel Springs','NC'),('Laurelton','PA'),('Laurelville','OH'),('Laurens','IA'),('Laurens','NY'),('Laurens','SC'),('Laurie','MO'),('Laurier','WA'),('Laurinburg','NC'),('Laurys Station','PA'),('Lava Hot Springs','ID'),('Lavaca','AR'),('Lavalette','WV'),('Lavallette','NJ'),('Laveen','AZ'),('Lavelle','PA'),('Laverne','OK'),('Lavina','MT'),('Lavinia','TN'),('Lavon','TX'),('Lavonia','GA'),('Lawai','HI'),('Lawler','IA'),('Lawley','AL'),('Lawn','PA'),('Lawn','TX'),('Lawndale','CA'),('Lawndale','IL'),('Lawndale','NC'),('Lawnside','NJ'),('Lawrence','KS'),('Lawrence','MA'),('Lawrence','MI'),('Lawrence','MS'),('Lawrence','NE'),('Lawrence','NY'),('Lawrence','PA'),('Lawrenceburg','IN'),('Lawrenceburg','KY'),('Lawrenceburg','TN'),('Lawrenceville','GA'),('Lawrenceville','IL'),('Lawrenceville','NY'),('Lawrenceville','PA'),('Lawrenceville','VA'),('Lawson','AR'),('Lawson','MO'),('Lawsonville','NC'),('Lawtell','LA'),('Lawtey','FL'),('Lawton','IA'),('Lawton','MI'),('Lawton','ND'),('Lawton','OK'),('Lawton','PA'),('Lawtons','NY'),('Layland','WV'),('Layton','NJ'),('Layton','UT'),('Laytonville','CA'),('Lazbuddie','TX'),('Lazear','CO'),('Le Center','MN'),('Le Claire','IA'),('Le Grand','CA'),('Le Grand','IA'),('Le Mars','IA'),('Le Raysville','PA'),('Le Roy','IL'),('Le Roy','KS'),('Le Roy','MN'),('Le Roy','NY'),('Le Roy','WV'),('Le Sueur','MN'),('Leachville','AR'),('Lead','SD'),('Lead Hill','AR'),('Leadore','ID'),('Leadville','CO'),('Leadwood','MO'),('Leaf River','IL'),('League City','TX'),('Leakesville','MS'),('Leakey','TX'),('Leamington','UT'),('Leander','TX'),('Leary','GA'),('Leasburg','MO'),('Leasburg','NC'),('Leavenworth','IN'),('Leavenworth','KS'),('Leavenworth','WA'),('Leavittsburg','OH'),('Leawood','KS'),('Lebam','WA'),('Lebanon','CT'),('Lebanon','GA'),('Lebanon','IL'),('Lebanon','IN'),('Lebanon','KS'),('Lebanon','KY'),('Lebanon','ME'),('Lebanon','MO'),('Lebanon','NE'),('Lebanon','NH'),('Lebanon','NJ'),('Lebanon','OH'),('Lebanon','OK'),('Lebanon','OR'),('Lebanon','PA'),('Lebanon','SD'),('Lebanon','TN'),('Lebanon','VA'),('Lebanon','WI'),('Lebanon Junction','KY'),('Lebeau','LA'),('Lebec','CA'),('Leblanc','LA'),('Lebo','KS'),('Leburn','KY'),('Lecanto','FL'),('Leck Kill','PA'),('Leckrone','PA'),('Lecompte','LA'),('Lecompton','KS'),('Lecontes Mills','PA'),('Ledbetter','KY'),('Ledbetter','TX'),('Lederach','PA'),('Ledger','MT'),('Ledgewood','NJ'),('Ledyard','CT'),('Ledyard','IA'),('Lee','FL'),('Lee','IL'),('Lee','MA'),('Lee','ME'),('Lee','NH'),('Lee Center','IL'),('Lee Center','NY'),('Lee Vining','CA'),('Leechburg','PA'),('Leedey','OK'),('Leeds','AL'),('Leeds','MA'),('Leeds','ME'),('Leeds','ND'),('Leeds','NY'),('Leeds','UT'),('Leeds Point','NJ'),('Leeper','PA'),('Lees Creek','OH'),('Lees Summit','MO'),('Leesburg','AL'),('Leesburg','FL'),('Leesburg','GA'),('Leesburg','IN'),('Leesburg','NJ'),('Leesburg','OH'),('Leesburg','TX'),('Leesburg','VA'),('Leesport','PA'),('Leesville','LA'),('Leesville','OH'),('Leesville','SC'),('Leesville','TX'),('Leeton','MO'),('Leetonia','OH'),('Leetsdale','PA'),('Leflore','OK'),('Lefor','ND'),('Lefors','TX'),('Left Hand','WV'),('Leggett','CA'),('Leggett','TX'),('Lehi','UT'),('Lehigh','IA'),('Lehigh','KS'),('Lehigh','OK'),('Lehigh Acres','FL'),('Lehigh Valley','PA'),('Lehighton','PA'),('Lehman','PA'),('Lehr','ND'),('Leicester','MA'),('Leicester','NC'),('Leicester','NY'),('Leigh','NE'),('Leighton','AL'),('Leighton','IA'),('Leipsic','OH'),('Leisenring','PA'),('Leitchfield','KY'),('Leiter','WY'),('Leiters Ford','IN'),('Leivasy','WV'),('Lejunior','KY'),('Leland','IA'),('Leland','IL'),('Leland','MI'),('Leland','MS'),('Leland','NC'),('Lelia Lake','TX'),('Lemasters','PA'),('Lemhi','ID'),('Leming','TX'),('Lemitar','NM'),('Lemmon','SD'),('Lemon Cove','CA'),('Lemon Grove','CA'),('Lemon Springs','NC'),('Lemont','IL'),('Lemont','PA'),('Lemont Furnace','PA'),('Lemoore','CA'),('Lemoyne','NE'),('Lemoyne','OH'),('Lemoyne','PA'),('Lempster','NH'),('Lena','IL'),('Lena','LA'),('Lena','MS'),('Lena','WI'),('Lenapah','OK'),('Lenexa','KS'),('Lengby','MN'),('Lenhartsville','PA'),('Lenni','PA'),('Lennon','MI'),('Lennox','SD'),('Lenoir','NC'),('Lenoir City','TN'),('Lenora','KS'),('Lenorah','TX'),('Lenore','ID'),('Lenore','WV'),('Lenox','AL'),('Lenox','GA'),('Lenox','IA'),('Lenox','MA'),('Lenox','MO'),('Lenox','TN'),('Lenox Dale','MA'),('Lenoxville','PA'),('Lentner','MO'),('Lenzburg','IL'),('Leo','IN'),('Leola','AR'),('Leola','PA'),('Leola','SD'),('Leoma','TN'),('Leominster','MA'),('Leon','IA'),('Leon','KS'),('Leon','NY'),('Leon','OK'),('Leon','VA'),('Leon','WV'),('Leona','TX'),('Leonard','MI'),('Leonard','MN'),('Leonard','MO'),('Leonard','ND'),('Leonard','OK'),('Leonard','TX'),('Leonardo','NJ'),('Leonardsville','NY'),('Leonardtown','MD'),('Leonardville','KS'),('Leonia','NJ'),('Leonidas','MI'),('Leonore','IL'),('Leonville','LA'),('Leopold','IN'),('Leopold','MO'),('Leopolis','WI'),('Leota','MN'),('Leoti','KS'),('Lepanto','AR'),('Lequire','OK'),('Lerna','IL'),('Lerona','WV'),('Leroy','AL'),('Leroy','IN'),('Leroy','MI'),('Leroy','TX'),('Lesage','WV'),('Leslie','AR'),('Leslie','GA'),('Leslie','MI'),('Leslie','MO'),('Leslie','WV'),('Lester','AL'),('Lester','IA'),('Lester','WV'),('Lester Prairie','MN'),('Lesterville','MO'),('Lesterville','SD'),('Letart','WV'),('Letcher','KY'),('Letcher','SD'),('Letha','ID'),('Letohatchee','AL'),('Letona','AR'),('Letts','IA'),('Lettsworth','LA'),('Leupp','AZ'),('Levan','UT'),('Levant','KS'),('Levant','ME'),('Levasy','MO'),('Levelland','TX'),('Levelock','AK'),('Levels','WV'),('Leverett','MA'),('Levering','MI'),('Levittown','NY'),('Levittown','PA'),('Lewellen','NE'),('Lewes','DE'),('Lewis','CO'),('Lewis','IA'),('Lewis','IN'),('Lewis','KS'),('Lewis','NY'),('Lewis Center','OH'),('Lewis Run','PA'),('Lewisberry','PA'),('Lewisburg','KY'),('Lewisburg','OH'),('Lewisburg','PA'),('Lewisburg','TN'),('Lewisburg','WV'),('Lewisport','KY'),('Lewiston','CA'),('Lewiston','ID'),('Lewiston','ME'),('Lewiston','MI'),('Lewiston','MN'),('Lewiston','NE'),('Lewiston','NY'),('Lewiston','UT'),('Lewiston Woodville','NC'),('Lewistown','IL'),('Lewistown','MO'),('Lewistown','MT'),('Lewistown','OH'),('Lewistown','PA'),('Lewisville','AR'),('Lewisville','ID'),('Lewisville','IN'),('Lewisville','MN'),('Lewisville','NC'),('Lewisville','OH'),('Lewisville','PA'),('Lewisville','TX'),('Lexa','AR'),('Lexington','AL'),('Lexington','GA'),('Lexington','IL'),('Lexington','IN'),('Lexington','KY'),('Lexington','MA'),('Lexington','MI'),('Lexington','MO'),('Lexington','MS'),('Lexington','NC'),('Lexington','NE'),('Lexington','NY'),('Lexington','OK'),('Lexington','OR'),('Lexington','SC'),('Lexington','TN'),('Lexington','TX'),('Lexington','VA'),('Lexington Park','MD'),('Libby','MT'),('Liberal','KS'),('Liberal','MO'),('Liberty','IL'),('Liberty','IN'),('Liberty','KS'),('Liberty','KY'),('Liberty','ME'),('Liberty','MO'),('Liberty','MS'),('Liberty','NC'),('Liberty','NE'),('Liberty','NY'),('Liberty','PA'),('Liberty','SC'),('Liberty','TN'),('Liberty','TX'),('Liberty','WV'),('Liberty Center','IA'),('Liberty Center','IN'),('Liberty Center','OH'),('Liberty Corner','NJ'),('Liberty Hill','SC'),('Liberty Hill','TX'),('Liberty Lake','WA'),('Liberty Mills','IN'),('Libertytown','MD'),('Libertyville','IA'),('Libertyville','IL'),('Libuse','LA'),('Lick Creek','KY'),('Licking','MO'),('Lickingville','PA'),('Lidderdale','IA'),('Lidgerwood','ND'),('Liebenthal','KS'),('Light','AR'),('Lightfoot','VA'),('Lighthouse Point','FL'),('Lightstreet','PA'),('Lignite','ND'),('Lignum','VA'),('Ligonier','IN'),('Ligonier','PA'),('Liguori','MO'),('Lihue','HI'),('Likely','CA'),('Lilbourn','MO'),('Lilburn','GA'),('Lilesville','NC'),('Lillian','AL'),('Lillian','TX'),('Lillie','LA'),('Lillington','NC'),('Lilliwaup','WA'),('Lilly','GA'),('Lilly','PA'),('Lily','KY'),('Lily Dale','NY'),('Lima','IL'),('Lima','MT'),('Lima','NY'),('Lima','OH'),('Limaville','OH'),('Lime Ridge','WI'),('Lime Springs','IA'),('Limekiln','PA'),('Limeport','PA'),('Limerick','ME'),('Limerick','NY'),('Limestone','ME'),('Limestone','NY'),('Limestone','PA'),('Limestone','TN'),('Limington','ME'),('Limon','CO'),('Linch','WY'),('Lincoln','AL'),('Lincoln','AR'),('Lincoln','CA'),('Lincoln','DE'),('Lincoln','IA'),('Lincoln','IL'),('Lincoln','KS'),('Lincoln','MA'),('Lincoln','ME'),('Lincoln','MI'),('Lincoln','MO'),('Lincoln','MT'),('Lincoln','NE'),('Lincoln','NH'),('Lincoln','NM'),('Lincoln','RI'),('Lincoln','TX'),('Lincoln','VA'),('Lincoln','WA'),('Lincoln Acres','CA'),('Lincoln City','IN'),('Lincoln City','OR'),('Lincoln Park','MI'),('Lincoln Park','NJ'),('Lincoln University','PA'),('Lincolndale','NY'),('Lincolns New Salem','IL'),('Lincolnshire','IL'),('Lincolnton','GA'),('Lincolnton','NC'),('Lincolnville','KS'),('Lincolnville','ME'),('Lincolnville Center','ME'),('Lincolnwood','IL'),('Lincroft','NJ'),('Lind','WA'),('Lindale','GA'),('Lindale','TX'),('Linden','AL'),('Linden','CA'),('Linden','IA'),('Linden','IN'),('Linden','MI'),('Linden','NC'),('Linden','NJ'),('Linden','PA'),('Linden','TN'),('Linden','TX'),('Linden','VA'),('Linden','WI'),('Lindenhurst','NY'),('Lindenwood','IL'),('Lindley','NY'),('Lindon','CO'),('Lindon','UT'),('Lindrith','NM'),('Lindsay','CA'),('Lindsay','MT'),('Lindsay','NE'),('Lindsay','OK'),('Lindsay','TX'),('Lindsborg','KS'),('Lindsey','OH'),('Lindside','WV'),('Lindstrom','MN'),('Line Lexington','PA'),('Lineboro','MD'),('Linefork','KY'),('Linesville','PA'),('Lineville','AL'),('Lineville','IA'),('Lingle','WY'),('Lingleville','TX'),('Lingo','NM'),('Linkwood','MD'),('Linn','KS'),('Linn','MO'),('Linn','TX'),('Linn','WV'),('Linn Creek','MO'),('Linn Grove','IA'),('Linneus','MO'),('Linthicum Heights','MD'),('Linton','IN'),('Linton','ND'),('Linville','NC'),('Linville','VA'),('Linville Falls','NC'),('Linwood','KS'),('Linwood','MA'),('Linwood','MI'),('Linwood','NC'),('Linwood','NE'),('Linwood','NJ'),('Linwood','NY'),('Lionville','PA'),('Lipan','TX'),('Lipscomb','TX'),('Lisbon','IA'),('Lisbon','LA'),('Lisbon','MD'),('Lisbon','ME'),('Lisbon','ND'),('Lisbon','NH'),('Lisbon','NY'),('Lisbon','OH'),('Lisbon Falls','ME'),('Lisco','NE'),('Liscomb','IA'),('Lisle','IL'),('Lisle','NY'),('Lisman','AL'),('Lismore','MN'),('Lissie','TX'),('Listie','PA'),('Litchfield','CA'),('Litchfield','CT'),('Litchfield','IL'),('Litchfield','ME'),('Litchfield','MI'),('Litchfield','MN'),('Litchfield','NE'),('Litchfield','NH'),('Litchfield','OH'),('Litchfield Park','AZ'),('Litchville','ND'),('Literberry','IL'),('Lithia','FL'),('Lithia Springs','GA'),('Lithonia','GA'),('Lithopolis','OH'),('Lititz','PA'),('Littcarr','KY'),('Little America','WY'),('Little Birch','WV'),('Little Cedar','IA'),('Little Chute','WI'),('Little Compton','RI'),('Little Creek','DE'),('Little Deer Isle','ME'),('Little Eagle','SD'),('Little Egg Harbor Twp','NJ'),('Little Elm','TX'),('Little Falls','MN'),('Little Falls','NJ'),('Little Falls','NY'),('Little Ferry','NJ'),('Little Genesee','NY'),('Little Hocking','OH'),('Little Lake','CA'),('Little Lake','MI'),('Little Meadows','PA'),('Little Mountain','SC'),('Little Neck','NY'),('Little Orleans','MD'),('Little Plymouth','VA'),('Little River','AL'),('Little River','CA'),('Little River','KS'),('Little River','SC'),('Little River Academy','TX'),('Little Rock','AR'),('Little Rock','IA'),('Little Rock','MS'),('Little Rock','SC'),('Little Rock Air Force Base','AR'),('Little Silver','NJ'),('Little Sioux','IA'),('Little Suamico','WI'),('Little Switzerland','NC'),('Little Valley','NY'),('Little York','IL'),('Little York','IN'),('Little York','NJ'),('Little York','NY'),('Littlefield','AZ'),('Littlefield','TX'),('Littlefork','MN'),('Littlerock','CA'),('Littlerock','WA'),('Littlestown','PA'),('Littleton','CO'),('Littleton','IL'),('Littleton','MA'),('Littleton','NC'),('Littleton','NH'),('Littleton','WV'),('Live Oak','CA'),('Live Oak','FL'),('Lively','VA'),('Livermore','CA'),('Livermore','CO'),('Livermore','IA'),('Livermore','KY'),('Livermore','ME'),('Livermore Falls','ME'),('Liverpool','IL'),('Liverpool','NY'),('Liverpool','PA'),('Liverpool','TX'),('Livingston','AL'),('Livingston','CA'),('Livingston','IL'),('Livingston','KY'),('Livingston','LA'),('Livingston','MT'),('Livingston','NJ'),('Livingston','NY'),('Livingston','TN'),('Livingston','TX'),('Livingston','WI'),('Livingston Manor','NY'),('Livonia','LA'),('Livonia','MI'),('Livonia','MO'),('Livonia','NY'),('Livonia Center','NY'),('Lizella','GA'),('Lizemores','WV'),('Lizton','IN'),('Llano','CA'),('Llano','NM'),('Llano','TX'),('Llewellyn','PA'),('Lloyd','FL'),('Lloyd','MT'),('Loa','UT'),('Loachapoka','AL'),('Loami','IL'),('Lobeco','SC'),('Lobelville','TN'),('Loch Sheldrake','NY'),('Lochgelly','WV'),('Lochloosa','FL'),('Lochmere','NH'),('Lock Haven','PA'),('Lock Springs','MO'),('Lockbourne','OH'),('Locke','NY'),('Lockeford','CA'),('Lockesburg','AR'),('Lockhart','AL'),('Lockhart','SC'),('Lockhart','TX'),('Lockney','TX'),('Lockport','IL'),('Lockport','KY'),('Lockport','LA'),('Lockport','NY'),('Lockridge','IA'),('Lockwood','CA'),('Lockwood','MO'),('Lockwood','NY'),('Loco','OK'),('Loco Hills','NM'),('Locust','NC'),('Locust Dale','VA'),('Locust Fork','AL'),('Locust Gap','PA'),('Locust Grove','AR'),('Locust Grove','GA'),('Locust Grove','OK'),('Locust Grove','VA'),('Locust Hill','VA'),('Locust Valley','NY'),('Locustdale','PA'),('Locustville','VA'),('Loda','IL'),('Lodge','SC'),('Lodge Grass','MT'),('Lodgepole','NE'),('Lodgepole','SD'),('Lodi','CA'),('Lodi','MO'),('Lodi','NJ'),('Lodi','NY'),('Lodi','OH'),('Lodi','TX'),('Lodi','WI'),('Log Lane Village','CO'),('Logan','AL'),('Logan','IA'),('Logan','IL'),('Logan','KS'),('Logan','NM'),('Logan','OH'),('Logan','UT'),('Logan','WV'),('Logandale','NV'),('Logansport','IN'),('Logansport','LA'),('Loganton','PA'),('Loganville','GA'),('Loganville','PA'),('Loganville','WI'),('Logsden','OR'),('Lohman','MO'),('Lohn','TX'),('Lohrville','IA'),('Loiza','PR'),('Loleta','CA'),('Lolita','TX'),('Lolo','MT'),('Loma','CO'),('Loma','MT'),('Loma Linda','CA'),('Loma Mar','CA'),('Loman','MN'),('Lomax','IL'),('Lombard','IL'),('Lometa','TX'),('Lomira','WI'),('Lomita','CA'),('Lompoc','CA'),('Lonaconing','MD'),('London','AR'),('London','KY'),('London','OH'),('London','TX'),('London','WV'),('London Mills','IL'),('Londonderry','NH'),('Londonderry','OH'),('Londonderry','VT'),('Lone','KY'),('Lone Grove','OK'),('Lone Jack','MO'),('Lone Mountain','TN'),('Lone Oak','TX'),('Lone Pine','CA'),('Lone Rock','IA'),('Lone Rock','WI'),('Lone Star','TX'),('Lone Tree','IA'),('Lone Wolf','OK'),('Lonedell','MO'),('Lonepine','MT'),('Lonetree','WY'),('Long Barn','CA'),('Long Beach','CA'),('Long Beach','MS'),('Long Beach','NY'),('Long Beach','WA'),('Long Bottom','OH'),('Long Branch','NJ'),('Long Branch','TX'),('Long Creek','OR'),('Long Creek','SC'),('Long Eddy','NY'),('Long Green','MD'),('Long Grove','IA'),('Long Grove','IL'),('Long Island','KS'),('Long Island','ME'),('Long Island','VA'),('Long Island City','NY'),('Long Key','FL'),('Long Lake','MI'),('Long Lake','MN'),('Long Lake','NY'),('Long Lake','WI'),('Long Lane','MO'),('Long Pine','NE'),('Long Point','IL'),('Long Pond','PA'),('Long Prairie','MN'),('Long Valley','NJ'),('Long Valley','SD'),('Longboat Key','FL'),('Longbranch','WA'),('Longdale','OK'),('Longford','KS'),('Longlake','SD'),('Longleaf','LA'),('Longmeadow','MA'),('Longmire','WA'),('Longmont','CO'),('Longport','NJ'),('Longs','SC'),('Longstreet','LA'),('Longton','KS'),('Longview','IL'),('Longview','TX'),('Longview','WA'),('Longville','LA'),('Longville','MN'),('Longwood','FL'),('Longwood','NC'),('Lonoke','AR'),('Lonsdale','AR'),('Lonsdale','MN'),('Loogootee','IL'),('Loogootee','IN'),('Lookeba','OK'),('Lookout','CA'),('Lookout','KY'),('Lookout','WV'),('Lookout Mountain','GA'),('Lookout Mountain','TN'),('Loomis','CA'),('Loomis','NE'),('Loomis','WA'),('Loon Lake','WA'),('Looneyville','WV'),('Loop','TX'),('Loose Creek','MO'),('Lopeno','TX'),('Lopez','PA'),('Lopez Island','WA'),('Lorado','WV'),('Lorain','OH'),('Loraine','IL'),('Loraine','TX'),('Lorane','OR'),('Loranger','LA'),('Lordsburg','NM'),('Lore City','OH'),('Loreauville','LA'),('Lorena','TX'),('Lorentz','WV'),('Lorenzo','TX'),('Loretto','KY'),('Loretto','MI'),('Loretto','MN'),('Loretto','PA'),('Loretto','TN'),('Loretto','VA'),('Lorida','FL'),('Lorimor','IA'),('Loring','MT'),('Loris','SC'),('Lorman','MS'),('Lorraine','KS'),('Lorraine','NY'),('Lorton','NE'),('Lorton','VA'),('Los Alamitos','CA'),('Los Alamos','CA'),('Los Alamos','NM'),('Los Altos','CA'),('Los Angeles','CA'),('Los Banos','CA'),('Los Ebanos','TX'),('Los Fresnos','TX'),('Los Gatos','CA'),('Los Indios','TX'),('Los Lunas','NM'),('Los Molinos','CA'),('Los Ojos','NM'),('Los Olivos','CA'),('Los Osos','CA'),('Losantville','IN'),('Lost City','WV'),('Lost Creek','KY'),('Lost Creek','PA'),('Lost Creek','WV'),('Lost Hills','CA'),('Lost Nation','IA'),('Lost Springs','KS'),('Lost Springs','WY'),('Lostant','IL'),('Lostine','OR'),('Lothair','MT'),('Lothian','MD'),('Lott','TX'),('Lottie','LA'),('Lottsburg','VA'),('Lotus','CA'),('Louann','AR'),('Loudon','NH'),('Loudon','TN'),('Loudonville','OH'),('Loughman','FL'),('Louin','MS'),('Louisa','KY'),('Louisa','VA'),('Louisburg','KS'),('Louisburg','MO'),('Louisburg','NC'),('Louise','MS'),('Louise','TX'),('Louisiana','MO'),('Louisville','AL'),('Louisville','CO'),('Louisville','GA'),('Louisville','IL'),('Louisville','KY'),('Louisville','MS'),('Louisville','NE'),('Louisville','OH'),('Louisville','TN'),('Loup City','NE'),('Louvale','GA'),('Louviers','CO'),('Lovejoy','GA'),('Lovejoy','IL'),('Lovelaceville','KY'),('Lovelady','TX'),('Loveland','CO'),('Loveland','OH'),('Loveland','OK'),('Lovell','ME'),('Lovell','WY'),('Lovelock','NV'),('Lovely','KY'),('Loves Park','IL'),('Lovettsville','VA'),('Loveville','MD'),('Lovilia','IA'),('Loving','NM'),('Loving','TX'),('Lovingston','VA'),('Lovington','IL'),('Lovington','NM'),('Low Moor','IA'),('Low Moor','VA'),('Lowake','TX'),('Lowber','PA'),('Lowden','IA'),('Lowder','IL'),('Lowell','AR'),('Lowell','FL'),('Lowell','IN'),('Lowell','MA'),('Lowell','MI'),('Lowell','NC'),('Lowell','OH'),('Lowell','OR'),('Lowell','VT'),('Lowell','WI'),('Lowellville','OH'),('Lower Brule','SD'),('Lower Kalskag','AK'),('Lower Lake','CA'),('Lower Peach Tree','AL'),('Lower Salem','OH'),('Lower Waterford','VT'),('Lowes','KY'),('Lowgap','NC'),('Lowland','NC'),('Lowland','TN'),('Lowman','ID'),('Lowman','NY'),('Lowmansville','KY'),('Lowndes','MO'),('Lowndesboro','AL'),('Lowndesville','SC'),('Lowpoint','IL'),('Lowry','MN'),('Lowry','VA'),('Lowry City','MO'),('Lowville','NY'),('Loxahatchee','FL'),('Loxley','AL'),('Loyal','OK'),('Loyal','WI'),('Loyalhanna','PA'),('Loyall','KY'),('Loyalton','CA'),('Loysburg','PA'),('Loysville','PA'),('Lozano','TX'),('Lu Verne','IA'),('Luana','IA'),('Lubbock','TX'),('Lubec','ME'),('Lublin','WI'),('Lucama','NC'),('Lucan','MN'),('Lucas','IA'),('Lucas','KS'),('Lucas','KY'),('Lucas','OH'),('Lucasville','OH'),('Lucedale','MS'),('Lucerne','CA'),('Lucerne','CO'),('Lucerne','IN'),('Lucerne','MO'),('Lucerne Valley','CA'),('Lucernemines','PA'),('Lucien','OK'),('Lucile','ID'),('Lucinda','PA'),('Luck','WI'),('Luckey','OH'),('Ludell','KS'),('Ludington','MI'),('Ludlow','CA'),('Ludlow','IL'),('Ludlow','MA'),('Ludlow','MO'),('Ludlow','MS'),('Ludlow','PA'),('Ludlow','SD'),('Ludlow','VT'),('Ludlow Falls','OH'),('Ludowici','GA'),('Luebbering','MO'),('Lueders','TX'),('Lufkin','TX'),('Lugoff','SC'),('Lukachukai','AZ'),('Luke','MD'),('Lukeville','AZ'),('Lula','GA'),('Lula','MS'),('Luling','LA'),('Luling','TX'),('Lulu','FL'),('Lumber Bridge','NC'),('Lumber City','GA'),('Lumberport','WV'),('Lumberton','MS'),('Lumberton','NC'),('Lumberton','NJ'),('Lumberton','TX'),('Lumberville','PA'),('Lummi Island','WA'),('Lumpkin','GA'),('Luna','NM'),('Luna Pier','MI'),('Lund','NV'),('Lunenburg','MA'),('Lunenburg','VA'),('Lunenburg','VT'),('Luning','NV'),('Lupton','AZ'),('Lupton','MI'),('Lupton City','TN'),('Luquillo','PR'),('Luray','KS'),('Luray','MO'),('Luray','SC'),('Luray','TN'),('Luray','VA'),('Lurgan','PA'),('Lusby','MD'),('Lusk','WY'),('Lutcher','LA'),('Luther','IA'),('Luther','MI'),('Luther','OK'),('Luthersburg','PA'),('Luthersville','GA'),('Lutherville Timonium','MD'),('Lutsen','MN'),('Luttrell','TN'),('Lutts','TN'),('Lutz','FL'),('Luverne','AL'),('Luverne','MN'),('Luverne','ND'),('Luxemburg','IA'),('Luxemburg','WI'),('Luxor','PA'),('Luxora','AR'),('Luzerne','IA'),('Luzerne','MI'),('Luzerne','PA'),('Lyburn','WV'),('Lycoming','NY'),('Lydia','LA'),('Lydia','SC'),('Lyerly','GA'),('Lyford','TX'),('Lykens','PA'),('Lyle','MN'),('Lyle','WA'),('Lyles','TN'),('Lyman','NE'),('Lyman','SC'),('Lyman','UT'),('Lyman','WA'),('Lyman','WY'),('Lyme','NH'),('Lyme Center','NH'),('Lynbrook','NY'),('Lynch','KY'),('Lynch','NE'),('Lynch Station','VA'),('Lynchburg','MO'),('Lynchburg','OH'),('Lynchburg','SC'),('Lynchburg','TN'),('Lynchburg','VA'),('Lynco','WV'),('Lynd','MN'),('Lyndeborough','NH'),('Lyndell','PA'),('Lynden','WA'),('Lyndhurst','NJ'),('Lyndhurst','VA'),('Lyndon','IL'),('Lyndon','KS'),('Lyndon','VT'),('Lyndon Center','VT'),('Lyndon Station','WI'),('Lyndonville','NY'),('Lyndonville','VT'),('Lyndora','PA'),('Lynn','AL'),('Lynn','AR'),('Lynn','IN'),('Lynn','MA'),('Lynn','NC'),('Lynn Center','IL'),('Lynn Haven','FL'),('Lynndyl','UT'),('Lynnfield','MA'),('Lynnville','IA'),('Lynnville','IN'),('Lynnville','KY'),('Lynnville','TN'),('Lynnwood','WA'),('Lynwood','CA'),('Lynx','OH'),('Lynxville','WI'),('Lyon','MS'),('Lyon Mountain','NY'),('Lyon Station','PA'),('Lyons','CO'),('Lyons','GA'),('Lyons','IL'),('Lyons','IN'),('Lyons','KS'),('Lyons','MI'),('Lyons','NE'),('Lyons','NJ'),('Lyons','NY'),('Lyons','OH'),('Lyons','OR'),('Lyons','SD'),('Lyons','TX'),('Lyons','WI'),('Lyons Falls','NY'),('Lysite','WY'),('Lytle','TX'),('Lytle Creek','CA'),('Lytton','IA'),('M C B H Kaneohe Bay','HI'),('Mabank','TX'),('Mabel','MN'),('Mabelvale','AR'),('Maben','MS'),('Maben','WV'),('Mabie','WV'),('Mableton','GA'),('Mabscott','WV'),('Mabton','WA'),('Mac Arthur','WV'),('Macatawa','MI'),('Macclenny','FL'),('Macclesfield','NC'),('Macdoel','CA'),('Macdona','TX'),('Macedon','NY'),('Macedonia','IA'),('Macedonia','IL'),('Macedonia','OH'),('Maceo','KY'),('Macfarlan','WV'),('Machesney Park','IL'),('Machias','ME'),('Machias','NY'),('Machiasport','ME'),('Machipongo','VA'),('Mack','CO'),('Mackay','ID'),('Mackey','IN'),('Mackeyville','PA'),('Mackinac Island','MI'),('Mackinaw','IL'),('Mackinaw City','MI'),('Macks Creek','MO'),('Macks Inn','ID'),('Macksburg','IA'),('Macksburg','OH'),('Macksville','KS'),('Mackville','KY'),('Macomb','IL'),('Macomb','MI'),('Macomb','MO'),('Macomb','OK'),('Macon','GA'),('Macon','IL'),('Macon','MO'),('Macon','MS'),('Macon','NC'),('Macon','TN'),('Macon','VA'),('Macungie','PA'),('Macy','IN'),('Macy','NE'),('Mad River','CA'),('Madawaska','ME'),('Madbury','NH'),('Madden','MS'),('Maddock','ND'),('Madelia','MN'),('Madeline','CA'),('Madera','CA'),('Madera','PA'),('Madill','OK'),('Madison','AL'),('Madison','AR'),('Madison','CA'),('Madison','CT'),('Madison','FL'),('Madison','GA'),('Madison','IL'),('Madison','IN'),('Madison','KS'),('Madison','MD'),('Madison','ME'),('Madison','MN'),('Madison','MO'),('Madison','MS'),('Madison','NC'),('Madison','NE'),('Madison','NH'),('Madison','NJ'),('Madison','NY'),('Madison','OH'),('Madison','PA'),('Madison','SD'),('Madison','TN'),('Madison','VA'),('Madison','WI'),('Madison','WV'),('Madison Heights','MI'),('Madison Heights','VA'),('Madison Lake','MN'),('Madisonburg','PA'),('Madisonville','KY'),('Madisonville','LA'),('Madisonville','TN'),('Madisonville','TX'),('Madras','OR'),('Madrid','IA'),('Madrid','NE'),('Madrid','NY'),('Maeystown','IL'),('Magalia','CA'),('Magazine','AR'),('Magdalena','NM'),('Magee','MS'),('Maggie Valley','NC'),('Magna','UT'),('Magness','AR'),('Magnet','NE'),('Magnetic Springs','OH'),('Magnolia','AL'),('Magnolia','AR'),('Magnolia','DE'),('Magnolia','IA'),('Magnolia','IL'),('Magnolia','KY'),('Magnolia','MN'),('Magnolia','MS'),('Magnolia','NC'),('Magnolia','NJ'),('Magnolia','OH'),('Magnolia','TX'),('Magnolia Springs','AL'),('Mahanoy City','PA'),('Mahanoy Plane','PA'),('Mahaska','KS'),('Mahnomen','MN'),('Mahomet','IL'),('Mahopac','NY'),('Mahopac Falls','NY'),('Mahwah','NJ'),('Maida','ND'),('Maiden','NC'),('Maiden Rock','WI'),('Maidens','VA'),('Maidsville','WV'),('Maine','NY'),('Mainesburg','PA'),('Maineville','OH'),('Mainland','PA'),('Maitland','FL'),('Maitland','MO'),('Maize','KS'),('Majestic','KY'),('Makanda','IL'),('Makawao','HI'),('Makaweli','HI'),('Makinen','MN'),('Makoti','ND'),('Malabar','FL'),('Malad City','ID'),('Malaga','NJ'),('Malaga','NM'),('Malaga','OH'),('Malaga','WA'),('Malakoff','TX'),('Malcolm','AL'),('Malcolm','NE'),('Malcom','IA'),('Malden','IL'),('Malden','MA'),('Malden','MO'),('Malden','WA'),('Malden Bridge','NY'),('Malden On Hudson','NY'),('Malibu','CA'),('Malin','OR'),('Malinta','OH'),('Maljamar','NM'),('Mallard','IA'),('Mallie','KY'),('Mallory','NY'),('Mallory','WV'),('Malmo','NE'),('Malmstrom A F B','MT'),('Malo','WA'),('Malone','FL'),('Malone','KY'),('Malone','NY'),('Malone','TX'),('Malone','WA'),('Malone','WI'),('Malott','WA'),('Malta','ID'),('Malta','IL'),('Malta','MT'),('Malta','OH'),('Malta Bend','MO'),('Malvern','AL'),('Malvern','AR'),('Malvern','IA'),('Malvern','OH'),('Malvern','PA'),('Malverne','NY'),('Mamaroneck','NY'),('Mamers','NC'),('Mammoth','AZ'),('Mammoth','PA'),('Mammoth','WV'),('Mammoth Cave','KY'),('Mammoth Lakes','CA'),('Mammoth Spring','AR'),('Mamou','LA'),('Man','WV'),('Manahawkin','NJ'),('Manakin Sabot','VA'),('Manasquan','NJ'),('Manassa','CO'),('Manassas','GA'),('Manassas','VA'),('Manati','PR'),('Manawa','WI'),('Mancelona','MI'),('Manchaca','TX'),('Manchaug','MA'),('Manchester','CA'),('Manchester','CT'),('Manchester','GA'),('Manchester','IA'),('Manchester','IL'),('Manchester','KY'),('Manchester','MA'),('Manchester','MD'),('Manchester','ME'),('Manchester','MI'),('Manchester','NH'),('Manchester','NY'),('Manchester','OH'),('Manchester','OK'),('Manchester','PA'),('Manchester','TN'),('Manchester','VT'),('Manchester','WA'),('Manchester Center','VT'),('Manchester Township','NJ'),('Mancos','CO'),('Mandan','ND'),('Mandaree','ND'),('Manderson','SD'),('Manderson','WY'),('Mandeville','LA'),('Mangham','LA'),('Mango','FL'),('Mangum','OK'),('Manhasset','NY'),('Manhattan','IL'),('Manhattan','KS'),('Manhattan','MT'),('Manhattan','NV'),('Manhattan Beach','CA'),('Manheim','PA'),('Manila','AR'),('Manila','UT'),('Manilla','IA'),('Manilla','IN'),('Manistee','MI'),('Manistique','MI'),('Manito','IL'),('Manitou','KY'),('Manitou','OK'),('Manitou Beach','MI'),('Manitou Springs','CO'),('Manitowish Waters','WI'),('Manitowoc','WI'),('Mankato','KS'),('Mankato','MN'),('Manley','NE'),('Manley Hot Springs','AK'),('Manlius','IL'),('Manlius','NY'),('Manly','IA'),('Mannboro','VA'),('Mannford','OK'),('Manning','IA'),('Manning','ND'),('Manning','OR'),('Manning','SC'),('Mannington','WV'),('Manns Choice','PA'),('Manns Harbor','NC'),('Mannsville','KY'),('Mannsville','NY'),('Mannsville','OK'),('Manokin','MD'),('Manokotak','AK'),('Manomet','MA'),('Manor','GA'),('Manor','PA'),('Manor','TX'),('Manorville','NY'),('Manorville','PA'),('Manquin','VA'),('Mansfield','AR'),('Mansfield','GA'),('Mansfield','IL'),('Mansfield','LA'),('Mansfield','MA'),('Mansfield','MO'),('Mansfield','OH'),('Mansfield','PA'),('Mansfield','SD'),('Mansfield','TN'),('Mansfield','TX'),('Mansfield','WA'),('Mansfield Center','CT'),('Mansfield Depot','CT'),('Manson','IA'),('Manson','NC'),('Manson','WA'),('Mansura','LA'),('Mantachie','MS'),('Mantador','ND'),('Manteca','CA'),('Mantee','MS'),('Manteno','IL'),('Manteo','NC'),('Manter','KS'),('Manti','UT'),('Mantoloking','NJ'),('Manton','CA'),('Manton','MI'),('Mantorville','MN'),('Mantua','NJ'),('Mantua','OH'),('Mantua','UT'),('Manvel','ND'),('Manvel','TX'),('Manville','NJ'),('Manville','RI'),('Manville','WY'),('Many','LA'),('Many Farms','AZ'),('Manzanita','OR'),('Manzanola','CO'),('Mapaville','MO'),('Maple','NC'),('Maple','TX'),('Maple','WI'),('Maple City','KS'),('Maple City','MI'),('Maple Falls','WA'),('Maple Heights','OH'),('Maple Hill','KS'),('Maple Hill','NC'),('Maple Lake','MN'),('Maple Mount','KY'),('Maple Park','IL'),('Maple Plain','MN'),('Maple Rapids','MI'),('Maple Shade','NJ'),('Maple Springs','NY'),('Maple Valley','WA'),('Maple View','NY'),('Maplecrest','NY'),('Maplesville','AL'),('Mapleton','IA'),('Mapleton','IL'),('Mapleton','KS'),('Mapleton','ME'),('Mapleton','MN'),('Mapleton','ND'),('Mapleton','OR'),('Mapleton','UT'),('Mapleton Depot','PA'),('Mapleville','RI'),('Maplewood','NJ'),('Maplewood','OH'),('Maplewood','WI'),('Mappsville','VA'),('Maquoketa','IA'),('Maquon','IL'),('Mar Lin','PA'),('Maramec','OK'),('Marana','AZ'),('Marathon','FL'),('Marathon','IA'),('Marathon','NY'),('Marathon','OH'),('Marathon','TX'),('Marathon','WI'),('Marathon Shores','FL'),('Marble','MN'),('Marble','NC'),('Marble','PA'),('Marble Canyon','AZ'),('Marble City','OK'),('Marble Falls','AR'),('Marble Falls','TX'),('Marble Hill','GA'),('Marble Hill','MO'),('Marble Rock','IA'),('Marblehead','MA'),('Marblemount','WA'),('Marbury','AL'),('Marbury','MD'),('Marceline','MO'),('Marcell','MN'),('Marcella','AR'),('Marcellus','MI'),('Marcellus','NY'),('March Air Reserve Base','CA'),('Marchand','PA'),('Marco Island','FL'),('Marcola','OR'),('Marcus','IA'),('Marcus','WA'),('Marcus Hook','PA'),('Marcy','NY'),('Mardela Springs','MD'),('Marengo','IA'),('Marengo','IL'),('Marengo','IN'),('Marengo','OH'),('Marengo','WI'),('Marenisco','MI'),('Marfa','TX'),('Margaret','AL'),('Margarettsville','NC'),('Margaretville','NY'),('Margate City','NJ'),('Margie','MN'),('Maria Stein','OH'),('Mariah Hill','IN'),('Marianna','AR'),('Marianna','FL'),('Marianna','PA'),('Marianna','WV'),('Maribel','WI'),('Maricao','PR'),('Maricopa','AZ'),('Maricopa','CA'),('Marienthal','KS'),('Marienville','PA'),('Marietta','GA'),('Marietta','IL'),('Marietta','MN'),('Marietta','MS'),('Marietta','NC'),('Marietta','NY'),('Marietta','OH'),('Marietta','OK'),('Marietta','PA'),('Marietta','SC'),('Marietta','TX'),('Marilla','NY'),('Marina','CA'),('Marina Del Rey','CA'),('Marine','IL'),('Marine City','MI'),('Marine On Saint Croix','MN'),('Marinette','WI'),('Maringouin','LA'),('Marion','AL'),('Marion','AR'),('Marion','CT'),('Marion','IA'),('Marion','IL'),('Marion','IN'),('Marion','KS'),('Marion','KY'),('Marion','LA'),('Marion','MA'),('Marion','MI'),('Marion','MS'),('Marion','MT'),('Marion','NC'),('Marion','ND'),('Marion','NY'),('Marion','OH'),('Marion','PA'),('Marion','SC'),('Marion','SD'),('Marion','TX'),('Marion','VA'),('Marion','WI'),('Marion Center','PA'),('Marion Heights','PA'),('Marion Junction','AL'),('Marion Station','MD'),('Marionville','MO'),('Marionville','VA'),('Mariposa','CA'),('Marissa','IL'),('Mark','IL'),('Mark Center','OH'),('Marked Tree','AR'),('Markesan','WI'),('Markham','IL'),('Markham','TX'),('Markham','VA'),('Markle','IN'),('Markleeville','CA'),('Markleton','PA'),('Markleville','IN'),('Markleysburg','PA'),('Marks','MS'),('Marksville','LA'),('Marland','OK'),('Marlboro','NJ'),('Marlboro','NY'),('Marlboro','VT'),('Marlborough','CT'),('Marlborough','MA'),('Marlborough','NH'),('Marlette','MI'),('Marlin','TX'),('Marlin','WA'),('Marlinton','WV'),('Marlow','NH'),('Marlow','OK'),('Marlton','NJ'),('Marmaduke','AR'),('Marmarth','ND'),('Marmora','NJ'),('Marne','IA'),('Marne','MI'),('Maroa','IL'),('Marquand','MO'),('Marquette','IA'),('Marquette','KS'),('Marquette','MI'),('Marquette','NE'),('Marquette','WI'),('Marquez','TX'),('Marrero','LA'),('Marriottsville','MD'),('Marrowbone','KY'),('Mars','PA'),('Mars Hill','ME'),('Mars Hill','NC'),('Marseilles','IL'),('Marshall','AK'),('Marshall','AR'),('Marshall','CA'),('Marshall','IL'),('Marshall','IN'),('Marshall','MI'),('Marshall','MN'),('Marshall','MO'),('Marshall','NC'),('Marshall','ND'),('Marshall','OK'),('Marshall','TX'),('Marshall','VA'),('Marshall','WA'),('Marshall','WI'),('Marshallberg','NC'),('Marshalls Creek','PA'),('Marshalltown','IA'),('Marshallville','GA'),('Marshallville','OH'),('Marshes Siding','KY'),('Marshfield','MA'),('Marshfield','MO'),('Marshfield','VT'),('Marshfield','WI'),('Marshfield Hills','MA'),('Marshville','NC'),('Marsing','ID'),('Marsland','NE'),('Marsteller','PA'),('Marston','MO'),('Marston','NC'),('Marstons Mills','MA'),('Mart','TX'),('Martel','OH'),('Martell','CA'),('Martell','NE'),('Martelle','IA'),('Martensdale','IA'),('Martha','KY'),('Martha','OK'),('Marthasville','MO'),('Marthaville','LA'),('Martin','GA'),('Martin','KY'),('Martin','MI'),('Martin','ND'),('Martin','OH'),('Martin','PA'),('Martin','SC'),('Martin','SD'),('Martin','TN'),('Martin City','MT'),('Martindale','PA'),('Martindale','TX'),('Martinez','CA'),('Martins Creek','PA'),('Martins Ferry','OH'),('Martinsburg','IA'),('Martinsburg','MO'),('Martinsburg','NY'),('Martinsburg','OH'),('Martinsburg','PA'),('Martinsburg','WV'),('Martinsdale','MT'),('Martinsville','IL'),('Martinsville','IN'),('Martinsville','MO'),('Martinsville','NJ'),('Martinsville','OH'),('Martinsville','TX'),('Martinsville','VA'),('Martinton','IL'),('Martville','NY'),('Marty','SD'),('Marvel','CO'),('Marvell','AR'),('Marvin','SD'),('Mary Alice','KY'),('Mary D','PA'),('Mary Esther','FL'),('Marydel','DE'),('Marydel','MD'),('Maryknoll','NY'),('Maryland','NY'),('Maryland Heights','MO'),('Maryland Line','MD'),('Marylhurst','OR'),('Maryneal','TX'),('Marysvale','UT'),('Marysville','CA'),('Marysville','IN'),('Marysville','KS'),('Marysville','MI'),('Marysville','MT'),('Marysville','OH'),('Marysville','PA'),('Marysville','WA'),('Maryus','VA'),('Maryville','IL'),('Maryville','MO'),('Maryville','TN'),('Mascot','TN'),('Mascot','VA'),('Mascotte','FL'),('Mascoutah','IL'),('Mashantucket','CT'),('Mashpee','MA'),('Maskell','NE'),('Mason','IL'),('Mason','KY'),('Mason','MI'),('Mason','OH'),('Mason','TN'),('Mason','TX'),('Mason','WI'),('Mason','WV'),('Mason City','IA'),('Mason City','IL'),('Mason City','NE'),('Masonic Home','KY'),('Masontown','PA'),('Masontown','WV'),('Masonville','CO'),('Masonville','IA'),('Masonville','NY'),('Maspeth','NY'),('Mass City','MI'),('Massapequa','NY'),('Massapequa Park','NY'),('Massena','IA'),('Massena','NY'),('Massey','MD'),('Massillon','OH'),('Masterson','TX'),('Mastic','NY'),('Mastic Beach','NY'),('Masury','OH'),('Matador','TX'),('Matagorda','TX'),('Matamoras','PA'),('Matawan','NJ'),('Matewan','WV'),('Matfield Green','KS'),('Matheny','WV'),('Mather','CA'),('Mather','PA'),('Mather','WI'),('Matherville','IL'),('Matheson','CO'),('Mathews','AL'),('Mathews','LA'),('Mathews','VA'),('Mathias','WV'),('Mathis','TX'),('Mathiston','MS'),('Matinicus','ME'),('Matlock','IA'),('Matlock','WA'),('Matoaka','WV'),('Mattapan','MA'),('Mattapoisett','MA'),('Mattaponi','VA'),('Mattawa','WA'),('Mattawamkeag','ME'),('Mattawan','MI'),('Mattawana','PA'),('Matteson','IL'),('Matthews','GA'),('Matthews','IN'),('Matthews','MO'),('Matthews','NC'),('Mattituck','NY'),('Mattoon','IL'),('Mattoon','WI'),('Mauckport','IN'),('Maud','OK'),('Maud','TX'),('Maugansville','MD'),('Mauk','GA'),('Mauldin','SC'),('Maumee','OH'),('Maumelle','AR'),('Maunabo','PR'),('Maunaloa','HI'),('Maunie','IL'),('Maupin','OR'),('Maurepas','LA'),('Maurertown','VA'),('Maurice','IA'),('Maurice','LA'),('Mauricetown','NJ'),('Mauriceville','TX'),('Maury','NC'),('Maury City','TN'),('Mauston','WI'),('Mavisdale','VA'),('Max','MN'),('Max','ND'),('Max','NE'),('Max Meadows','VA'),('Maxatawny','PA'),('Maxbass','ND'),('Maxeys','GA'),('Maxie','VA'),('Maximo','OH'),('Maxton','NC'),('Maxwell','CA'),('Maxwell','IA'),('Maxwell','IN'),('Maxwell','NE'),('Maxwell','NM'),('Maxwell','TX'),('Maxwelton','WV'),('May','ID'),('May','OK'),('May','TX'),('Mayaguez','PR'),('Maybee','MI'),('Maybell','CO'),('Maybeury','WV'),('Maybrook','NY'),('Maydelle','TX'),('Mayer','AZ'),('Mayer','MN'),('Mayersville','MS'),('Mayesville','SC'),('Mayetta','KS'),('Mayfield','KS'),('Mayfield','KY'),('Mayfield','MI'),('Mayfield','NY'),('Mayfield','UT'),('Mayflower','AR'),('Mayhew','MS'),('Mayhill','NM'),('Mayking','KY'),('Maylene','AL'),('Maynard','AR'),('Maynard','IA'),('Maynard','MA'),('Maynard','MN'),('Maynard','OH'),('Maynardville','TN'),('Mayo','FL'),('Mayo','MD'),('Mayo','SC'),('Mayodan','NC'),('Maypearl','TX'),('Mayport','PA'),('Mays','IN'),('Mays Landing','NJ'),('Maysel','WV'),('Mayslick','KY'),('Maysville','AR'),('Maysville','GA'),('Maysville','KY'),('Maysville','MO'),('Maysville','NC'),('Maysville','OK'),('Maysville','WV'),('Maytown','PA'),('Mayview','MO'),('Mayville','MI'),('Mayville','ND'),('Mayville','NY'),('Mayville','WI'),('Maywood','CA'),('Maywood','IL'),('Maywood','MO'),('Maywood','NE'),('Maywood','NJ'),('Mazama','WA'),('Mazeppa','MN'),('Mazie','KY'),('Mazomanie','WI'),('Mazon','IL'),('Mc Adams','MS'),('Mc Adenville','NC'),('Mc Afee','NJ'),('Mc Alister','NM'),('Mc Alisterville','PA'),('Mc Allister','MT'),('Mc Alpin','FL'),('Mc Andrews','KY'),('Mc Arthur','OH'),('Mc Bain','MI'),('Mc Bee','SC'),('Mc Bride','MO'),('Mc Call Creek','MS'),('Mc Calla','AL'),('Mc Callsburg','IA'),('Mc Camey','TX'),('Mc Carley','MS'),('Mc Carr','KY'),('Mc Caskill','AR'),('Mc Caulley','TX'),('Mc Causland','IA'),('Mc Caysville','GA'),('Mc Clave','CO'),('Mc Clelland','IA'),('Mc Clellandtown','PA'),('Mc Clellanville','SC'),('Mc Clure','IL'),('Mc Clure','OH'),('Mc Clure','PA'),('Mc Clure','VA'),('Mc Clurg','MO'),('Mc Coll','SC'),('Mc Comb','OH'),('Mc Connell','IL'),('Mc Connells','SC'),('Mc Connellsburg','PA'),('Mc Connellstown','PA'),('Mc Connellsville','NY'),('Mc Cook','NE'),('Mc Cool','MS'),('Mc Cool Junction','NE'),('Mc Cordsville','IN'),('Mc Cormick','SC'),('Mc Coy','CO'),('Mc Coy','VA'),('Mc Cracken','KS'),('Mc Crory','AR'),('Mc Cune','KS'),('Mc Cutchenville','OH'),('Mc Dade','TX'),('Mc Daniels','KY'),('Mc David','FL'),('Mc Dermitt','NV'),('Mc Dermott','OH'),('Mc Donald','KS'),('Mc Donald','NM'),('Mc Donald','OH'),('Mc Donald','PA'),('Mc Donald','TN'),('Mc Donough','NY'),('Mc Dougal','AR'),('Mc Dowell','KY'),('Mc Dowell','VA'),('Mc Elhattan','PA'),('Mc Ewen','TN'),('Mc Ewensville','PA'),('Mc Fall','MO'),('Mc Farlan','NC'),('Mc Farland','CA'),('Mc Farland','KS'),('Mc Farland','WI'),('Mc Gaheysville','VA'),('Mc Gee','MO'),('Mc Gehee','AR'),('Mc Gill','NV'),('Mc Girk','MO'),('Mc Grady','NC'),('Mc Grann','PA'),('Mc Grath','AK'),('Mc Grath','MN'),('Mc Graw','NY'),('Mc Graws','WV'),('Mc Gregor','IA'),('Mc Gregor','TX'),('Mc Guffey','OH'),('Mc Henry','KY'),('Mc Henry','MD'),('Mc Henry','MS'),('Mc Indoe Falls','VT'),('Mc Intire','IA'),('Mc Intosh','AL'),('Mc Intosh','FL'),('Mc Intosh','NM'),('Mc Intosh','SD'),('Mc Intyre','GA'),('Mc Intyre','PA'),('Mc Kean','PA'),('Mc Kee','KY'),('Mc Kees Rocks','PA'),('Mc Kenney','VA'),('Mc Kenzie','AL'),('Mc Kenzie','TN'),('Mc Kinney','KY'),('Mc Kinnon','WY'),('Mc Kittrick','CA'),('Mc Knightstown','PA'),('Mc Lain','MS'),('Mc Laughlin','SD'),('Mc Lean','IL'),('Mc Lean','NY'),('Mc Lean','VA'),('Mc Leansboro','IL'),('Mc Leansville','NC'),('Mc Lemoresville','TN'),('Mc Leod','MT'),('Mc Leod','TX'),('Mc Louth','KS'),('Mc Millan','MI'),('Mc Minnville','TN'),('Mc Nabb','IL'),('Mc Naughton','WI'),('Mc Neal','AZ'),('Mc Neil','AR'),('Mc Neil','TX'),('Mc Neill','MS'),('Mc Quady','KY'),('Mc Queeney','TX'),('Mc Rae','AR'),('Mc Rae','GA'),('Mc Roberts','KY'),('Mc Shan','AL'),('Mc Sherrystown','PA'),('Mc Veytown','PA'),('Mc Williams','AL'),('Mcadoo','PA'),('Mcadoo','TX'),('Mcalester','OK'),('Mcallen','TX'),('Mcarthur','CA'),('Mcbrides','MI'),('Mccall','ID'),('Mccammon','ID'),('Mcchord Afb','WA'),('Mccleary','WA'),('Mcclellan','CA'),('Mccloud','CA'),('Mcclusky','ND'),('Mccomb','MS'),('Mcconnell Afb','KS'),('Mcconnelsville','OH'),('Mccurtain','OK'),('Mccutcheon Field','NC'),('Mcdaniel','MD'),('Mcdonough','GA'),('Mcfaddin','TX'),('Mcgees Mills','PA'),('Mcgregor','MN'),('Mcgregor','ND'),('Mcgrew','NE'),('Mchenry','IL'),('Mchenry','ND'),('Mcintosh','MN'),('Mckeesport','PA'),('Mckenna','WA'),('Mckinleyville','CA'),('Mckinney','TX'),('Mclean','NE'),('Mclean','TX'),('Mcleod','ND'),('Mcloud','OK'),('Mcmechen','WV'),('Mcminnville','OR'),('Mcnary','AZ'),('Mcpherson','KS'),('Mcville','ND'),('Meacham','OR'),('Mead','CO'),('Mead','NE'),('Mead','OK'),('Mead','WA'),('Meade','KS'),('Meadow','SD'),('Meadow','TX'),('Meadow','UT'),('Meadow Bridge','WV'),('Meadow Creek','WV'),('Meadow Grove','NE'),('Meadow Lands','PA'),('Meadow Valley','CA'),('Meadow Vista','CA'),('Meadowbrook','WV'),('Meadowlands','MN'),('Meadows Of Dan','VA'),('Meadowview','VA'),('Meadview','AZ'),('Meadville','MO'),('Meadville','MS'),('Meadville','PA'),('Meally','KY'),('Means','KY'),('Meansville','GA'),('Mears','MI'),('Mears','VA'),('Mebane','NC'),('Mecca','CA'),('Mecca','IN'),('Mechanic Falls','ME'),('Mechanicsburg','IL'),('Mechanicsburg','OH'),('Mechanicsburg','PA'),('Mechanicstown','OH'),('Mechanicsville','IA'),('Mechanicsville','MD'),('Mechanicsville','PA'),('Mechanicsville','VA'),('Mechanicville','NY'),('Mecklenburg','NY'),('Mecosta','MI'),('Medanales','NM'),('Medaryville','IN'),('Meddybemps','ME'),('Medfield','MA'),('Medford','MA'),('Medford','MN'),('Medford','NJ'),('Medford','NY'),('Medford','OK'),('Medford','OR'),('Medford','WI'),('Media','IL'),('Media','PA'),('Mediapolis','IA'),('Medical Lake','WA'),('Medicine Bow','WY'),('Medicine Lake','MT'),('Medicine Lodge','KS'),('Medicine Park','OK'),('Medimont','ID'),('Medina','ND'),('Medina','NY'),('Medina','OH'),('Medina','TN'),('Medina','TX'),('Medina','WA'),('Medinah','IL'),('Medon','TN'),('Medora','IL'),('Medora','IN'),('Medora','ND'),('Medusa','NY'),('Medway','MA'),('Medway','ME'),('Medway','OH'),('Meeker','CO'),('Meeker','OK'),('Meers','OK'),('Meeteetse','WY'),('Megargel','AL'),('Megargel','TX'),('Mehama','OR'),('Meherrin','VA'),('Mehoopany','PA'),('Meigs','GA'),('Mekinock','ND'),('Mekoryuk','AK'),('Melba','ID'),('Melber','KY'),('Melbeta','NE'),('Melbourne','AR'),('Melbourne','FL'),('Melbourne','IA'),('Melbourne','KY'),('Melbourne Beach','FL'),('Melcher','IA'),('Melcroft','PA'),('Meldrim','GA'),('Melfa','VA'),('Melissa','TX'),('Mellen','WI'),('Mellenville','NY'),('Mellette','SD'),('Mellott','IN'),('Mellwood','AR'),('Melmore','OH'),('Melrose','FL'),('Melrose','IA'),('Melrose','LA'),('Melrose','MA'),('Melrose','MN'),('Melrose','MT'),('Melrose','NM'),('Melrose','NY'),('Melrose','OH'),('Melrose','WI'),('Melrose Park','IL'),('Melrude','MN'),('Melstone','MT'),('Melvern','KS'),('Melville','LA'),('Melville','MT'),('Melville','NY'),('Melvin','AL'),('Melvin','IA'),('Melvin','IL'),('Melvin','KY'),('Melvin','MI'),('Melvin','TX'),('Melvin Village','NH'),('Melvindale','MI'),('Memphis','IN'),('Memphis','MI'),('Memphis','MO'),('Memphis','NE'),('Memphis','NY'),('Memphis','TN'),('Memphis','TX'),('Mena','AR'),('Menahga','MN'),('Menan','ID'),('Menard','IL'),('Menard','TX'),('Menasha','WI'),('Mendenhall','MS'),('Mendenhall','PA'),('Mendham','NJ'),('Mendocino','CA'),('Mendon','IL'),('Mendon','MA'),('Mendon','MI'),('Mendon','MO'),('Mendon','NY'),('Mendon','OH'),('Mendon','UT'),('Mendota','CA'),('Mendota','IL'),('Mendota','MN'),('Mendota','VA'),('Menemsha','MA'),('Menifee','AR'),('Menifee','CA'),('Menlo','GA'),('Menlo','IA'),('Menlo','WA'),('Menlo Park','CA'),('Menno','SD'),('Meno','OK'),('Menoken','ND'),('Menominee','MI'),('Menomonee Falls','WI'),('Menomonie','WI'),('Mentcle','PA'),('Mentmore','NM'),('Mentone','AL'),('Mentone','CA'),('Mentone','IN'),('Mentone','TX'),('Mentor','MN'),('Mentor','OH'),('Mequon','WI'),('Mer Rouge','LA'),('Meraux','LA'),('Merced','CA'),('Mercedes','TX'),('Mercedita','PR'),('Mercer','MO'),('Mercer','ND'),('Mercer','PA'),('Mercer','TN'),('Mercer','WI'),('Mercer Island','WA'),('Mercersburg','PA'),('Merchantville','NJ'),('Mercury','NV'),('Meredith','CO'),('Meredith','NH'),('Meredithville','VA'),('Meredosia','IL'),('Mereta','TX'),('Meridale','NY'),('Meriden','CT'),('Meriden','IA'),('Meriden','KS'),('Meriden','NH'),('Meriden','WY'),('Meridian','CA'),('Meridian','GA'),('Meridian','ID'),('Meridian','MS'),('Meridian','NY'),('Meridian','OK'),('Meridian','TX'),('Meridianville','AL'),('Merigold','MS'),('Merino','CO'),('Merion Station','PA'),('Merit','TX'),('Merkel','TX'),('Merlin','OR'),('Mermentau','LA'),('Merna','IL'),('Merna','NE'),('Merom','IN'),('Merrick','NY'),('Merrifield','MN'),('Merrifield','VA'),('Merrill','IA'),('Merrill','MI'),('Merrill','OR'),('Merrill','WI'),('Merrillan','WI'),('Merrillville','IN'),('Merrimac','MA'),('Merrimac','WI'),('Merrimack','NH'),('Merriman','NE'),('Merritt','MI'),('Merritt','NC'),('Merritt Island','FL'),('Merrittstown','PA'),('Merry Hill','NC'),('Merry Point','VA'),('Merryville','LA'),('Mershon','GA'),('Mertens','TX'),('Merton','WI'),('Mertzon','TX'),('Mertztown','PA'),('Mesa','AZ'),('Mesa','CO'),('Mesa','ID'),('Mesa','WA'),('Mesa Verde National Park','CO'),('Mescalero','NM'),('Mesena','GA'),('Meservey','IA'),('Meshoppen','PA'),('Mesick','MI'),('Mesilla','NM'),('Mesilla Park','NM'),('Mesopotamia','OH'),('Mesquite','NM'),('Mesquite','NV'),('Mesquite','TX'),('Meta','MO'),('Metairie','LA'),('Metaline','WA'),('Metaline Falls','WA'),('Metamora','IL'),('Metamora','IN'),('Metamora','MI'),('Metamora','OH'),('Metcalf','IL'),('Metcalfe','MS'),('Methow','WA'),('Methuen','MA'),('Metlakatla','AK'),('Metropolis','IL'),('Metter','GA'),('Metuchen','NJ'),('Metz','MO'),('Metz','WV'),('Mexia','AL'),('Mexia','TX'),('Mexican Hat','UT'),('Mexican Springs','NM'),('Mexico','IN'),('Mexico','ME'),('Mexico','MO'),('Mexico','NY'),('Mexico','PA'),('Mexico Beach','FL'),('Meyers Chuck','AK'),('Meyersdale','PA'),('Meyersville','TX'),('Mi Wuk Village','CA'),('Miami','AZ'),('Miami','FL'),('Miami','IN'),('Miami','MO'),('Miami','NM'),('Miami','OK'),('Miami','TX'),('Miami','WV'),('Miami Beach','FL'),('Miamisburg','OH'),('Miamitown','OH'),('Miamiville','OH'),('Mica','WA'),('Micanopy','FL'),('Micaville','NC'),('Michael','IL'),('Michie','TN'),('Michigamme','MI'),('Michigan','ND'),('Michigan Center','MI'),('Michigan City','IN'),('Michigan City','MS'),('Michigantown','IN'),('Mickleton','NJ'),('Mico','TX'),('Micro','NC'),('Mid Florida','FL'),('Middle Amana','IA'),('Middle Bass','OH'),('Middle Brook','MO'),('Middle Falls','NY'),('Middle Granville','NY'),('Middle Grove','NY'),('Middle Haddam','CT'),('Middle Island','NY'),('Middle Point','OH'),('Middle River','MD'),('Middle River','MN'),('Middle Village','NY'),('Middleboro','MA'),('Middlebourne','WV'),('Middlebranch','OH'),('Middlebrook','VA'),('Middleburg','FL'),('Middleburg','KY'),('Middleburg','NC'),('Middleburg','OH'),('Middleburg','PA'),('Middleburg','VA'),('Middleburgh','NY'),('Middlebury','CT'),('Middlebury','IN'),('Middlebury','VT'),('Middlebury Center','PA'),('Middlefield','CT'),('Middlefield','MA'),('Middlefield','OH'),('Middleport','NY'),('Middleport','OH'),('Middleport','PA'),('Middlesboro','KY'),('Middlesex','NC'),('Middlesex','NJ'),('Middlesex','NY'),('Middleton','ID'),('Middleton','MA'),('Middleton','MI'),('Middleton','TN'),('Middleton','WI'),('Middletown','CA'),('Middletown','CT'),('Middletown','DE'),('Middletown','IA'),('Middletown','IL'),('Middletown','IN'),('Middletown','MD'),('Middletown','MO'),('Middletown','NJ'),('Middletown','NY'),('Middletown','OH'),('Middletown','PA'),('Middletown','RI'),('Middletown','VA'),('Middletown Springs','VT'),('Middleville','MI'),('Middleville','NJ'),('Middleville','NY'),('Midfield','TX'),('Midkiff','TX'),('Midkiff','WV'),('Midland','AR'),('Midland','GA'),('Midland','IN'),('Midland','MD'),('Midland','MI'),('Midland','NC'),('Midland','OH'),('Midland','OR'),('Midland','PA'),('Midland','SD'),('Midland','TX'),('Midland','VA'),('Midland City','AL'),('Midland Park','NJ'),('Midlothian','IL'),('Midlothian','MD'),('Midlothian','TX'),('Midlothian','VA'),('Midnight','MS'),('Midpines','CA'),('Midvale','ID'),('Midvale','OH'),('Midvale','UT'),('Midville','GA'),('Midway','AL'),('Midway','AR'),('Midway','FL'),('Midway','GA'),('Midway','KY'),('Midway','PA'),('Midway','TN'),('Midway','TX'),('Midway','UT'),('Midway','WV'),('Midway City','CA'),('Midway Park','NC'),('Midwest','WY'),('Mifflin','PA'),('Mifflinburg','PA'),('Mifflintown','PA'),('Mifflinville','PA'),('Mikado','MI'),('Mikana','WI'),('Mikkalo','OR'),('Milaca','MN'),('Milam','TX'),('Milam','WV'),('Milan','GA'),('Milan','IL'),('Milan','IN'),('Milan','KS'),('Milan','MI'),('Milan','MN'),('Milan','MO'),('Milan','NH'),('Milan','NM'),('Milan','OH'),('Milan','PA'),('Milan','TN'),('Milano','TX'),('Milanville','PA'),('Milbank','SD'),('Milbridge','ME'),('Milburn','KY'),('Milburn','OK'),('Mildred','MT'),('Mildred','PA'),('Miles','IA'),('Miles','TX'),('Miles City','MT'),('Milesburg','PA'),('Milesville','SD'),('Miley','SC'),('Milfay','OK'),('Milford','CA'),('Milford','CT'),('Milford','DE'),('Milford','IA'),('Milford','IL'),('Milford','IN'),('Milford','KS'),('Milford','KY'),('Milford','MA'),('Milford','ME'),('Milford','MI'),('Milford','MO'),('Milford','NE'),('Milford','NH'),('Milford','NJ'),('Milford','NY'),('Milford','OH'),('Milford','PA'),('Milford','TX'),('Milford','UT'),('Milford','VA'),('Milford Center','OH'),('Milford Square','PA'),('Mililani','HI'),('Mill City','OR'),('Mill Creek','CA'),('Mill Creek','IN'),('Mill Creek','OK'),('Mill Creek','PA'),('Mill Creek','WA'),('Mill Creek','WV'),('Mill Hall','PA'),('Mill Neck','NY'),('Mill River','MA'),('Mill Run','PA'),('Mill Shoals','IL'),('Mill Spring','MO'),('Mill Spring','NC'),('Mill Valley','CA'),('Mill Village','PA'),('Milladore','WI'),('Millboro','VA'),('Millbrae','CA'),('Millbrook','AL'),('Millbrook','IL'),('Millbrook','NY'),('Millburn','NJ'),('Millbury','MA'),('Millbury','OH'),('Millcreek','IL'),('Milldale','CT'),('Milledgeville','GA'),('Milledgeville','IL'),('Milledgeville','OH'),('Milledgeville','TN'),('Millen','GA'),('Miller','MO'),('Miller','NE'),('Miller','SD'),('Miller City','IL'),('Miller City','OH'),('Miller Place','NY'),('Millers Creek','NC'),('Millers Falls','MA'),('Millers Tavern','VA'),('Millersburg','IA'),('Millersburg','IN'),('Millersburg','KY'),('Millersburg','MI'),('Millersburg','OH'),('Millersburg','PA'),('Millersport','OH'),('Millerstown','PA'),('Millersview','TX'),('Millersville','MD'),('Millersville','MO'),('Millersville','PA'),('Millerton','IA'),('Millerton','NY'),('Millerton','OK'),('Millerton','PA'),('Millerville','AL'),('Millfield','OH'),('Millheim','PA'),('Millhousen','IN'),('Millican','TX'),('Milligan','FL'),('Milligan','NE'),('Milligan College','TN'),('Milliken','CO'),('Millington','IL'),('Millington','MD'),('Millington','MI'),('Millington','NJ'),('Millington','TN'),('Millinocket','ME'),('Millis','MA'),('Millmont','PA'),('Millport','AL'),('Millport','NY'),('Millrift','PA'),('Millry','AL'),('Mills','NE'),('Mills','NM'),('Mills','PA'),('Mills','WY'),('Mills River','NC'),('Millsap','TX'),('Millsboro','DE'),('Millsboro','PA'),('Millstadt','IL'),('Millston','WI'),('Millstone','KY'),('Millstone','WV'),('Millstone Township','NJ'),('Milltown','IN'),('Milltown','MT'),('Milltown','NJ'),('Milltown','WI'),('Millville','CA'),('Millville','DE'),('Millville','MA'),('Millville','MN'),('Millville','NJ'),('Millville','PA'),('Millville','UT'),('Millville','WV'),('Millwood','GA'),('Millwood','KY'),('Millwood','NY'),('Millwood','VA'),('Millwood','WV'),('Milmay','NJ'),('Milmine','IL'),('Milner','GA'),('Milnesand','NM'),('Milnesville','PA'),('Milnor','ND'),('Milo','IA'),('Milo','ME'),('Milo','MO'),('Milpitas','CA'),('Milroy','IN'),('Milroy','MN'),('Milroy','PA'),('Milton','DE'),('Milton','FL'),('Milton','IA'),('Milton','IL'),('Milton','IN'),('Milton','KS'),('Milton','KY'),('Milton','LA'),('Milton','MA'),('Milton','NC'),('Milton','ND'),('Milton','NH'),('Milton','NY'),('Milton','PA'),('Milton','TN'),('Milton','VT'),('Milton','WA'),('Milton','WI'),('Milton','WV'),('Milton Center','OH'),('Milton Freewater','OR'),('Milton Mills','NH'),('Milton Village','MA'),('Miltona','MN'),('Miltonvale','KS'),('Milwaukee','NC'),('Milwaukee','WI'),('Mimbres','NM'),('Mims','FL'),('Mina','NV'),('Minatare','NE'),('Minburn','IA'),('Minco','OK'),('Minden','IA'),('Minden','LA'),('Minden','NE'),('Minden','NV'),('Minden','TX'),('Minden','WV'),('Minden City','MI'),('Mindenmines','MO'),('Mindoro','WI'),('Mine Hill','NJ'),('Mineola','IA'),('Mineola','NY'),('Mineola','TX'),('Mineral','CA'),('Mineral','IL'),('Mineral','TX'),('Mineral','VA'),('Mineral','WA'),('Mineral Bluff','GA'),('Mineral City','OH'),('Mineral Point','MO'),('Mineral Point','PA'),('Mineral Point','WI'),('Mineral Ridge','OH'),('Mineral Springs','AR'),('Mineral Springs','NC'),('Mineral Springs','PA'),('Mineral Wells','TX'),('Mineral Wells','WV'),('Minersville','PA'),('Minersville','UT'),('Minerva','KY'),('Minerva','NY'),('Minerva','OH'),('Minetto','NY'),('Mineville','NY'),('Minford','OH'),('Mingo','IA'),('Mingo','OH'),('Mingo Junction','OH'),('Mingoville','PA'),('Mingus','TX'),('Minidoka','ID'),('Minier','IL'),('Minisink Hills','PA'),('Minneapolis','KS'),('Minneapolis','MN'),('Minneapolis','NC'),('Minneola','FL'),('Minneola','KS'),('Minneota','MN'),('Minnesota City','MN'),('Minnesota Lake','MN'),('Minnetonka','MN'),('Minnetonka Beach','MN'),('Minnewaukan','ND'),('Minnie','KY'),('Minoa','NY'),('Minocqua','WI'),('Minong','WI'),('Minonk','IL'),('Minooka','IL'),('Minor Hill','TN'),('Minot','MA'),('Minot','ME'),('Minot','ND'),('Minot Afb','ND'),('Minotola','NJ'),('Minster','OH'),('Mint Spring','VA'),('Minter','AL'),('Minter City','MS'),('Minto','AK'),('Minto','ND'),('Minturn','AR'),('Minturn','CO'),('Minturn','SC'),('Mio','MI'),('Mira Loma','CA'),('Miracle','KY'),('Miramar Beach','FL'),('Miramonte','CA'),('Miranda','CA'),('Mirando City','TX'),('Mirror Lake','NH'),('Misenheimer','NC'),('Mishawaka','IN'),('Mishicot','WI'),('Mission','KS'),('Mission','SD'),('Mission','TX'),('Mission Hill','SD'),('Mission Hills','CA'),('Mission Viejo','CA'),('Mississippi State','MS'),('Missoula','MT'),('Missouri City','MO'),('Missouri City','TX'),('Missouri Valley','IA'),('Mistletoe','KY'),('Mitchell','GA'),('Mitchell','IN'),('Mitchell','NE'),('Mitchell','OR'),('Mitchell','SD'),('Mitchells','VA'),('Mitchellsburg','KY'),('Mitchellville','IA'),('Mitchellville','TN'),('Mittie','LA'),('Mize','KY'),('Mize','MS'),('Mizpah','MN'),('Mizpah','NJ'),('Moab','UT'),('Moapa','NV'),('Moatsville','WV'),('Mobeetie','TX'),('Moberly','MO'),('Mobile','AL'),('Mobridge','SD'),('Moca','PR'),('Moccasin','CA'),('Moccasin','MT'),('Mocksville','NC'),('Moclips','WA'),('Modale','IA'),('Mode','IL'),('Model','CO'),('Model City','NY'),('Modena','NY'),('Modena','PA'),('Modena','UT'),('Modest Town','VA'),('Modesto','CA'),('Modesto','IL'),('Modoc','IL'),('Modoc','IN'),('Modoc','SC'),('Moffat','CO'),('Moffett','OK'),('Moffit','ND'),('Mogadore','OH'),('Mohall','ND'),('Mohave Valley','AZ'),('Mohawk','MI'),('Mohawk','NY'),('Mohawk','TN'),('Mohawk','WV'),('Mohegan Lake','NY'),('Mohler','WA'),('Mohnton','PA'),('Mohrsville','PA'),('Moira','NY'),('Mojave','CA'),('Mokane','MO'),('Mokelumne Hill','CA'),('Mokena','IL'),('Molalla','OR'),('Molena','GA'),('Molina','CO'),('Moline','IL'),('Moline','KS'),('Moline','MI'),('Molino','FL'),('Mollusk','VA'),('Molt','MT'),('Momence','IL'),('Mona','UT'),('Monaca','PA'),('Monahans','TX'),('Monarch','CO'),('Monarch','MT'),('Moncks Corner','SC'),('Monclova','OH'),('Moncure','NC'),('Mondamin','IA'),('Mondovi','WI'),('Monee','IL'),('Monessen','PA'),('Moneta','VA'),('Monett','MO'),('Monetta','SC'),('Monette','AR'),('Money','MS'),('Mongaup Valley','NY'),('Mongo','IN'),('Monhegan','ME'),('Monitor','WA'),('Monkton','MD'),('Monkton','VT'),('Monmouth','IA'),('Monmouth','IL'),('Monmouth','ME'),('Monmouth','OR'),('Monmouth Beach','NJ'),('Monmouth Junction','NJ'),('Mono Hot Springs','CA'),('Monocacy Station','PA'),('Monon','IN'),('Monona','IA'),('Monongahela','PA'),('Monponsett','MA'),('Monroe','AR'),('Monroe','CT'),('Monroe','GA'),('Monroe','IA'),('Monroe','IN'),('Monroe','LA'),('Monroe','ME'),('Monroe','MI'),('Monroe','NC'),('Monroe','NE'),('Monroe','NH'),('Monroe','NY'),('Monroe','OH'),('Monroe','OK'),('Monroe','OR'),('Monroe','SD'),('Monroe','TN'),('Monroe','UT'),('Monroe','VA'),('Monroe','WA'),('Monroe','WI'),('Monroe Bridge','MA'),('Monroe Center','IL'),('Monroe City','IN'),('Monroe City','MO'),('Monroe Township','NJ'),('Monroeton','PA'),('Monroeville','AL'),('Monroeville','IN'),('Monroeville','NJ'),('Monroeville','OH'),('Monroeville','PA'),('Monrovia','CA'),('Monrovia','IN'),('Monrovia','MD'),('Monsey','NY'),('Monson','MA'),('Monson','ME'),('Mont Alto','PA'),('Mont Belvieu','TX'),('Mont Clare','PA'),('Mont Vernon','NH'),('Montague','CA'),('Montague','MA'),('Montague','MI'),('Montague','NJ'),('Montague','TX'),('Montalba','TX'),('Montana Mines','WV'),('Montandon','PA'),('Montara','CA'),('Montauk','NY'),('Montcalm','WV'),('Montchanin','DE'),('Montclair','CA'),('Montclair','NJ'),('Monte Rio','CA'),('Monte Vista','CO'),('Monteagle','TN'),('Montebello','CA'),('Montebello','VA'),('Montegut','LA'),('Montello','NV'),('Montello','WI'),('Monterey','CA'),('Monterey','IN'),('Monterey','LA'),('Monterey','MA'),('Monterey','TN'),('Monterey','VA'),('Monterey Park','CA'),('Monterville','WV'),('Montesano','WA'),('Montevallo','AL'),('Montevideo','MN'),('Monteview','ID'),('Montezuma','GA'),('Montezuma','IA'),('Montezuma','IN'),('Montezuma','KS'),('Montezuma','NC'),('Montezuma','NM'),('Montezuma','NY'),('Montezuma','OH'),('Montezuma Creek','UT'),('Montfort','WI'),('Montgomery','AL'),('Montgomery','IL'),('Montgomery','IN'),('Montgomery','LA'),('Montgomery','MI'),('Montgomery','MN'),('Montgomery','NY'),('Montgomery','PA'),('Montgomery','TX'),('Montgomery','VT'),('Montgomery','WV'),('Montgomery Center','VT'),('Montgomery City','MO'),('Montgomery Creek','CA'),('Montgomery Village','MD'),('Montgomeryville','PA'),('Monticello','AR'),('Monticello','FL'),('Monticello','GA'),('Monticello','IA'),('Monticello','IL'),('Monticello','IN'),('Monticello','KY'),('Monticello','ME'),('Monticello','MN'),('Monticello','MO'),('Monticello','MS'),('Monticello','NM'),('Monticello','NY'),('Monticello','UT'),('Monticello','WI'),('Montier','MO'),('Montmorenci','IN'),('Montmorenci','SC'),('Montour','IA'),('Montour Falls','NY'),('Montoursville','PA'),('Montpelier','IA'),('Montpelier','ID'),('Montpelier','IN'),('Montpelier','MS'),('Montpelier','ND'),('Montpelier','OH'),('Montpelier','VA'),('Montpelier','VT'),('Montpelier Station','VA'),('Montreal','MO'),('Montreal','WI'),('Montreat','NC'),('Montrose','AL'),('Montrose','AR'),('Montrose','CA'),('Montrose','CO'),('Montrose','GA'),('Montrose','IA'),('Montrose','IL'),('Montrose','MI'),('Montrose','MN'),('Montrose','MO'),('Montrose','NY'),('Montrose','PA'),('Montrose','SD'),('Montrose','WV'),('Montross','VA'),('Montvale','NJ'),('Montvale','VA'),('Montverde','FL'),('Montville','CT'),('Montville','NJ'),('Montville','OH'),('Monument','CO'),('Monument','KS'),('Monument','NM'),('Monument','OR'),('Monument Beach','MA'),('Monument Valley','UT'),('Moodus','CT'),('Moody','AL'),('Moody','ME'),('Moody','MO'),('Moody','TX'),('Moody A F B','GA'),('Moodys','OK'),('Mooers','NY'),('Mooers Forks','NY'),('Moon','VA'),('Moonachie','NJ'),('Moorcroft','WY'),('Moore','ID'),('Moore','MT'),('Moore','SC'),('Moore','TX'),('Moore Haven','FL'),('Moorefield','KY'),('Moorefield','NE'),('Moorefield','WV'),('Mooreland','IN'),('Mooreland','OK'),('Moores Hill','IN'),('Mooresboro','NC'),('Mooresburg','TN'),('Moorestown','NJ'),('Mooresville','AL'),('Mooresville','IN'),('Mooresville','MO'),('Mooresville','NC'),('Mooreton','ND'),('Mooreville','MS'),('Moorhead','IA'),('Moorhead','MN'),('Moorhead','MS'),('Mooringsport','LA'),('Moorland','IA'),('Moorpark','CA'),('Moose','WY'),('Moose Lake','MN'),('Moose Pass','AK'),('Mooseheart','IL'),('Moosic','PA'),('Moosup','CT'),('Mora','LA'),('Mora','MN'),('Mora','MO'),('Mora','NM'),('Moraga','CA'),('Moran','KS'),('Moran','MI'),('Moran','TX'),('Moran','WY'),('Morann','PA'),('Morattico','VA'),('Moravia','IA'),('Moravia','NY'),('Moravian Falls','NC'),('Moreauville','LA'),('Morehead','KY'),('Morehead City','NC'),('Morehouse','MO'),('Moreland','GA'),('Moreland','ID'),('Morenci','AZ'),('Morenci','MI'),('Moreno Valley','CA'),('Moretown','VT'),('Morgan','GA'),('Morgan','MN'),('Morgan','PA'),('Morgan','TX'),('Morgan','UT'),('Morgan','VT'),('Morgan City','LA'),('Morgan City','MS'),('Morgan Hill','CA'),('Morgan Mill','TX'),('Morganfield','KY'),('Morganton','GA'),('Morganton','NC'),('Morgantown','IN'),('Morgantown','KY'),('Morgantown','PA'),('Morgantown','WV'),('Morganville','KS'),('Morganville','NJ'),('Morganza','LA'),('Morganza','MD'),('Moriah','NY'),('Moriah Center','NY'),('Moriarty','NM'),('Moriches','NY'),('Morland','KS'),('Morley','IA'),('Morley','MI'),('Morley','MO'),('Mormon Lake','AZ'),('Morning Sun','IA'),('Morning View','KY'),('Moro','AR'),('Moro','IL'),('Moro','OR'),('Morocco','IN'),('Morongo Valley','CA'),('Moroni','UT'),('Morovis','PR'),('Morral','OH'),('Morrice','MI'),('Morrill','KS'),('Morrill','ME'),('Morrill','NE'),('Morrilton','AR'),('Morris','AL'),('Morris','CT'),('Morris','GA'),('Morris','IL'),('Morris','IN'),('Morris','MN'),('Morris','NY'),('Morris','OK'),('Morris','PA'),('Morris Chapel','TN'),('Morris Run','PA'),('Morrisdale','PA'),('Morrison','CO'),('Morrison','IA'),('Morrison','IL'),('Morrison','MO'),('Morrison','OK'),('Morrison','TN'),('Morrisonville','IL'),('Morrisonville','NY'),('Morrisonville','WI'),('Morriston','FL'),('Morristown','AZ'),('Morristown','IN'),('Morristown','MN'),('Morristown','NJ'),('Morristown','NY'),('Morristown','OH'),('Morristown','SD'),('Morristown','TN'),('Morrisville','MO'),('Morrisville','NC'),('Morrisville','NY'),('Morrisville','PA'),('Morrisville','VT'),('Morro Bay','CA'),('Morrow','AR'),('Morrow','GA'),('Morrow','LA'),('Morrow','OH'),('Morrowville','KS'),('Morse','LA'),('Morse','TX'),('Morse Bluff','NE'),('Morse Mill','MO'),('Morton','IL'),('Morton','MN'),('Morton','MS'),('Morton','NY'),('Morton','PA'),('Morton','TX'),('Morton','WA'),('Morton Grove','IL'),('Mortons Gap','KY'),('Morven','GA'),('Morven','NC'),('Morvin','AL'),('Mosby','MO'),('Mosby','MT'),('Mosca','CO'),('Moscow','AR'),('Moscow','IA'),('Moscow','ID'),('Moscow','KS'),('Moscow','MI'),('Moscow','OH'),('Moscow','PA'),('Moscow','TN'),('Moscow','TX'),('Moscow','VT'),('Moscow Mills','MO'),('Moseley','VA'),('Moselle','MS'),('Moses Lake','WA'),('Moshannon','PA'),('Mosheim','TN'),('Mosherville','MI'),('Mosier','OR'),('Mosinee','WI'),('Mosquero','NM'),('Moss','MS'),('Moss','TN'),('Moss Beach','CA'),('Moss Landing','CA'),('Moss Point','MS'),('Mossville','IL'),('Mossy Head','FL'),('Mossyrock','WA'),('Motley','MN'),('Mott','ND'),('Mottville','NY'),('Moulton','AL'),('Moulton','IA'),('Moulton','TX'),('Moultonborough','NH'),('Moultrie','GA'),('Mound','MN'),('Mound','TX'),('Mound Bayou','MS'),('Mound City','IL'),('Mound City','KS'),('Mound City','MO'),('Mound City','SD'),('Mound Valley','KS'),('Moundridge','KS'),('Mounds','IL'),('Mounds','OK'),('Moundsville','WV'),('Moundville','AL'),('Moundville','MO'),('Mount Aetna','PA'),('Mount Airy','GA'),('Mount Airy','LA'),('Mount Airy','MD'),('Mount Airy','NC'),('Mount Alto','WV'),('Mount Angel','OR'),('Mount Arlington','NJ'),('Mount Auburn','IA'),('Mount Auburn','IL'),('Mount Aukum','CA'),('Mount Ayr','IA'),('Mount Ayr','IN'),('Mount Berry','GA'),('Mount Bethel','PA'),('Mount Blanchard','OH'),('Mount Braddock','PA'),('Mount Calm','TX'),('Mount Calvary','WI'),('Mount Carbon','WV'),('Mount Carmel','IL'),('Mount Carmel','PA'),('Mount Carmel','SC'),('Mount Carmel','TN'),('Mount Carmel','UT'),('Mount Carroll','IL'),('Mount Clare','WV'),('Mount Clemens','MI'),('Mount Cory','OH'),('Mount Crawford','VA'),('Mount Croghan','SC'),('Mount Desert','ME'),('Mount Dora','FL'),('Mount Eaton','OH'),('Mount Eden','KY'),('Mount Enterprise','TX'),('Mount Ephraim','NJ'),('Mount Erie','IL'),('Mount Freedom','NJ'),('Mount Gay','WV'),('Mount Gilead','NC'),('Mount Gilead','OH'),('Mount Gretna','PA'),('Mount Hamilton','CA'),('Mount Hermon','CA'),('Mount Hermon','KY'),('Mount Hermon','LA'),('Mount Holly','AR'),('Mount Holly','NC'),('Mount Holly','NJ'),('Mount Holly','VA'),('Mount Holly','VT'),('Mount Holly Springs','PA'),('Mount Hood Parkdale','OR'),('Mount Hope','AL'),('Mount Hope','KS'),('Mount Hope','OH'),('Mount Hope','WI'),('Mount Hope','WV'),('Mount Horeb','WI'),('Mount Ida','AR'),('Mount Jackson','VA'),('Mount Jewett','PA'),('Mount Joy','PA'),('Mount Judea','AR'),('Mount Juliet','TN'),('Mount Kisco','NY'),('Mount Laguna','CA'),('Mount Laurel','NJ'),('Mount Lemmon','AZ'),('Mount Liberty','OH'),('Mount Lookout','WV'),('Mount Marion','NY'),('Mount Meigs','AL'),('Mount Morris','IL'),('Mount Morris','MI'),('Mount Morris','NY'),('Mount Morris','PA'),('Mount Mourne','NC'),('Mount Nebo','WV'),('Mount Olive','AL'),('Mount Olive','IL'),('Mount Olive','MS'),('Mount Olive','NC'),('Mount Olive','WV'),('Mount Olivet','KY'),('Mount Orab','OH'),('Mount Perry','OH'),('Mount Pleasant','AR'),('Mount Pleasant','IA'),('Mount Pleasant','MI'),('Mount Pleasant','MS'),('Mount Pleasant','NC'),('Mount Pleasant','OH'),('Mount Pleasant','PA'),('Mount Pleasant','SC'),('Mount Pleasant','TN'),('Mount Pleasant','TX'),('Mount Pleasant','UT'),('Mount Pleasant Mills','PA'),('Mount Pocono','PA'),('Mount Prospect','IL'),('Mount Pulaski','IL'),('Mount Rainier','MD'),('Mount Royal','NJ'),('Mount Saint Francis','IN'),('Mount Saint Joseph','OH'),('Mount Savage','MD'),('Mount Shasta','CA'),('Mount Sherman','KY'),('Mount Sidney','VA'),('Mount Sinai','NY'),('Mount Solon','VA'),('Mount Sterling','IA'),('Mount Sterling','IL'),('Mount Sterling','KY'),('Mount Sterling','MO'),('Mount Sterling','OH'),('Mount Sterling','WI'),('Mount Storm','WV'),('Mount Summit','IN'),('Mount Tabor','NJ'),('Mount Tremper','NY'),('Mount Ulla','NC'),('Mount Union','IA'),('Mount Union','PA'),('Mount Upton','NY'),('Mount Vernon','AL'),('Mount Vernon','AR'),('Mount Vernon','GA'),('Mount Vernon','IA'),('Mount Vernon','IL'),('Mount Vernon','IN'),('Mount Vernon','KY'),('Mount Vernon','ME'),('Mount Vernon','MO'),('Mount Vernon','NY'),('Mount Vernon','OH'),('Mount Vernon','OR'),('Mount Vernon','SD'),('Mount Vernon','TX'),('Mount Vernon','VA'),('Mount Vernon','WA'),('Mount Victoria','MD'),('Mount Victory','OH'),('Mount Vision','NY'),('Mount Washington','KY'),('Mount Washington','NH'),('Mount Wilson','CA'),('Mount Wolf','PA'),('Mount Zion','GA'),('Mount Zion','WV'),('Mountain','ND'),('Mountain','WI'),('Mountain Center','CA'),('Mountain City','GA'),('Mountain City','NV'),('Mountain City','TN'),('Mountain Dale','NY'),('Mountain Grove','MO'),('Mountain Home','AR'),('Mountain Home','ID'),('Mountain Home','NC'),('Mountain Home','TN'),('Mountain Home','TX'),('Mountain Home','UT'),('Mountain Home A F B','ID'),('Mountain Iron','MN'),('Mountain Lake','MN'),('Mountain Lakes','NJ'),('Mountain Park','OK'),('Mountain Pass','CA'),('Mountain Pine','AR'),('Mountain Ranch','CA'),('Mountain Rest','SC'),('Mountain Top','PA'),('Mountain View','AR'),('Mountain View','CA'),('Mountain View','HI'),('Mountain View','MO'),('Mountain View','OK'),('Mountain View','WY'),('Mountain Village','AK'),('Mountainair','NM'),('Mountainburg','AR'),('Mountainhome','PA'),('Mountainside','NJ'),('Mountainville','NY'),('Mountlake Terrace','WA'),('Mountville','PA'),('Mountville','SC'),('Mousie','KY'),('Mouth Of Wilson','VA'),('Mouthcard','KY'),('Moville','IA'),('Moweaqua','IL'),('Mowrystown','OH'),('Moxahala','OH'),('Moxee','WA'),('Moyers','OK'),('Moyie Springs','ID'),('Moyock','NC'),('Mozelle','KY'),('Mozier','IL'),('Mt Baldy','CA'),('Mt Zion','IL'),('Mud Butte','SD'),('Muddy','IL'),('Muenster','TX'),('Muir','MI'),('Muir','PA'),('Mukilteo','WA'),('Mukwonago','WI'),('Mulberry','AR'),('Mulberry','FL'),('Mulberry','IN'),('Mulberry','KS'),('Mulberry','TN'),('Mulberry Grove','IL'),('Muldoon','TX'),('Muldraugh','KY'),('Muldrow','OK'),('Mule Creek','NM'),('Muleshoe','TX'),('Mulga','AL'),('Mulhall','OK'),('Mulino','OR'),('Mulkeytown','IL'),('Mullan','ID'),('Mullen','NE'),('Mullens','WV'),('Mullett Lake','MI'),('Mullica Hill','NJ'),('Mulliken','MI'),('Mullin','TX'),('Mullins','SC'),('Mullinville','KS'),('Mulvane','KS'),('Mumford','NY'),('Mumford','TX'),('Muncie','IL'),('Muncie','IN'),('Muncy','PA'),('Muncy Valley','PA'),('Munday','TX'),('Munday','WV'),('Mundelein','IL'),('Munden','KS'),('Munds Park','AZ'),('Munford','AL'),('Munford','TN'),('Munfordville','KY'),('Munger','MI'),('Munich','ND'),('Munising','MI'),('Munith','MI'),('Munnsville','NY'),('Munroe Falls','OH'),('Munson','PA'),('Munster','IN'),('Murchison','TX'),('Murdo','SD'),('Murdock','FL'),('Murdock','IL'),('Murdock','KS'),('Murdock','MN'),('Murdock','NE'),('Murfreesboro','AR'),('Murfreesboro','NC'),('Murfreesboro','TN'),('Murphy','ID'),('Murphy','NC'),('Murphy','OR'),('Murphys','CA'),('Murphysboro','IL'),('Murray','IA'),('Murray','ID'),('Murray','KY'),('Murray','NE'),('Murray City','OH'),('Murrayville','GA'),('Murrayville','IL'),('Murrells Inlet','SC'),('Murrieta','CA'),('Murrysville','PA'),('Murtaugh','ID'),('Muscadine','AL'),('Muscatine','IA'),('Muscle Shoals','AL'),('Muscoda','WI'),('Muscotah','KS'),('Muse','OK'),('Muse','PA'),('Musella','GA'),('Muses Mills','KY'),('Muskego','WI'),('Muskegon','MI'),('Muskogee','OK'),('Musselshell','MT'),('Mustang','OK'),('Mustoe','VA'),('Mutual','OK'),('Myakka City','FL'),('Myers Flat','CA'),('Myerstown','PA'),('Myersville','MD'),('Mylo','ND'),('Myra','KY'),('Myra','TX'),('Myra','WV'),('Myrtle','MO'),('Myrtle','MS'),('Myrtle Beach','SC'),('Myrtle Creek','OR'),('Myrtle Point','OR'),('Myrtlewood','AL'),('Mystic','CT'),('Mystic','GA'),('Mystic','IA'),('Myton','UT'),('Naalehu','HI'),('Nabb','IN'),('Naches','WA'),('Nachusa','IL'),('Naco','AZ'),('Nacogdoches','TX'),('Nada','TX'),('Nadeau','MI'),('Nageezi','NM'),('Nags Head','NC'),('Naguabo','PR'),('Nahant','MA'),('Nahcotta','WA'),('Nahma','MI'),('Nahunta','GA'),('Nakina','NC'),('Naknek','AK'),('Nalcrest','FL'),('Nallen','WV'),('Nampa','ID'),('Nanafalia','AL'),('Nancy','KY'),('Nanjemoy','MD'),('Nankin','OH'),('Nanticoke','MD'),('Nanticoke','PA'),('Nantucket','MA'),('Nanty Glo','PA'),('Nanuet','NY'),('Naoma','WV'),('Napa','CA'),('Napakiak','AK'),('Napanoch','NY'),('Napavine','WA'),('Naper','NE'),('Naperville','IL'),('Napier','WV'),('Naples','FL'),('Naples','ID'),('Naples','ME'),('Naples','NC'),('Naples','NY'),('Naples','TX'),('Napoleon','IN'),('Napoleon','MI'),('Napoleon','MO'),('Napoleon','ND'),('Napoleon','OH'),('Napoleonville','LA'),('Naponee','NE'),('Nappanee','IN'),('Nara Visa','NM'),('Naranjito','PR'),('Narberth','PA'),('Nardin','OK'),('Narka','KS'),('Narragansett','RI'),('Narrows','VA'),('Narrowsburg','NY'),('Naruna','VA'),('Narvon','PA'),('Naselle','WA'),('Nash','OK'),('Nash','TX'),('Nashoba','OK'),('Nashotah','WI'),('Nashport','OH'),('Nashua','IA'),('Nashua','MN'),('Nashua','MT'),('Nashua','NH'),('Nashville','AR'),('Nashville','GA'),('Nashville','IL'),('Nashville','IN'),('Nashville','KS'),('Nashville','MI'),('Nashville','NC'),('Nashville','OH'),('Nashville','TN'),('Nashwauk','MN'),('Nason','IL'),('Nassau','DE'),('Nassau','NY'),('Nassawadox','VA'),('Natalbany','LA'),('Natalia','TX'),('Natchez','LA'),('Natchez','MS'),('Natchitoches','LA'),('Nathalie','VA'),('Nathrop','CO'),('Natick','MA'),('National City','CA'),('National City','MI'),('National Mine','MI'),('National Park','NJ'),('National Stock Yards','IL'),('Natoma','KS'),('Natrona','WY'),('Natrona Heights','PA'),('Natural Bridge','AL'),('Natural Bridge','NY'),('Natural Bridge','VA'),('Natural Bridge Station','VA'),('Natural Dam','AR'),('Naturita','CO'),('Naubinway','MI'),('Naugatuck','CT'),('Naugatuck','WV'),('Nauvoo','AL'),('Nauvoo','IL'),('Navajo','NM'),('Navajo Dam','NM'),('Naval Air Station/ Jrb','TX'),('Naval Anacost Annex','DC'),('Navarre','FL'),('Navarre','MN'),('Navarre','OH'),('Navarro','CA'),('Navasota','TX'),('Navesink','NJ'),('Naylor','GA'),('Naylor','MO'),('Naytahwaush','MN'),('Nazareth','KY'),('Nazareth','MI'),('Nazareth','PA'),('Nazareth','TX'),('Nazlini','AZ'),('Neah Bay','WA'),('Neal','KS'),('Neapolis','OH'),('Neavitt','MD'),('Nebo','IL'),('Nebo','KY'),('Nebo','NC'),('Nebo','WV'),('Nebraska City','NE'),('Necedah','WI'),('Neche','ND'),('Neches','TX'),('Neck City','MO'),('Nederland','CO'),('Nederland','TX'),('Nedrow','NY'),('Needham','AL'),('Needham','IN'),('Needham','MA'),('Needham Heights','MA'),('Needles','CA'),('Needmore','PA'),('Needville','TX'),('Neely','MS'),('Neelyton','PA'),('Neelyville','MO'),('Neenah','WI'),('Neeses','SC'),('Neffs','OH'),('Neffs','PA'),('Negaunee','MI'),('Negley','OH'),('Negreet','LA'),('Nehalem','OR'),('Nehawka','NE'),('Neihart','MT'),('Neillsville','WI'),('Neilton','WA'),('Nekoma','KS'),('Nekoma','ND'),('Nekoosa','WI'),('Neligh','NE'),('Nellis','WV'),('Nellis Afb','NV'),('Nelliston','NY'),('Nellysford','VA'),('Nelson','CA'),('Nelson','GA'),('Nelson','MN'),('Nelson','MO'),('Nelson','NE'),('Nelson','NH'),('Nelson','PA'),('Nelson','VA'),('Nelson','WI'),('Nelsonia','VA'),('Nelsonville','OH'),('Nelsonville','WI'),('Nemacolin','PA'),('Nemaha','IA'),('Nemaha','NE'),('Nemo','SD'),('Nemo','TX'),('Nemours','WV'),('Nenana','AK'),('Nenzel','NE'),('Neodesha','KS'),('Neoga','IL'),('Neola','IA'),('Neola','UT'),('Neola','WV'),('Neon','KY'),('Neopit','WI'),('Neosho','MO'),('Neosho','WI'),('Neosho Falls','KS'),('Neosho Rapids','KS'),('Neotsu','OR'),('Nephi','UT'),('Neponset','IL'),('Neptune','NJ'),('Neptune Beach','FL'),('Nerinx','KY'),('Nerstrand','MN'),('Nesbit','MS'),('Nesconset','NY'),('Nescopeck','PA'),('Neshanic Station','NJ'),('Neshkoro','WI'),('Neskowin','OR'),('Nesmith','SC'),('Nespelem','WA'),('Nesquehoning','PA'),('Ness City','KS'),('Netarts','OR'),('Netawaka','KS'),('Netcong','NJ'),('Nett Lake','MN'),('Nettie','WV'),('Nettleton','MS'),('Nevada','IA'),('Nevada','MO'),('Nevada','OH'),('Nevada','TX'),('Nevada City','CA'),('Neversink','NY'),('Neville','OH'),('Nevis','MN'),('Nevisdale','KY'),('New Albany','IN'),('New Albany','KS'),('New Albany','MS'),('New Albany','OH'),('New Albany','PA'),('New Albin','IA'),('New Alexandria','PA'),('New Almaden','CA'),('New Athens','IL'),('New Athens','OH'),('New Auburn','MN'),('New Auburn','WI'),('New Augusta','MS'),('New Baden','IL'),('New Baden','TX'),('New Baltimore','MI'),('New Baltimore','NY'),('New Baltimore','PA'),('New Bavaria','OH'),('New Bedford','IL'),('New Bedford','MA'),('New Bedford','PA'),('New Berlin','IL'),('New Berlin','NY'),('New Berlin','PA'),('New Berlin','WI'),('New Berlinville','PA'),('New Bern','NC'),('New Bethlehem','PA'),('New Blaine','AR'),('New Bloomfield','MO'),('New Bloomfield','PA'),('New Bloomington','OH'),('New Boston','IL'),('New Boston','MI'),('New Boston','MO'),('New Boston','NH'),('New Boston','TX'),('New Braintree','MA'),('New Braunfels','TX'),('New Bremen','OH'),('New Brighton','PA'),('New Britain','CT'),('New Brockton','AL'),('New Brunswick','NJ'),('New Buffalo','MI'),('New Buffalo','PA'),('New Burnside','IL'),('New Cambria','KS'),('New Cambria','MO'),('New Canaan','CT'),('New Caney','TX'),('New Canton','IL'),('New Canton','VA'),('New Carlisle','IN'),('New Carlisle','OH'),('New Castle','AL'),('New Castle','CO'),('New Castle','DE'),('New Castle','IN'),('New Castle','KY'),('New Castle','NH'),('New Castle','PA'),('New Castle','VA'),('New Century','KS'),('New Church','VA'),('New City','NY'),('New Columbia','PA'),('New Concord','KY'),('New Concord','OH'),('New Creek','WV'),('New Cumberland','PA'),('New Cumberland','WV'),('New Cuyama','CA'),('New Deal','TX'),('New Derry','PA'),('New Douglas','IL'),('New Durham','NH'),('New Eagle','PA'),('New Edinburg','AR'),('New Effington','SD'),('New Egypt','NJ'),('New Ellenton','SC'),('New England','ND'),('New Enterprise','PA'),('New Era','MI'),('New Fairfield','CT'),('New Florence','MO'),('New Florence','PA'),('New Franken','WI'),('New Franklin','MO'),('New Freedom','PA'),('New Freeport','PA'),('New Galilee','PA'),('New Geneva','PA'),('New Germantown','PA'),('New Germany','MN'),('New Glarus','WI'),('New Gloucester','ME'),('New Goshen','IN'),('New Gretna','NJ'),('New Hampshire','OH'),('New Hampton','IA'),('New Hampton','MO'),('New Hampton','NH'),('New Hampton','NY'),('New Harbor','ME'),('New Harmony','IN'),('New Harmony','UT'),('New Hartford','CT'),('New Hartford','IA'),('New Hartford','NY'),('New Haven','CT'),('New Haven','IL'),('New Haven','IN'),('New Haven','KY'),('New Haven','MI'),('New Haven','MO'),('New Haven','NY'),('New Haven','OH'),('New Haven','VT'),('New Haven','WV'),('New Hill','NC'),('New Holland','IL'),('New Holland','OH'),('New Holland','PA'),('New Holland','SD'),('New Holstein','WI'),('New Home','TX'),('New Hope','AL'),('New Hope','KY'),('New Hope','PA'),('New Hope','VA'),('New Hudson','MI'),('New Hyde Park','NY'),('New Iberia','LA'),('New Ipswich','NH'),('New Johnsonville','TN'),('New Kensington','PA'),('New Kent','VA'),('New Kingston','NY'),('New Kingstown','PA'),('New Knoxville','OH'),('New Laguna','NM'),('New Lebanon','IN'),('New Lebanon','NY'),('New Lebanon','OH'),('New Leipzig','ND'),('New Lenox','IL'),('New Lexington','OH'),('New Liberty','IA'),('New Liberty','KY'),('New Limerick','ME'),('New Lisbon','IN'),('New Lisbon','NJ'),('New Lisbon','NY'),('New Lisbon','WI'),('New London','CT'),('New London','IA'),('New London','MN'),('New London','MO'),('New London','NC'),('New London','NH'),('New London','OH'),('New London','PA'),('New London','TX'),('New London','WI'),('New Lothrop','MI'),('New Madison','OH'),('New Madrid','MO'),('New Manchester','WV'),('New Market','AL'),('New Market','IA'),('New Market','IN'),('New Market','MD'),('New Market','MN'),('New Market','TN'),('New Market','VA'),('New Marshfield','OH'),('New Martinsville','WV'),('New Matamoras','OH'),('New Meadows','ID'),('New Melle','MO'),('New Memphis','IL'),('New Middletown','IN'),('New Middletown','OH'),('New Midway','MD'),('New Milford','CT'),('New Milford','NJ'),('New Milford','NY'),('New Milford','PA'),('New Millport','PA'),('New Milton','WV'),('New Munich','MN'),('New Munster','WI'),('New Orleans','LA'),('New Oxford','PA'),('New Palestine','IN'),('New Paltz','NY'),('New Paris','IN'),('New Paris','OH'),('New Paris','PA'),('New Park','PA'),('New Philadelphia','OH'),('New Philadelphia','PA'),('New Pine Creek','OR'),('New Plymouth','ID'),('New Plymouth','OH'),('New Point','IN'),('New Point','VA'),('New Port Richey','FL'),('New Portland','ME'),('New Prague','MN'),('New Preston Marble Dale','CT'),('New Providence','IA'),('New Providence','NJ'),('New Providence','PA'),('New Raymer','CO'),('New Richland','MN'),('New Richmond','IN'),('New Richmond','OH'),('New Richmond','WI'),('New Richmond','WV'),('New Riegel','OH'),('New Ringgold','PA'),('New River','AZ'),('New River','VA'),('New Roads','LA'),('New Rochelle','NY'),('New Rockford','ND'),('New Ross','IN'),('New Rumley','OH'),('New Russia','NY'),('New Salem','IL'),('New Salem','MA'),('New Salem','ND'),('New Salem','PA'),('New Salisbury','IN'),('New Sarpy','LA'),('New Sharon','IA'),('New Sharon','ME'),('New Site','MS'),('New Smyrna Beach','FL'),('New Springfield','OH'),('New Stanton','PA'),('New Straitsville','OH'),('New Stuyahok','AK'),('New Suffolk','NY'),('New Summerfield','TX'),('New Sweden','ME'),('New Tazewell','TN'),('New Town','MA'),('New Town','ND'),('New Trenton','IN'),('New Tripoli','PA'),('New Troy','MI'),('New Ulm','MN'),('New Ulm','TX'),('New Underwood','SD'),('New Vernon','NJ'),('New Vienna','IA'),('New Vienna','OH'),('New Vineyard','ME'),('New Virginia','IA'),('New Washington','IN'),('New Washington','OH'),('New Waterford','OH'),('New Waverly','IN'),('New Waverly','TX'),('New Weston','OH'),('New Wilmington','PA'),('New Windsor','IL'),('New Windsor','MD'),('New Windsor','NY'),('New Woodstock','NY'),('New York','NY'),('New York Mills','MN'),('New York Mills','NY'),('New Zion','SC'),('Newalla','OK'),('Newark','AR'),('Newark','CA'),('Newark','DE'),('Newark','IL'),('Newark','MD'),('Newark','MO'),('Newark','NJ'),('Newark','NY'),('Newark','OH'),('Newark','TX'),('Newark Valley','NY'),('Newaygo','MI'),('Newberg','OR'),('Newbern','AL'),('Newbern','TN'),('Newbern','VA'),('Newberry','FL'),('Newberry','IN'),('Newberry','MI'),('Newberry','SC'),('Newberry Springs','CA'),('Newborn','GA'),('Newburg','MD'),('Newburg','MO'),('Newburg','ND'),('Newburg','PA'),('Newburg','WI'),('Newburg','WV'),('Newburgh','IN'),('Newburgh','NY'),('Newbury','MA'),('Newbury','NH'),('Newbury','OH'),('Newbury','VT'),('Newbury Park','CA'),('Newburyport','MA'),('Newcastle','CA'),('Newcastle','ME'),('Newcastle','NE'),('Newcastle','OK'),('Newcastle','TX'),('Newcastle','UT'),('Newcastle','WY'),('Newcomb','MD'),('Newcomb','NM'),('Newcomb','NY'),('Newcomb','TN'),('Newcomerstown','OH'),('Newdale','ID'),('Newell','IA'),('Newell','NC'),('Newell','PA'),('Newell','SD'),('Newell','WV'),('Newellton','LA'),('Newfane','NY'),('Newfane','VT'),('Newfield','ME'),('Newfield','NJ'),('Newfield','NY'),('Newfields','NH'),('Newfolden','MN'),('Newfoundland','NJ'),('Newfoundland','PA'),('Newhall','CA'),('Newhall','IA'),('Newhall','WV'),('Newhebron','MS'),('Newhope','AR'),('Newington','CT'),('Newington','GA'),('Newington','NH'),('Newington','VA'),('Newkirk','NM'),('Newkirk','OK'),('Newland','NC'),('Newllano','LA'),('Newman','CA'),('Newman','IL'),('Newman Grove','NE'),('Newman Lake','WA'),('Newmanstown','PA'),('Newmarket','NH'),('Newnan','GA'),('Newport','AR'),('Newport','IN'),('Newport','KY'),('Newport','ME'),('Newport','MI'),('Newport','MN'),('Newport','NC'),('Newport','NE'),('Newport','NH'),('Newport','NJ'),('Newport','NY'),('Newport','OH'),('Newport','OR'),('Newport','PA'),('Newport','RI'),('Newport','TN'),('Newport','VA'),('Newport','VT'),('Newport','WA'),('Newport Beach','CA'),('Newport Center','VT'),('Newport Coast','CA'),('Newport News','VA'),('Newry','ME'),('Newry','PA'),('Newry','SC'),('Newsoms','VA'),('Newton','AL'),('Newton','GA'),('Newton','IA'),('Newton','IL'),('Newton','KS'),('Newton','MA'),('Newton','MS'),('Newton','NC'),('Newton','NH'),('Newton','NJ'),('Newton','TX'),('Newton','UT'),('Newton','WI'),('Newton','WV'),('Newton Center','MA'),('Newton Falls','NY'),('Newton Falls','OH'),('Newton Grove','NC'),('Newton Hamilton','PA'),('Newton Highlands','MA'),('Newton Junction','NH'),('Newton Lower Falls','MA'),('Newton Upper Falls','MA'),('Newtonia','MO'),('Newtonsville','OH'),('Newtonville','MA'),('Newtonville','NJ'),('Newtonville','NY'),('Newtown','CT'),('Newtown','IN'),('Newtown','MO'),('Newtown','PA'),('Newtown','VA'),('Newtown','WV'),('Newtown Square','PA'),('Newville','AL'),('Newville','PA'),('Ney','OH'),('Nezperce','ID'),('Niagara','ND'),('Niagara','WI'),('Niagara Falls','NY'),('Niagara University','NY'),('Niangua','MO'),('Niantic','CT'),('Niantic','IL'),('Nicasio','CA'),('Nice','CA'),('Niceville','FL'),('Nicholasville','KY'),('Nicholls','GA'),('Nichols','FL'),('Nichols','IA'),('Nichols','NY'),('Nichols','SC'),('Nichols','WI'),('Nicholson','GA'),('Nicholson','MS'),('Nicholson','PA'),('Nicholville','NY'),('Nickelsville','VA'),('Nickerson','KS'),('Nickerson','NE'),('Nicktown','PA'),('Nicolaus','CA'),('Nicollet','MN'),('Nicoma Park','OK'),('Nielsville','MN'),('Nightmute','AK'),('Nikiski','AK'),('Nikolai','AK'),('Nikolski','AK'),('Niland','CA'),('Niles','IL'),('Niles','MI'),('Niles','OH'),('Nilwood','IL'),('Nimitz','WV'),('Nimrod','MN'),('Ninde','VA'),('Nine Mile Falls','WA'),('Ninety Six','SC'),('Nineveh','IN'),('Nineveh','NY'),('Nineveh','PA'),('Ninilchik','AK'),('Ninnekah','OK'),('Ninole','HI'),('Niobe','NY'),('Niobrara','NE'),('Niota','IL'),('Niota','TN'),('Niotaze','KS'),('Nipomo','CA'),('Nipton','CA'),('Nisland','SD'),('Nisswa','MN'),('Nisula','MI'),('Nitro','WV'),('Niverville','NY'),('Niwot','CO'),('Nixa','MO'),('Nixon','NV'),('Nixon','TX'),('Noatak','AK'),('Noble','IL'),('Noble','LA'),('Noble','MO'),('Noble','OK'),('Nobleboro','ME'),('Noblesville','IN'),('Nobleton','FL'),('Nocatee','FL'),('Nocona','TX'),('Nodaway','IA'),('Noel','MO'),('Nogal','NM'),('Nogales','AZ'),('Nokesville','VA'),('Nokomis','FL'),('Nokomis','IL'),('Nolan','TX'),('Nolanville','TX'),('Nolensville','TN'),('Noma','FL'),('Nome','AK'),('Nome','ND'),('Nome','TX'),('Nonantum','MA'),('Nondalton','AK'),('Nooksack','WA'),('Noonan','ND'),('Noorvik','AK'),('Nora','IL'),('Nora','VA'),('Nora Springs','IA'),('Norborne','MO'),('Norcatur','KS'),('Norco','CA'),('Norco','LA'),('Norcross','GA'),('Norcross','MN'),('Norden','CA'),('Nordheim','TX'),('Nordland','WA'),('Nordman','ID'),('Norene','TN'),('Norfolk','CT'),('Norfolk','MA'),('Norfolk','NE'),('Norfolk','NY'),('Norfolk','VA'),('Norfork','AR'),('Norge','VA'),('Norlina','NC'),('Norma','NJ'),('Normal','AL'),('Normal','IL'),('Normalville','PA'),('Norman','AR'),('Norman','IN'),('Norman','NC'),('Norman','OK'),('Norman Park','GA'),('Normandy','TN'),('Normandy Beach','NJ'),('Normangee','TX'),('Normanna','TX'),('Normantown','WV'),('Norphlet','AR'),('Norridgewock','ME'),('Norris','IL'),('Norris','MT'),('Norris','SC'),('Norris','SD'),('Norris','TN'),('Norris City','IL'),('Norristown','GA'),('Norristown','PA'),('North','SC'),('North','VA'),('North Adams','MA'),('North Adams','MI'),('North Amherst','MA'),('North Andover','MA'),('North Anson','ME'),('North Apollo','PA'),('North Arlington','NJ'),('North Attleboro','MA'),('North Augusta','SC'),('North Aurora','IL'),('North Babylon','NY'),('North Baltimore','OH'),('North Bangor','NY'),('North Bay','NY'),('North Beach','MD'),('North Bend','NE'),('North Bend','OH'),('North Bend','OR'),('North Bend','PA'),('North Bend','WA'),('North Bennington','VT'),('North Benton','OH'),('North Bergen','NJ'),('North Berwick','ME'),('North Billerica','MA'),('North Blenheim','NY'),('North Bloomfield','OH'),('North Bonneville','WA'),('North Boston','NY'),('North Branch','MI'),('North Branch','MN'),('North Branch','NY'),('North Branford','CT'),('North Bridgton','ME'),('North Brookfield','MA'),('North Brookfield','NY'),('North Brunswick','NJ'),('North Buena Vista','IA'),('North Canton','CT'),('North Canton','OH'),('North Carrollton','MS'),('North Carver','MA'),('North Charleston','SC'),('North Chatham','MA'),('North Chatham','NY'),('North Chelmsford','MA'),('North Chicago','IL'),('North Chili','NY'),('North Clarendon','VT'),('North Collins','NY'),('North Concord','VT'),('North Conway','NH'),('North Creek','NY'),('North Dartmouth','MA'),('North Dighton','MA'),('North East','MD'),('North East','PA'),('North Eastham','MA'),('North Easton','MA'),('North Egremont','MA'),('North English','IA'),('North Evans','NY'),('North Fairfield','OH'),('North Falmouth','MA'),('North Ferrisburgh','VT'),('North Fork','CA'),('North Fork','ID'),('North Fort Myers','FL'),('North Franklin','CT'),('North Freedom','WI'),('North Garden','VA'),('North Georgetown','OH'),('North Grafton','MA'),('North Granby','CT'),('North Granville','NY'),('North Greece','NY'),('North Grosvenordale','CT'),('North Hampton','NH'),('North Hampton','OH'),('North Hartland','VT'),('North Hatfield','MA'),('North Haven','CT'),('North Haven','ME'),('North Haverhill','NH'),('North Henderson','IL'),('North Hero','VT'),('North Highlands','CA'),('North Hills','CA'),('North Hollywood','CA'),('North Hoosick','NY'),('North Houston','TX'),('North Hudson','NY'),('North Hyde Park','VT'),('North Jackson','OH'),('North Java','NY'),('North Jay','ME'),('North Judson','IN'),('North Kingstown','RI'),('North Kingsville','OH'),('North Lake','WI'),('North Lakewood','WA'),('North Las Vegas','NV'),('North Lawrence','NY'),('North Lawrence','OH'),('North Lewisburg','OH'),('North Liberty','IA'),('North Liberty','IN'),('North Lima','OH'),('North Little Rock','AR'),('North Loup','NE'),('North Manchester','IN'),('North Marshfield','MA'),('North Matewan','WV'),('North Metro','GA'),('North Miami','OK'),('North Miami Beach','FL'),('North Middletown','KY'),('North Monmouth','ME'),('North Montpelier','VT'),('North Myrtle Beach','SC'),('North Newton','KS'),('North Norwich','NY'),('North Olmsted','OH'),('North Oxford','MA'),('North Palm Beach','FL'),('North Palm Springs','CA'),('North Pembroke','MA'),('North Pitcher','NY'),('North Plains','OR'),('North Platte','NE'),('North Pole','AK'),('North Pomfret','VT'),('North Port','FL'),('North Powder','OR'),('North Pownal','VT'),('North Prairie','WI'),('North Providence','RI'),('North Reading','MA'),('North Richland Hills','TX'),('North Ridgeville','OH'),('North Rim','AZ'),('North River','NY'),('North Robinson','OH'),('North Rose','NY'),('North Royalton','OH'),('North Salem','IN'),('North Salem','NH'),('North Salem','NY'),('North Salt Lake','UT'),('North San Juan','CA'),('North Sandwich','NH'),('North Scituate','MA'),('North Scituate','RI'),('North Sioux City','SD'),('North Smithfield','RI'),('North Spring','WV'),('North Springfield','PA'),('North Springfield','VT'),('North Star','MI'),('North Star','OH'),('North Stonington','CT'),('North Stratford','NH'),('North Street','MI'),('North Sutton','NH'),('North Tazewell','VA'),('North Thetford','VT'),('North Tonawanda','NY'),('North Troy','VT'),('North Truro','MA'),('North Turner','ME'),('North Uxbridge','MA'),('North Vassalboro','ME'),('North Vernon','IN'),('North Versailles','PA'),('North Wales','PA'),('North Walpole','NH'),('North Waltham','MA'),('North Washington','IA'),('North Washington','PA'),('North Waterboro','ME'),('North Waterford','ME'),('North Webster','IN'),('North Westchester','CT'),('North Weymouth','MA'),('North Wilkesboro','NC'),('North Windham','CT'),('North Woodstock','NH'),('North Yarmouth','ME'),('North Zulch','TX'),('Northampton','MA'),('Northampton','PA'),('Northboro','IA'),('Northborough','MA'),('Northbridge','MA'),('Northbrook','IL'),('Northeast Harbor','ME'),('Northern Cambria','PA'),('Northfield','CT'),('Northfield','MA'),('Northfield','MN'),('Northfield','NJ'),('Northfield','OH'),('Northfield','VT'),('Northfield Falls','VT'),('Northford','CT'),('Northfork','WV'),('Northome','MN'),('Northpoint','PA'),('Northport','AL'),('Northport','MI'),('Northport','NY'),('Northport','WA'),('Northridge','CA'),('Northrop','MN'),('Northumberland','NH'),('Northumberland','PA'),('Northvale','NJ'),('Northville','MI'),('Northville','NY'),('Northville','SD'),('Northway','AK'),('Northwood','IA'),('Northwood','ND'),('Northwood','NH'),('Northwood','OH'),('Norton','KS'),('Norton','MA'),('Norton','TX'),('Norton','VA'),('Norton','VT'),('Norton','WV'),('Nortonville','KS'),('Nortonville','KY'),('Norvell','MI'),('Norvelt','PA'),('Norwalk','CA'),('Norwalk','CT'),('Norwalk','IA'),('Norwalk','OH'),('Norwalk','WI'),('Norway','IA'),('Norway','KS'),('Norway','ME'),('Norway','MI'),('Norway','SC'),('Norwell','MA'),('Norwich','CT'),('Norwich','KS'),('Norwich','ND'),('Norwich','NY'),('Norwich','OH'),('Norwich','VT'),('Norwood','CO'),('Norwood','GA'),('Norwood','LA'),('Norwood','MA'),('Norwood','MN'),('Norwood','MO'),('Norwood','NC'),('Norwood','NJ'),('Norwood','NY'),('Norwood','PA'),('Norwood','VA'),('Norwood Young America','MN'),('Notasulga','AL'),('Noti','OR'),('Notre Dame','IN'),('Notrees','TX'),('Nottawa','MI'),('Nottingham','MD'),('Nottingham','NH'),('Nottingham','PA'),('Nottoway','VA'),('Notus','ID'),('Nova','OH'),('Novato','CA'),('Novelty','MO'),('Novelty','OH'),('Novi','MI'),('Novice','TX'),('Novinger','MO'),('Nowata','OK'),('Noxapater','MS'),('Noxen','PA'),('Noxon','MT'),('Noyes','MN'),('Nu Mine','PA'),('Nubieber','CA'),('Nucla','CO'),('Nuevo','CA'),('Nuiqsut','AK'),('Nulato','AK'),('Numidia','PA'),('Nunam Iqua','AK'),('Nunapitchuk','AK'),('Nunda','NY'),('Nunda','SD'),('Nunez','GA'),('Nunica','MI'),('Nunn','CO'),('Nunnelly','TN'),('Nuremberg','PA'),('Nursery','TX'),('Nutley','NJ'),('Nutrioso','AZ'),('Nutting Lake','MA'),('Nuttsville','VA'),('Nyack','NY'),('Nye','MT'),('Nyssa','OR'),('O Brien','FL'),('O Brien','OR'),('O Brien','TX'),('O Fallon','IL'),('O Fallon','MO'),('O Kean','AR'),('O Neals','CA'),('Oacoma','SD'),('Oak','NE'),('Oak Bluffs','MA'),('Oak Brook','IL'),('Oak City','NC'),('Oak City','UT'),('Oak Creek','CO'),('Oak Creek','WI'),('Oak Forest','IL'),('Oak Grove','AR'),('Oak Grove','KY'),('Oak Grove','LA'),('Oak Grove','MO'),('Oak Hall','VA'),('Oak Harbor','OH'),('Oak Harbor','WA'),('Oak Hill','AL'),('Oak Hill','FL'),('Oak Hill','NY'),('Oak Hill','OH'),('Oak Island','MN'),('Oak Island','NC'),('Oak Lawn','IL'),('Oak Park','CA'),('Oak Park','IL'),('Oak Park','MI'),('Oak Park','MN'),('Oak Ridge','LA'),('Oak Ridge','MO'),('Oak Ridge','NC'),('Oak Ridge','NJ'),('Oak Ridge','PA'),('Oak Ridge','TN'),('Oak Run','CA'),('Oak Vale','MS'),('Oak View','CA'),('Oakboro','NC'),('Oakdale','CA'),('Oakdale','CT'),('Oakdale','IA'),('Oakdale','IL'),('Oakdale','LA'),('Oakdale','NE'),('Oakdale','NY'),('Oakdale','PA'),('Oakdale','TN'),('Oakdale','WI'),('Oakes','ND'),('Oakesdale','WA'),('Oakfield','GA'),('Oakfield','ME'),('Oakfield','NY'),('Oakfield','TN'),('Oakfield','WI'),('Oakford','IL'),('Oakford','IN'),('Oakham','MA'),('Oakhurst','CA'),('Oakhurst','NJ'),('Oakhurst','OK'),('Oakhurst','TX'),('Oakland','AR'),('Oakland','CA'),('Oakland','FL'),('Oakland','IA'),('Oakland','IL'),('Oakland','KY'),('Oakland','MD'),('Oakland','ME'),('Oakland','MI'),('Oakland','MS'),('Oakland','NE'),('Oakland','NJ'),('Oakland','OR'),('Oakland','RI'),('Oakland','TN'),('Oakland','TX'),('Oakland City','IN'),('Oakland Gardens','NY'),('Oakland Mills','PA'),('Oakley','CA'),('Oakley','ID'),('Oakley','KS'),('Oakley','MI'),('Oakley','UT'),('Oakman','AL'),('Oakman','GA'),('Oakmont','PA'),('Oakpark','VA'),('Oakridge','OR'),('Oaks','OK'),('Oaks','PA'),('Oaks Corners','NY'),('Oakton','VA'),('Oaktown','IN'),('Oakvale','WV'),('Oakville','CA'),('Oakville','CT'),('Oakville','IA'),('Oakville','IN'),('Oakville','TX'),('Oakville','WA'),('Oakwood','GA'),('Oakwood','IL'),('Oakwood','OH'),('Oakwood','OK'),('Oakwood','TX'),('Oakwood','VA'),('Oark','AR'),('Oatman','AZ'),('Oberlin','KS'),('Oberlin','LA'),('Oberlin','OH'),('Obernburg','NY'),('Oberon','ND'),('Obion','TN'),('Oblong','IL'),('Obrien','CA'),('Ocala','FL'),('Ocate','NM'),('Occidental','CA'),('Occoquan','VA'),('Ocean Beach','NY'),('Ocean Bluff','MA'),('Ocean City','MD'),('Ocean City','NJ'),('Ocean Gate','NJ'),('Ocean Grove','NJ'),('Ocean Isle Beach','NC'),('Ocean Park','ME'),('Ocean Park','WA'),('Ocean Shores','WA'),('Ocean Springs','MS'),('Ocean View','DE'),('Ocean View','HI'),('Ocean View','NJ'),('Oceana','WV'),('Oceano','CA'),('Oceanport','NJ'),('Oceanside','CA'),('Oceanside','NY'),('Oceanside','OR'),('Oceanville','NJ'),('Oceola','OH'),('Ochelata','OK'),('Ocheyedan','IA'),('Ochlocknee','GA'),('Ochopee','FL'),('Ocilla','GA'),('Ocklawaha','FL'),('Ocoee','FL'),('Ocoee','TN'),('Oconee','GA'),('Oconee','IL'),('Oconomowoc','WI'),('Oconto','NE'),('Oconto','WI'),('Oconto Falls','WI'),('Ocotillo','CA'),('Ocracoke','NC'),('Odanah','WI'),('Odd','WV'),('Odebolt','IA'),('Odell','IL'),('Odell','NE'),('Odell','OR'),('Odell','TX'),('Odem','TX'),('Oden','AR'),('Oden','MI'),('Odenton','MD'),('Odenville','AL'),('Odessa','DE'),('Odessa','FL'),('Odessa','MN'),('Odessa','MO'),('Odessa','NE'),('Odessa','NY'),('Odessa','TX'),('Odessa','WA'),('Odin','IL'),('Odin','MN'),('Odon','IN'),('Odonnell','TX'),('Odum','GA'),('Oelrichs','SD'),('Oelwein','IA'),('Offerle','KS'),('Offerman','GA'),('Offutt A F B','NE'),('Ogallah','KS'),('Ogallala','NE'),('Ogden','AR'),('Ogden','IA'),('Ogden','IL'),('Ogden','KS'),('Ogden','UT'),('Ogdensburg','NJ'),('Ogdensburg','NY'),('Ogdensburg','WI'),('Ogema','MN'),('Ogema','WI'),('Ogilvie','MN'),('Oglala','SD'),('Oglesby','IL'),('Oglesby','TX'),('Oglethorpe','GA'),('Ogunquit','ME'),('Ohatchee','AL'),('Ohio','IL'),('Ohio City','CO'),('Ohio City','OH'),('Ohiopyle','PA'),('Ohiowa','NE'),('Ohlman','IL'),('Oil City','LA'),('Oil City','PA'),('Oil Springs','KY'),('Oil Trough','AR'),('Oilmont','MT'),('Oilton','OK'),('Oilton','TX'),('Oilville','VA'),('Ojai','CA'),('Ojibwa','WI'),('Ojo Caliente','NM'),('Ojo Feliz','NM'),('Okabena','MN'),('Okahumpka','FL'),('Okanogan','WA'),('Okarche','OK'),('Okatie','SC'),('Okaton','SD'),('Okauchee','WI'),('Okawville','IL'),('Okay','OK'),('Okeana','OH'),('Okeechobee','FL'),('Okeene','OK'),('Okemah','OK'),('Okemos','MI'),('Oketo','KS'),('Oklahoma City','OK'),('Oklaunion','TX'),('Oklee','MN'),('Okmulgee','OK'),('Okoboji','IA'),('Okolona','AR'),('Okolona','MS'),('Okolona','OH'),('Okreek','SD'),('Oktaha','OK'),('Ola','AR'),('Ola','ID'),('Olalla','WA'),('Olamon','ME'),('Olancha','CA'),('Olanta','PA'),('Olanta','SC'),('Olar','SC'),('Olathe','CO'),('Olathe','KS'),('Olaton','KY'),('Olcott','NY'),('Old Appleton','MO'),('Old Bethpage','NY'),('Old Bridge','NJ'),('Old Chatham','NY'),('Old Fields','WV'),('Old Forge','NY'),('Old Forge','PA'),('Old Fort','NC'),('Old Fort','OH'),('Old Glory','TX'),('Old Greenwich','CT'),('Old Harbor','AK'),('Old Hickory','TN'),('Old Lyme','CT'),('Old Mission','MI'),('Old Monroe','MO'),('Old Mystic','CT'),('Old Ocean','TX'),('Old Orchard Beach','ME'),('Old Saybrook','CT'),('Old Station','CA'),('Old Town','FL'),('Old Town','ME'),('Old Washington','OH'),('Old Westbury','NY'),('Old Zionsville','PA'),('Olden','TX'),('Oldenburg','IN'),('Oldfield','MO'),('Oldfort','TN'),('Oldham','SD'),('Oldhams','VA'),('Olds','IA'),('Oldsmar','FL'),('Oldtown','ID'),('Oldtown','MD'),('Oldwick','NJ'),('Olean','MO'),('Olean','NY'),('Olema','CA'),('Oley','PA'),('Olga','WA'),('Olin','IA'),('Olin','NC'),('Olive','MT'),('Olive Branch','IL'),('Olive Branch','MS'),('Olive Hill','KY'),('Olivebridge','NY'),('Oliveburg','PA'),('Olivehill','TN'),('Olivehurst','CA'),('Oliver','GA'),('Oliver','PA'),('Oliver Springs','TN'),('Olivet','MI'),('Olivet','SD'),('Olivia','MN'),('Olivia','NC'),('Olla','LA'),('Ollie','IA'),('Olmito','TX'),('Olmitz','KS'),('Olmstead','KY'),('Olmsted','IL'),('Olmsted Falls','OH'),('Olmstedville','NY'),('Olney','IL'),('Olney','MD'),('Olney','MO'),('Olney','MT'),('Olney','TX'),('Olney Springs','CO'),('Olpe','KS'),('Olsburg','KS'),('Olton','TX'),('Olustee','FL'),('Olustee','OK'),('Olympia','KY'),('Olympia','WA'),('Olympia Fields','IL'),('Olympic Valley','CA'),('Olyphant','PA'),('Omaha','AR'),('Omaha','GA'),('Omaha','IL'),('Omaha','NE'),('Omaha','TX'),('Omak','WA'),('Omar','WV'),('Omega','GA'),('Omega','OK'),('Omena','MI'),('Omer','MI'),('Omro','WI'),('Ona','FL'),('Ona','WV'),('Onaga','KS'),('Onaka','SD'),('Onalaska','TX'),('Onalaska','WA'),('Onalaska','WI'),('Onamia','MN'),('Onancock','VA'),('Onarga','IL'),('Onawa','IA'),('Onaway','MI'),('Oneco','CT'),('Oneco','FL'),('Onego','WV'),('Oneida','AR'),('Oneida','IL'),('Oneida','KS'),('Oneida','KY'),('Oneida','NY'),('Oneida','PA'),('Oneida','TN'),('Oneida','WI'),('Oneill','NE'),('Onekama','MI'),('Onemo','VA'),('Oneonta','AL'),('Oneonta','NY'),('Ong','NE'),('Onia','AR'),('Onida','SD'),('Onley','VA'),('Only','TN'),('Ono','PA'),('Onondaga','MI'),('Onset','MA'),('Onslow','IA'),('Onsted','MI'),('Ontario','CA'),('Ontario','NY'),('Ontario','OH'),('Ontario','OR'),('Ontario','WI'),('Ontario Center','NY'),('Ontonagon','MI'),('Onward','IN'),('Onyx','CA'),('Ookala','HI'),('Oolitic','IN'),('Oologah','OK'),('Ooltewah','TN'),('Oostburg','WI'),('Opa Locka','FL'),('Opal','WY'),('Opdyke','IL'),('Opelika','AL'),('Opelousas','LA'),('Opheim','MT'),('Ophelia','VA'),('Ophiem','IL'),('Ophir','CO'),('Ophir','KY'),('Ophir','OR'),('Opolis','KS'),('Opp','AL'),('Oquawka','IL'),('Oquossoc','ME'),('Ora','IN'),('Oracle','AZ'),('Oradell','NJ'),('Oral','SD'),('Oran','IA'),('Oran','MO'),('Orange','CA'),('Orange','CT'),('Orange','MA'),('Orange','NJ'),('Orange','TX'),('Orange','VA'),('Orange Beach','AL'),('Orange City','FL'),('Orange City','IA'),('Orange Cove','CA'),('Orange Grove','TX'),('Orange Lake','FL'),('Orange Park','FL'),('Orange Springs','FL'),('Orangeburg','NY'),('Orangeburg','SC'),('Orangefield','TX'),('Orangevale','CA'),('Orangeville','IL'),('Orangeville','OH'),('Orangeville','PA'),('Orangeville','UT'),('Oraville','IL'),('Orbisonia','PA'),('Orcas','WA'),('Orchard','CO'),('Orchard','IA'),('Orchard','NE'),('Orchard','TX'),('Orchard Hill','GA'),('Orchard Park','NY'),('Ord','NE'),('Orderville','UT'),('Ordinary','VA'),('Ordway','CO'),('Ore City','TX'),('Oreana','IL'),('Orefield','PA'),('Oregon','IL'),('Oregon','MO'),('Oregon','OH'),('Oregon','WI'),('Oregon City','OR'),('Oregon House','CA'),('Oregonia','OH'),('Oreland','PA'),('Orem','UT'),('Orestes','IN'),('Orford','NH'),('Orfordville','WI'),('Organ','NM'),('Orgas','WV'),('Orick','CA'),('Orient','IA'),('Orient','IL'),('Orient','ME'),('Orient','NY'),('Orient','OH'),('Orient','SD'),('Orient','WA'),('Oriental','NC'),('Orinda','CA'),('Orion','IL'),('Oriska','ND'),('Oriskany','NY'),('Oriskany','VA'),('Oriskany Falls','NY'),('Orkney Springs','VA'),('Orla','TX'),('Orland','CA'),('Orland','IN'),('Orland','ME'),('Orland Park','IL'),('Orlando','FL'),('Orlando','KY'),('Orlando','OK'),('Orlando','WV'),('Orlean','VA'),('Orleans','CA'),('Orleans','IN'),('Orleans','MA'),('Orleans','MI'),('Orleans','NE'),('Orleans','VT'),('Orlinda','TN'),('Orma','WV'),('Ormond Beach','FL'),('Ormsby','MN'),('Oro Grande','CA'),('Orocovis','PR'),('Orofino','ID'),('Orogrande','NM'),('Orondo','WA'),('Orono','ME'),('Oronoco','MN'),('Oronogo','MO'),('Orosi','CA'),('Orovada','NV'),('Oroville','CA'),('Oroville','WA'),('Orr','MN'),('Orrick','MO'),('Orrington','ME'),('Orrs Island','ME'),('Orrstown','PA'),('Orrtanna','PA'),('Orrum','NC'),('Orrville','AL'),('Orrville','OH'),('Orson','PA'),('Orting','WA'),('Ortley','SD'),('Ortonville','MI'),('Ortonville','MN'),('Orviston','PA'),('Orwell','NY'),('Orwell','OH'),('Orwell','VT'),('Orwigsburg','PA'),('Osage','IA'),('Osage','MN'),('Osage','OK'),('Osage','WV'),('Osage','WY'),('Osage Beach','MO'),('Osage City','KS'),('Osakis','MN'),('Osawatomie','KS'),('Osborn','MO'),('Osborne','KS'),('Osburn','ID'),('Oscar','LA'),('Oscar','OK'),('Osceola','AR'),('Osceola','IA'),('Osceola','IN'),('Osceola','MO'),('Osceola','NE'),('Osceola','PA'),('Osceola','WI'),('Osceola Mills','PA'),('Osco','IL'),('Oscoda','MI'),('Osgood','IN'),('Osgood','OH'),('Oshkosh','NE'),('Oshkosh','WI'),('Oshtemo','MI'),('Oskaloosa','IA'),('Oskaloosa','KS'),('Oslo','MN'),('Osmond','NE'),('Osnabrock','ND'),('Osprey','FL'),('Osseo','MI'),('Osseo','MN'),('Osseo','WI'),('Ossian','IA'),('Ossian','IN'),('Ossineke','MI'),('Ossining','NY'),('Ossipee','NH'),('Osteen','FL'),('Osterville','MA'),('Ostrander','MN'),('Ostrander','OH'),('Oswegatchie','NY'),('Oswego','IL'),('Oswego','KS'),('Oswego','NY'),('Osyka','MS'),('Otego','NY'),('Othello','WA'),('Otho','IA'),('Otis','CO'),('Otis','KS'),('Otis','LA'),('Otis','MA'),('Otis','OR'),('Otis Orchards','WA'),('Otisco','IN'),('Otisville','MI'),('Otisville','NY'),('Otley','IA'),('Oto','IA'),('Otoe','NE'),('Otsego','MI'),('Ottawa','IL'),('Ottawa','KS'),('Ottawa','OH'),('Ottawa','WV'),('Ottawa Lake','MI'),('Otter','MT'),('Otter Creek','FL'),('Otter Lake','MI'),('Otter Rock','OR'),('Otterbein','IN'),('Ottertail','MN'),('Otterville','MO'),('Ottine','TX'),('Otto','NC'),('Otto','NY'),('Otto','WY'),('Ottosen','IA'),('Ottoville','OH'),('Ottsville','PA'),('Ottumwa','IA'),('Otway','OH'),('Otwell','IN'),('Ouaquaga','NY'),('Ouray','CO'),('Outing','MN'),('Outlook','MT'),('Outlook','WA'),('Ouzinkie','AK'),('Ovalo','TX'),('Ovando','MT'),('Overbrook','KS'),('Overbrook','OK'),('Overgaard','AZ'),('Overland Park','KS'),('Overpeck','OH'),('Overton','NE'),('Overton','NV'),('Overton','TX'),('Ovett','MS'),('Ovid','CO'),('Ovid','MI'),('Ovid','NY'),('Oviedo','FL'),('Owaneco','IL'),('Owanka','SD'),('Owasso','OK'),('Owatonna','MN'),('Owego','NY'),('Owen','WI'),('Owendale','MI'),('Owens Cross Roads','AL'),('Owensboro','KY'),('Owensburg','IN'),('Owensville','IN'),('Owensville','MO'),('Owensville','OH'),('Owenton','KY'),('Owings','MD'),('Owings Mills','MD'),('Owingsville','KY'),('Owls Head','ME'),('Owls Head','NY'),('Owosso','MI'),('Owyhee','NV'),('Oxbow','ME'),('Oxbow','NY'),('Oxbow','OR'),('Oxford','AL'),('Oxford','AR'),('Oxford','CT'),('Oxford','FL'),('Oxford','GA'),('Oxford','IA'),('Oxford','IN'),('Oxford','KS'),('Oxford','MA'),('Oxford','MD'),('Oxford','ME'),('Oxford','MI'),('Oxford','MS'),('Oxford','NC'),('Oxford','NE'),('Oxford','NJ'),('Oxford','NY'),('Oxford','OH'),('Oxford','PA'),('Oxford','WI'),('Oxford Junction','IA'),('Oxly','MO'),('Oxnard','CA'),('Oxon Hill','MD'),('Oyens','IA'),('Oyster','VA'),('Oyster Bay','NY'),('Oysterville','WA'),('Ozan','AR'),('Ozark','AL'),('Ozark','AR'),('Ozark','IL'),('Ozark','MO'),('Ozawkie','KS'),('Ozona','FL'),('Ozona','TX'),('Ozone','AR'),('Ozone Park','NY'),('Paauilo','HI'),('Pablo','MT'),('Pace','MS'),('Pachuta','MS'),('Pacific','MO'),('Pacific','WA'),('Pacific Beach','WA'),('Pacific City','OR'),('Pacific Grove','CA'),('Pacific Junction','IA'),('Pacific Palisades','CA'),('Pacifica','CA'),('Packwaukee','WI'),('Packwood','IA'),('Packwood','WA'),('Pacoima','CA'),('Pacolet','SC'),('Pacolet Mills','SC'),('Paden','OK'),('Paden City','WV'),('Padroni','CO'),('Paducah','KY'),('Paducah','TX'),('Paeonian Springs','VA'),('Page','AZ'),('Page','ND'),('Page','NE'),('Page','WV'),('Pageland','SC'),('Pageton','WV'),('Pagosa Springs','CO'),('Paguate','NM'),('Pahala','HI'),('Pahoa','HI'),('Pahokee','FL'),('Pahrump','NV'),('Paia','HI'),('Paicines','CA'),('Paige','TX'),('Paincourtville','LA'),('Painesdale','MI'),('Painesville','OH'),('Paint Bank','VA'),('Paint Lick','KY'),('Paint Rock','AL'),('Paint Rock','TX'),('Painted Post','NY'),('Painter','VA'),('Paintsville','KY'),('Paisley','FL'),('Paisley','OR'),('Pala','CA'),('Palacios','TX'),('Palatine','IL'),('Palatine Bridge','NY'),('Palatka','FL'),('Palco','KS'),('Palenville','NY'),('Palermo','CA'),('Palermo','ME'),('Palermo','ND'),('Palestine','AR'),('Palestine','IL'),('Palestine','OH'),('Palestine','TX'),('Palestine','WV'),('Palisade','CO'),('Palisade','MN'),('Palisade','NE'),('Palisades','NY'),('Palisades','WA'),('Palisades Park','NJ'),('Pall Mall','TN'),('Palm','PA'),('Palm Bay','FL'),('Palm Beach','FL'),('Palm Beach Gardens','FL'),('Palm City','FL'),('Palm Coast','FL'),('Palm Desert','CA'),('Palm Harbor','FL'),('Palm Springs','CA'),('Palmdale','CA'),('Palmdale','FL'),('Palmer','AK'),('Palmer','IA'),('Palmer','IL'),('Palmer','KS'),('Palmer','MA'),('Palmer','MI'),('Palmer','NE'),('Palmer','PR'),('Palmer','TN'),('Palmer','TX'),('Palmer Lake','CO'),('Palmerdale','AL'),('Palmersville','TN'),('Palmerton','PA'),('Palmetto','FL'),('Palmetto','GA'),('Palmetto','LA'),('Palms','MI'),('Palmyra','IL'),('Palmyra','IN'),('Palmyra','ME'),('Palmyra','MI'),('Palmyra','MO'),('Palmyra','NE'),('Palmyra','NJ'),('Palmyra','NY'),('Palmyra','PA'),('Palmyra','TN'),('Palmyra','VA'),('Palmyra','WI'),('Palo','IA'),('Palo','MI'),('Palo Alto','CA'),('Palo Cedro','CA'),('Palo Pinto','TX'),('Palo Verde','AZ'),('Palo Verde','CA'),('Paloma','IL'),('Palomar Mountain','CA'),('Palos Heights','IL'),('Palos Hills','IL'),('Palos Park','IL'),('Palos Verdes Peninsula','CA'),('Palouse','WA'),('Paluxy','TX'),('Pampa','TX'),('Pamplico','SC'),('Pamplin','VA'),('Pana','IL'),('Panaca','NV'),('Panacea','FL'),('Panama','IA'),('Panama','IL'),('Panama','NE'),('Panama','NY'),('Panama','OK'),('Panama City','FL'),('Panama City Beach','FL'),('Pandora','OH'),('Pandora','TX'),('Pangburn','AR'),('Panguitch','UT'),('Panhandle','TX'),('Panna Maria','TX'),('Panola','AL'),('Panola','OK'),('Panola','TX'),('Panora','IA'),('Panorama City','CA'),('Pansey','AL'),('Pantego','NC'),('Panther','WV'),('Panther Burn','MS'),('Paola','KS'),('Paoli','CO'),('Paoli','IN'),('Paoli','OK'),('Paoli','PA'),('Paonia','CO'),('Papaaloa','HI'),('Papaikou','HI'),('Papillion','NE'),('Papineau','IL'),('Parachute','CO'),('Paradis','LA'),('Paradise','CA'),('Paradise','KS'),('Paradise','MI'),('Paradise','MT'),('Paradise','PA'),('Paradise','TX'),('Paradise','UT'),('Paradise Inn','WA'),('Paradise Valley','AZ'),('Paradise Valley','NV'),('Paradox','CO'),('Paradox','NY'),('Paragon','IN'),('Paragonah','UT'),('Paragould','AR'),('Paramount','CA'),('Paramus','NJ'),('Parchman','MS'),('Pardeeville','WI'),('Paris','AR'),('Paris','ID'),('Paris','IL'),('Paris','KY'),('Paris','ME'),('Paris','MI'),('Paris','MO'),('Paris','MS'),('Paris','OH'),('Paris','TN'),('Paris','TX'),('Paris','VA'),('Paris Crossing','IN'),('Parish','NY'),('Parishville','NY'),('Park','KS'),('Park City','KY'),('Park City','MT'),('Park City','UT'),('Park Falls','WI'),('Park Forest','IL'),('Park Hall','MD'),('Park Hill','OK'),('Park Hills','MO'),('Park Rapids','MN'),('Park Ridge','IL'),('Park Ridge','NJ'),('Park River','ND'),('Park Valley','UT'),('Parkdale','AR'),('Parker','AZ'),('Parker','CO'),('Parker','ID'),('Parker','KS'),('Parker','PA'),('Parker','SD'),('Parker','WA'),('Parker City','IN'),('Parker Dam','CA'),('Parker Ford','PA'),('Parkers Lake','KY'),('Parkers Prairie','MN'),('Parkersburg','IA'),('Parkersburg','IL'),('Parkersburg','WV'),('Parkesburg','PA'),('Parkhill','PA'),('Parkin','AR'),('Parkman','OH'),('Parkman','WY'),('Parks','AR'),('Parks','AZ'),('Parks','NE'),('Parksley','VA'),('Parkston','SD'),('Parksville','KY'),('Parksville','NY'),('Parksville','SC'),('Parkton','MD'),('Parkton','NC'),('Parkville','MD'),('Parlier','CA'),('Parlin','CO'),('Parlin','NJ'),('Parma','ID'),('Parma','MI'),('Parma','MO'),('Parmele','NC'),('Parmelee','SD'),('Parnell','IA'),('Parnell','MO'),('Paron','AR'),('Parowan','UT'),('Parrish','AL'),('Parrish','FL'),('Parrott','GA'),('Parrott','VA'),('Parrottsville','TN'),('Parryville','PA'),('Parshall','CO'),('Parshall','ND'),('Parsippany','NJ'),('Parsons','KS'),('Parsons','TN'),('Parsons','WV'),('Parsonsburg','MD'),('Parsonsfield','ME'),('Parthenon','AR'),('Partlow','VA'),('Partridge','KS'),('Partridge','KY'),('Pasadena','CA'),('Pasadena','MD'),('Pasadena','TX'),('Pascagoula','MS'),('Pasco','WA'),('Pascoag','RI'),('Paskenta','CA'),('Paso Robles','CA'),('Pass Christian','MS'),('Passadumkeag','ME'),('Passaic','NJ'),('Passumpsic','VT'),('Patagonia','AZ'),('Pataskala','OH'),('Patch Grove','WI'),('Patchogue','NY'),('Pateros','WA'),('Paterson','NJ'),('Paterson','WA'),('Pathfork','KY'),('Patillas','PR'),('Patoka','IL'),('Patoka','IN'),('Paton','IA'),('Patrick','SC'),('Patrick Afb','FL'),('Patrick Springs','VA'),('Patricksburg','IN'),('Patriot','IN'),('Patriot','OH'),('Patten','ME'),('Patterson','AR'),('Patterson','CA'),('Patterson','GA'),('Patterson','IA'),('Patterson','IL'),('Patterson','LA'),('Patterson','MO'),('Patterson','NC'),('Patterson','NY'),('Pattersonville','NY'),('Pattison','MS'),('Pattison','TX'),('Patton','CA'),('Patton','MO'),('Patton','PA'),('Pattonsburg','MO'),('Pattonville','TX'),('Patuxent River','MD'),('Paul','ID'),('Paul Smiths','NY'),('Paulden','AZ'),('Paulding','MS'),('Paulding','OH'),('Paulina','LA'),('Paulina','OR'),('Pauline','SC'),('Paullina','IA'),('Pauls Valley','OK'),('Paulsboro','NJ'),('Pauma Valley','CA'),('Paupack','PA'),('Pavilion','NY'),('Pavillion','WY'),('Pavo','GA'),('Paw Creek','NC'),('Paw Paw','IL'),('Paw Paw','MI'),('Paw Paw','WV'),('Pawcatuck','CT'),('Pawhuska','OK'),('Pawlet','VT'),('Pawleys Island','SC'),('Pawling','NY'),('Pawnee','IL'),('Pawnee','OK'),('Pawnee','TX'),('Pawnee City','NE'),('Pawnee Rock','KS'),('Pawtucket','RI'),('Pax','WV'),('Paxico','KS'),('Paxinos','PA'),('Paxton','FL'),('Paxton','IL'),('Paxton','IN'),('Paxton','MA'),('Paxton','NE'),('Paxtonville','PA'),('Payette','ID'),('Payne','OH'),('Paynes Creek','CA'),('Paynesville','MN'),('Paynesville','WV'),('Payneville','KY'),('Payson','AZ'),('Payson','IL'),('Payson','UT'),('Pe Ell','WA'),('Pea Ridge','AR'),('Peabody','KS'),('Peabody','MA'),('Peace Dale','RI'),('Peace Valley','MO'),('Peach Bottom','PA'),('Peach Creek','WV'),('Peach Glen','PA'),('Peach Orchard','AR'),('Peach Springs','AZ'),('Peacham','VT'),('Peachland','NC'),('Peachtree City','GA'),('Peak','SC'),('Peaks Island','ME'),('Peapack','NJ'),('Pearblossom','CA'),('Pearce','AZ'),('Pearces','MI'),('Pearcy','AR'),('Pearisburg','VA'),('Pearl','IL'),('Pearl','MS'),('Pearl City','HI'),('Pearl City','IL'),('Pearl Harbor','HI'),('Pearl River','LA'),('Pearl River','NY'),('Pearland','TX'),('Pearlington','MS'),('Pearsall','TX'),('Pearson','GA'),('Pearson','WI'),('Pease','MN'),('Peaster','TX'),('Pebble Beach','CA'),('Pecan Gap','TX'),('Pecatonica','IL'),('Peck','ID'),('Peck','KS'),('Peck','MI'),('Pecks Mill','WV'),('Peckville','PA'),('Peconic','NY'),('Pecos','NM'),('Pecos','TX'),('Peculiar','MO'),('Pedricktown','NJ'),('Pedro','OH'),('Pedro Bay','AK'),('Peebles','OH'),('Peekskill','NY'),('Peel','AR'),('Peerless','MT'),('Peetz','CO'),('Peever','SD'),('Peggs','OK'),('Peggy','TX'),('Pegram','TN'),('Pekin','IL'),('Pekin','IN'),('Pekin','ND'),('Pelahatchie','MS'),('Pelham','AL'),('Pelham','GA'),('Pelham','NC'),('Pelham','NH'),('Pelham','NY'),('Pelham','TN'),('Pelican','AK'),('Pelican','LA'),('Pelican Lake','WI'),('Pelican Rapids','MN'),('Pelion','SC'),('Pelkie','MI'),('Pell City','AL'),('Pell Lake','WI'),('Pella','IA'),('Pellston','MI'),('Pellville','KY'),('Pelsor','AR'),('Pelzer','SC'),('Pemaquid','ME'),('Pemberton','MN'),('Pemberton','NJ'),('Pemberton','OH'),('Pemberville','OH'),('Pembina','ND'),('Pembine','WI'),('Pembroke','GA'),('Pembroke','KY'),('Pembroke','MA'),('Pembroke','ME'),('Pembroke','NC'),('Pembroke','VA'),('Pembroke Pines','FL'),('Pen Argyl','PA'),('Pena Blanca','NM'),('Penasco','NM'),('Pence Springs','WV'),('Pencil Bluff','AR'),('Pender','NE'),('Pendergrass','GA'),('Pendleton','IN'),('Pendleton','KY'),('Pendleton','NC'),('Pendleton','OR'),('Pendleton','SC'),('Pendleton','TX'),('Pendroy','MT'),('Penelope','TX'),('Penfield','IL'),('Penfield','NY'),('Penfield','PA'),('Pengilly','MN'),('Penhook','VA'),('Peninsula','OH'),('Penitas','TX'),('Penland','NC'),('Penn','ND'),('Penn','PA'),('Penn Laird','VA'),('Penn Run','PA'),('Penn Valley','CA'),('Penn Yan','NY'),('Pennellville','NY'),('Penney Farms','FL'),('Penngrove','CA'),('Pennington','AL'),('Pennington','MN'),('Pennington','NJ'),('Pennington','TX'),('Pennington Gap','VA'),('Pennock','MN'),('Penns Creek','PA'),('Penns Grove','NJ'),('Penns Park','PA'),('Pennsauken','NJ'),('Pennsboro','WV'),('Pennsburg','PA'),('Pennsville','NJ'),('Pennsylvania Furnace','PA'),('Pennville','IN'),('Penobscot','ME'),('Penokee','KS'),('Penrose','CO'),('Penrose','NC'),('Penryn','CA'),('Penryn','PA'),('Pensacola','FL'),('Pentress','WV'),('Pentwater','MI'),('Penuelas','PR'),('Penwell','TX'),('Peoa','UT'),('Peoria','AZ'),('Peoria','IL'),('Peoria Heights','IL'),('Peosta','IA'),('Peotone','IL'),('Pep','NM'),('Pep','TX'),('Pepeekeo','HI'),('Pepin','WI'),('Pepperell','MA'),('Pequabuck','CT'),('Pequannock','NJ'),('Pequea','PA'),('Pequot Lakes','MN'),('Peralta','NM'),('Percival','IA'),('Percy','IL'),('Perdido','AL'),('Perdue Hill','AL'),('Perham','ME'),('Perham','MN'),('Peridot','AZ'),('Perkasie','PA'),('Perkins','GA'),('Perkins','MI'),('Perkins','MO'),('Perkins','OK'),('Perkinston','MS'),('Perkinsville','NY'),('Perkinsville','VT'),('Perkiomenville','PA'),('Perks','IL'),('Perley','MN'),('Perote','AL'),('Perrin','TX'),('Perrinton','MI'),('Perris','CA'),('Perronville','MI'),('Perry','AR'),('Perry','FL'),('Perry','GA'),('Perry','IA'),('Perry','IL'),('Perry','KS'),('Perry','LA'),('Perry','ME'),('Perry','MI'),('Perry','MO'),('Perry','NY'),('Perry','OH'),('Perry','OK'),('Perry Hall','MD'),('Perry Park','KY'),('Perry Point','MD'),('Perryman','MD'),('Perryopolis','PA'),('Perrysburg','NY'),('Perrysburg','OH'),('Perrysville','IN'),('Perrysville','OH'),('Perryton','TX'),('Perryville','AK'),('Perryville','AR'),('Perryville','KY'),('Perryville','MD'),('Perryville','MO'),('Pershing','IN'),('Persia','IA'),('Perth','ND'),('Perth Amboy','NJ'),('Peru','IA'),('Peru','IL'),('Peru','IN'),('Peru','KS'),('Peru','ME'),('Peru','NE'),('Peru','NY'),('Peru','VT'),('Pescadero','CA'),('Peshastin','WA'),('Peshtigo','WI'),('Pesotum','IL'),('Petaca','NM'),('Petal','MS'),('Petaluma','CA'),('Peterboro','NY'),('Peterborough','NH'),('Peterman','AL'),('Petersburg','AK'),('Petersburg','IL'),('Petersburg','IN'),('Petersburg','KY'),('Petersburg','MI'),('Petersburg','ND'),('Petersburg','NE'),('Petersburg','NY'),('Petersburg','OH'),('Petersburg','PA'),('Petersburg','TN'),('Petersburg','TX'),('Petersburg','VA'),('Petersburg','WV'),('Petersham','MA'),('Peterson','AL'),('Peterson','IA'),('Peterson','MN'),('Peterstown','WV'),('Petoskey','MI'),('Petrey','AL'),('Petrified Forest Natl Pk','AZ'),('Petroleum','IN'),('Petroleum','WV'),('Petrolia','CA'),('Petrolia','PA'),('Petrolia','TX'),('Petros','TN'),('Pettibone','ND'),('Pettigrew','AR'),('Pettisville','OH'),('Pettus','TX'),('Petty','TX'),('Pevely','MO'),('Pewamo','MI'),('Pewaukee','WI'),('Pewee Valley','KY'),('Peyton','CO'),('Peytona','WV'),('Pfafftown','NC'),('Pfeifer','KS'),('Pflugerville','TX'),('Pharr','TX'),('Pheba','MS'),('Phelan','CA'),('Phelps','KY'),('Phelps','NY'),('Phelps','WI'),('Phenix','VA'),('Phenix City','AL'),('Phil Campbell','AL'),('Philadelphia','MO'),('Philadelphia','MS'),('Philadelphia','NY'),('Philadelphia','PA'),('Philadelphia','TN'),('Philip','SD'),('Philipp','MS'),('Philippi','WV'),('Philipsburg','MT'),('Philipsburg','PA'),('Phillips','ME'),('Phillips','NE'),('Phillips','WI'),('Phillipsburg','KS'),('Phillipsburg','MO'),('Phillipsburg','NJ'),('Phillipsburg','OH'),('Phillipsport','NY'),('Phillipsville','CA'),('Philmont','NY'),('Philo','CA'),('Philo','IL'),('Philo','OH'),('Philomath','OR'),('Philomont','VA'),('Philpot','KY'),('Phippsburg','CO'),('Phippsburg','ME'),('Phlox','WI'),('Phoenicia','NY'),('Phoenix','AZ'),('Phoenix','MD'),('Phoenix','NY'),('Phoenix','OR'),('Phoenixville','PA'),('Phyllis','KY'),('Piasa','IL'),('Picabo','ID'),('Picacho','AZ'),('Picacho','NM'),('Picatinny Arsenal','NJ'),('Picayune','MS'),('Picher','OK'),('Pickens','AR'),('Pickens','MS'),('Pickens','OK'),('Pickens','SC'),('Pickens','WV'),('Pickerel','WI'),('Pickering','MO'),('Pickerington','OH'),('Pickett','WI'),('Pickford','MI'),('Pickrell','NE'),('Pickstown','SD'),('Pickton','TX'),('Pickwick Dam','TN'),('Pico Rivera','CA'),('Picture Rocks','PA'),('Pie Town','NM'),('Piedmont','AL'),('Piedmont','CA'),('Piedmont','KS'),('Piedmont','MO'),('Piedmont','OH'),('Piedmont','OK'),('Piedmont','SC'),('Piedmont','SD'),('Piedmont','WV'),('Piedra','CA'),('Pierce','CO'),('Pierce','ID'),('Pierce','NE'),('Pierce','TX'),('Pierce City','MO'),('Piercefield','NY'),('Pierceton','IN'),('Pierceville','IN'),('Pierceville','KS'),('Piercy','CA'),('Piermont','NH'),('Piermont','NY'),('Pierpont','OH'),('Pierpont','SD'),('Pierre','SD'),('Pierre Part','LA'),('Pierrepont Manor','NY'),('Pierron','IL'),('Pierson','FL'),('Pierson','IA'),('Pierson','MI'),('Pierz','MN'),('Piffard','NY'),('Pigeon','MI'),('Pigeon Falls','WI'),('Pigeon Forge','TN'),('Piggott','AR'),('Pike','NH'),('Pike','NY'),('Pike Road','AL'),('Pikesville','MD'),('Piketon','OH'),('Pikeville','KY'),('Pikeville','NC'),('Pikeville','TN'),('Pilger','NE'),('Pilgrim','KY'),('Pilgrims Knob','VA'),('Pillager','MN'),('Pillow','PA'),('Pillsbury','ND'),('Pilot','VA'),('Pilot Grove','IA'),('Pilot Grove','MO'),('Pilot Hill','CA'),('Pilot Knob','MO'),('Pilot Mound','IA'),('Pilot Mountain','NC'),('Pilot Point','AK'),('Pilot Point','TX'),('Pilot Rock','OR'),('Pilot Station','AK'),('Pilottown','LA'),('Pima','AZ'),('Pimento','IN'),('Pinch','WV'),('Pinckard','AL'),('Pinckney','MI'),('Pinckneyville','IL'),('Pinconning','MI'),('Pindall','AR'),('Pine','AZ'),('Pine','CO'),('Pine Apple','AL'),('Pine Beach','NJ'),('Pine Bluff','AR'),('Pine Bluffs','WY'),('Pine Brook','NJ'),('Pine Bush','NY'),('Pine City','MN'),('Pine City','NY'),('Pine Forge','PA'),('Pine Grove','CA'),('Pine Grove','LA'),('Pine Grove','PA'),('Pine Grove','WV'),('Pine Grove Mills','PA'),('Pine Hall','NC'),('Pine Hill','AL'),('Pine Hill','NY'),('Pine Island','MN'),('Pine Island','NY'),('Pine Knot','KY'),('Pine Lake','GA'),('Pine Level','AL'),('Pine Level','NC'),('Pine Meadow','CT'),('Pine Mountain','GA'),('Pine Mountain Valley','GA'),('Pine Plains','NY'),('Pine Prairie','LA'),('Pine Ridge','KY'),('Pine Ridge','SD'),('Pine River','MN'),('Pine River','WI'),('Pine Top','KY'),('Pine Valley','CA'),('Pine Valley','NY'),('Pine Valley','UT'),('Pine Village','IN'),('Pinebluff','NC'),('Pinecliffe','CO'),('Pinecrest','CA'),('Pinedale','AZ'),('Pinedale','WY'),('Pinehill','NM'),('Pinehurst','GA'),('Pinehurst','ID'),('Pinehurst','MA'),('Pinehurst','NC'),('Pinehurst','TX'),('Pineland','FL'),('Pineland','SC'),('Pineland','TX'),('Pinellas Park','FL'),('Pineola','NC'),('Pinesdale','MT'),('Pinetop','AZ'),('Pinetops','NC'),('Pinetown','NC'),('Pinetta','FL'),('Pineview','GA'),('Pineville','AR'),('Pineville','KY'),('Pineville','LA'),('Pineville','MO'),('Pineville','NC'),('Pineville','PA'),('Pineville','SC'),('Pineville','WV'),('Pinewood','SC'),('Piney Creek','NC'),('Piney Flats','TN'),('Piney Fork','OH'),('Piney Point','MD'),('Piney River','VA'),('Piney View','WV'),('Piney Woods','MS'),('Pingree','ID'),('Pingree','ND'),('Pink Hill','NC'),('Pinnacle','NC'),('Pinola','MS'),('Pinole','CA'),('Pinon','AZ'),('Pinon','NM'),('Pinon Hills','CA'),('Pinopolis','SC'),('Pinos Altos','NM'),('Pinson','AL'),('Pinson','TN'),('Pinsonfork','KY'),('Pinto','MD'),('Pioche','NV'),('Pioneer','CA'),('Pioneer','LA'),('Pioneer','OH'),('Pioneer','TN'),('Pioneertown','CA'),('Piper City','IL'),('Pipersville','PA'),('Pipestem','WV'),('Pipestone','MN'),('Pippa Passes','KY'),('Piqua','KS'),('Piqua','OH'),('Pirtleville','AZ'),('Piru','CA'),('Piscataway','NJ'),('Piseco','NY'),('Pisek','ND'),('Pisgah','AL'),('Pisgah','IA'),('Pisgah Forest','NC'),('Pismo Beach','CA'),('Pitcairn','PA'),('Pitcher','NY'),('Pitkin','CO'),('Pitkin','LA'),('Pitman','NJ'),('Pitman','PA'),('Pitsburg','OH'),('Pitts','GA'),('Pittsboro','IN'),('Pittsboro','MS'),('Pittsboro','NC'),('Pittsburg','CA'),('Pittsburg','IL'),('Pittsburg','KS'),('Pittsburg','KY'),('Pittsburg','MO'),('Pittsburg','NH'),('Pittsburg','OK'),('Pittsburg','TX'),('Pittsburgh','PA'),('Pittsfield','IL'),('Pittsfield','MA'),('Pittsfield','ME'),('Pittsfield','NH'),('Pittsfield','PA'),('Pittsfield','VT'),('Pittsford','MI'),('Pittsford','NY'),('Pittsford','VT'),('Pittston','PA'),('Pittstown','NJ'),('Pittsview','AL'),('Pittsville','MD'),('Pittsville','VA'),('Pittsville','WI'),('Pixley','CA'),('Placedo','TX'),('Placentia','CA'),('Placerville','CA'),('Placerville','CO'),('Placerville','ID'),('Placida','FL'),('Placitas','NM'),('Plain','WI'),('Plain City','OH'),('Plain Dealing','LA'),('Plainfield','CT'),('Plainfield','IA'),('Plainfield','IL'),('Plainfield','IN'),('Plainfield','MA'),('Plainfield','NH'),('Plainfield','NJ'),('Plainfield','OH'),('Plainfield','PA'),('Plainfield','VT'),('Plainfield','WI'),('Plains','GA'),('Plains','KS'),('Plains','MT'),('Plains','TX'),('Plainsboro','NJ'),('Plainview','AR'),('Plainview','MN'),('Plainview','NE'),('Plainview','NY'),('Plainview','TX'),('Plainville','CT'),('Plainville','GA'),('Plainville','IL'),('Plainville','IN'),('Plainville','KS'),('Plainville','MA'),('Plainville','NY'),('Plainwell','MI'),('Plaistow','NH'),('Planada','CA'),('Plankinton','SD'),('Plano','IA'),('Plano','IL'),('Plano','TX'),('Plant City','FL'),('Plantersville','AL'),('Plantersville','MS'),('Plantersville','TX'),('Plantsville','CT'),('Plaquemine','LA'),('Platina','CA'),('Platinum','AK'),('Plato','MN'),('Plato','MO'),('Plato Center','IL'),('Platte','SD'),('Platte Center','NE'),('Platte City','MO'),('Plattekill','NY'),('Plattenville','LA'),('Platter','OK'),('Platteville','CO'),('Platteville','WI'),('Plattsburg','MO'),('Plattsburgh','NY'),('Plattsmouth','NE'),('Plaucheville','LA'),('Playa Del Rey','CA'),('Playas','NM'),('Plaza','ND'),('Pleasant City','OH'),('Pleasant Dale','NE'),('Pleasant Garden','NC'),('Pleasant Grove','AL'),('Pleasant Grove','AR'),('Pleasant Grove','CA'),('Pleasant Grove','UT'),('Pleasant Hall','PA'),('Pleasant Hill','CA'),('Pleasant Hill','IL'),('Pleasant Hill','LA'),('Pleasant Hill','MO'),('Pleasant Hill','NC'),('Pleasant Hill','OH'),('Pleasant Hill','OR'),('Pleasant Hill','TN'),('Pleasant Hope','MO'),('Pleasant Lake','IN'),('Pleasant Lake','MI'),('Pleasant Mills','IN'),('Pleasant Mount','PA'),('Pleasant Plain','OH'),('Pleasant Plains','AR'),('Pleasant Plains','IL'),('Pleasant Prairie','WI'),('Pleasant Ridge','MI'),('Pleasant Shade','TN'),('Pleasant Unity','PA'),('Pleasant Valley','IA'),('Pleasant Valley','NY'),('Pleasant Valley','VA'),('Pleasant View','CO'),('Pleasant View','TN'),('Pleasanton','CA'),('Pleasanton','KS'),('Pleasanton','NE'),('Pleasanton','TX'),('Pleasantville','IA'),('Pleasantville','NJ'),('Pleasantville','NY'),('Pleasantville','OH'),('Pleasantville','PA'),('Pleasureville','KY'),('Pledger','TX'),('Plentywood','MT'),('Plessis','NY'),('Plevna','KS'),('Plevna','MO'),('Plevna','MT'),('Plover','IA'),('Plover','WI'),('Pluckemin','NJ'),('Plum','TX'),('Plum Branch','SC'),('Plum City','WI'),('Plumerville','AR'),('Plummer','ID'),('Plummer','MN'),('Plummers Landing','KY'),('Plumsteadville','PA'),('Plumtree','NC'),('Plumville','PA'),('Plush','OR'),('Plymouth','CA'),('Plymouth','CT'),('Plymouth','FL'),('Plymouth','IA'),('Plymouth','IL'),('Plymouth','IN'),('Plymouth','MA'),('Plymouth','ME'),('Plymouth','MI'),('Plymouth','NC'),('Plymouth','NE'),('Plymouth','NH'),('Plymouth','NY'),('Plymouth','OH'),('Plymouth','PA'),('Plymouth','UT'),('Plymouth','VT'),('Plymouth','WA'),('Plymouth','WI'),('Plymouth Meeting','PA'),('Plympton','MA'),('Poca','WV'),('Pocahontas','AR'),('Pocahontas','IA'),('Pocahontas','IL'),('Pocahontas','MO'),('Pocahontas','MS'),('Pocahontas','TN'),('Pocahontas','VA'),('Pocasset','MA'),('Pocasset','OK'),('Pocatello','ID'),('Pocola','OK'),('Pocomoke City','MD'),('Pocono Lake','PA'),('Pocono Lake Preserve','PA'),('Pocono Manor','PA'),('Pocono Pines','PA'),('Pocono Summit','PA'),('Pocopson','PA'),('Poestenkill','NY'),('Point','TX'),('Point Arena','CA'),('Point Baker','AK'),('Point Clear','AL'),('Point Comfort','TX'),('Point Harbor','NC'),('Point Hope','AK'),('Point Lay','AK'),('Point Lookout','MO'),('Point Lookout','NY'),('Point Marion','PA'),('Point Mugu Nawc','CA'),('Point Of Rocks','MD'),('Point Of Rocks','WY'),('Point Pleasant','PA'),('Point Pleasant','WV'),('Point Pleasant Beach','NJ'),('Point Reyes Station','CA'),('Point Roberts','WA'),('Point Washington','FL'),('Pointblank','TX'),('Pointe A La Hache','LA'),('Pointe Aux Pins','MI'),('Points','WV'),('Polacca','AZ'),('Poland','IN'),('Poland','ME'),('Poland','NY'),('Polaris','MT'),('Polebridge','MT'),('Polk','MO'),('Polk','NE'),('Polk','OH'),('Polk','PA'),('Polk City','FL'),('Polk City','IA'),('Polkton','NC'),('Polkville','NC'),('Pollard','AR'),('Pollock','ID'),('Pollock','LA'),('Pollock','MO'),('Pollock','SD'),('Pollock Pines','CA'),('Pollocksville','NC'),('Pollok','TX'),('Polo','IL'),('Polo','MO'),('Polson','MT'),('Polvadera','NM'),('Pomaria','SC'),('Pomerene','AZ'),('Pomeroy','IA'),('Pomeroy','OH'),('Pomeroy','PA'),('Pomeroy','WA'),('Pomfret','CT'),('Pomfret','MD'),('Pomfret Center','CT'),('Pomona','CA'),('Pomona','IL'),('Pomona','KS'),('Pomona','MO'),('Pomona','NJ'),('Pomona','NY'),('Pomona Park','FL'),('Pompano Beach','FL'),('Pompeii','MI'),('Pompey','NY'),('Pompeys Pillar','MT'),('Pompton Lakes','NJ'),('Pompton Plains','NJ'),('Ponca','AR'),('Ponca','NE'),('Ponca City','OK'),('Ponce','PR'),('Ponce De Leon','FL'),('Ponce De Leon','MO'),('Poncha Springs','CO'),('Ponchatoula','LA'),('Pond Creek','OK'),('Pond Eddy','NY'),('Pond Gap','WV'),('Ponder','TX'),('Ponderay','ID'),('Ponderosa','NM'),('Ponemah','MN'),('Poneto','IN'),('Ponsford','MN'),('Ponte Vedra','FL'),('Ponte Vedra Beach','FL'),('Pontiac','IL'),('Pontiac','MI'),('Pontiac','MO'),('Pontotoc','MS'),('Pontotoc','TX'),('Pony','MT'),('Pool','WV'),('Poole','KY'),('Pooler','GA'),('Poolesville','MD'),('Poolville','TX'),('Pope','MS'),('Pope A F B','NC'),('Pope Valley','CA'),('Popejoy','IA'),('Poplar','MT'),('Poplar','WI'),('Poplar Bluff','MO'),('Poplar Branch','NC'),('Poplar Grove','AR'),('Poplar Grove','IL'),('Poplar Ridge','NY'),('Poplarville','MS'),('Poquonock','CT'),('Poquoson','VA'),('Porcupine','SD'),('Port Alexander','AK'),('Port Allegany','PA'),('Port Allen','LA'),('Port Alsworth','AK'),('Port Angeles','WA'),('Port Aransas','TX'),('Port Arthur','TX'),('Port Austin','MI'),('Port Barre','LA'),('Port Bolivar','TX'),('Port Byron','IL'),('Port Byron','NY'),('Port Carbon','PA'),('Port Charlotte','FL'),('Port Chester','NY'),('Port Clinton','OH'),('Port Clinton','PA'),('Port Clyde','ME'),('Port Costa','CA'),('Port Crane','NY'),('Port Deposit','MD'),('Port Edwards','WI'),('Port Elizabeth','NJ'),('Port Ewen','NY'),('Port Gamble','WA'),('Port Gibson','MS'),('Port Gibson','NY'),('Port Hadlock','WA'),('Port Haywood','VA'),('Port Heiden','AK'),('Port Henry','NY'),('Port Hope','MI'),('Port Hueneme','CA'),('Port Hueneme Cbc Base','CA'),('Port Huron','MI'),('Port Isabel','TX'),('Port Jefferson','NY'),('Port Jefferson','OH'),('Port Jefferson Station','NY'),('Port Jervis','NY'),('Port Kent','NY'),('Port Lavaca','TX'),('Port Leyden','NY'),('Port Lions','AK'),('Port Ludlow','WA'),('Port Mansfield','TX'),('Port Matilda','PA'),('Port Monmouth','NJ'),('Port Murray','NJ'),('Port Neches','TX'),('Port Norris','NJ'),('Port O Connor','TX'),('Port Orange','FL'),('Port Orchard','WA'),('Port Orford','OR'),('Port Penn','DE'),('Port Reading','NJ'),('Port Republic','MD'),('Port Republic','NJ'),('Port Republic','VA'),('Port Richey','FL'),('Port Royal','KY'),('Port Royal','PA'),('Port Royal','SC'),('Port Royal','VA'),('Port Saint Joe','FL'),('Port Saint Lucie','FL'),('Port Salerno','FL'),('Port Sanilac','MI'),('Port Sulphur','LA'),('Port Tobacco','MD'),('Port Townsend','WA'),('Port Trevorton','PA'),('Port Washington','NY'),('Port Washington','OH'),('Port Washington','WI'),('Port William','OH'),('Port Wing','WI'),('Portage','IN'),('Portage','ME'),('Portage','MI'),('Portage','OH'),('Portage','PA'),('Portage','UT'),('Portage','WI'),('Portage Des Sioux','MO'),('Portageville','MO'),('Portageville','NY'),('Portal','GA'),('Portal','ND'),('Portales','NM'),('Porter','ME'),('Porter','MN'),('Porter','OK'),('Porter','TX'),('Porter Corners','NY'),('Porter Ranch','CA'),('Porterdale','GA'),('Porterfield','WI'),('Porters Falls','WV'),('Porters Sideling','PA'),('Portersville','PA'),('Porterville','CA'),('Porterville','MS'),('Porthill','ID'),('Portia','AR'),('Portis','KS'),('Portland','AR'),('Portland','CT'),('Portland','IN'),('Portland','ME'),('Portland','MI'),('Portland','MO'),('Portland','ND'),('Portland','NY'),('Portland','OH'),('Portland','OR'),('Portland','PA'),('Portland','TN'),('Portland','TX'),('Portlandville','NY'),('Portola','CA'),('Portola Vally','CA'),('Portsmouth','IA'),('Portsmouth','NH'),('Portsmouth','OH'),('Portsmouth','RI'),('Portsmouth','VA'),('Portville','NY'),('Porum','OK'),('Posen','IL'),('Posen','MI'),('Posey','CA'),('Poseyville','IN'),('Post','OR'),('Post','TX'),('Post Falls','ID'),('Post Mills','VT'),('Poston','AZ'),('Postville','IA'),('Poteau','OK'),('Potecasi','NC'),('Poteet','TX'),('Poth','TX'),('Potlatch','ID'),('Potomac','IL'),('Potomac','MD'),('Potosi','MO'),('Potosi','WI'),('Potrero','CA'),('Potsdam','NY'),('Potsdam','OH'),('Potter','KS'),('Potter','NE'),('Potter','WI'),('Potter Valley','CA'),('Pottersdale','PA'),('Pottersville','MO'),('Pottersville','NJ'),('Pottersville','NY'),('Potterville','MI'),('Potts Camp','MS'),('Potts Grove','PA'),('Pottsboro','TX'),('Pottstown','PA'),('Pottsville','AR'),('Pottsville','PA'),('Pottsville','TX'),('Potwin','KS'),('Poughkeepsie','AR'),('Poughkeepsie','NY'),('Poughquag','NY'),('Poulan','GA'),('Poulsbo','WA'),('Poultney','VT'),('Pound','VA'),('Pound','WI'),('Pound Ridge','NY'),('Pounding Mill','VA'),('Poway','CA'),('Powder River','WY'),('Powder Springs','GA'),('Powder Springs','TN'),('Powderhorn','CO'),('Powderly','KY'),('Powderly','TX'),('Powderville','MT'),('Powell','MO'),('Powell','OH'),('Powell','TN'),('Powell','TX'),('Powell','WY'),('Powell Butte','OR'),('Powells Point','NC'),('Powellsville','NC'),('Powellton','WV'),('Powellville','MD'),('Power','MT'),('Powers','MI'),('Powers','OR'),('Powers Lake','ND'),('Powers Lake','WI'),('Powersite','MO'),('Powersville','MO'),('Powhatan','AR'),('Powhatan','LA'),('Powhatan','VA'),('Powhatan Point','OH'),('Powhattan','KS'),('Pownal','ME'),('Pownal','VT'),('Poy Sippi','WI'),('Poyen','AR'),('Poynette','WI'),('Poynor','TX'),('Poyntelle','PA'),('Prague','NE'),('Prague','OK'),('Prairie','MS'),('Prairie City','IA'),('Prairie City','IL'),('Prairie City','OR'),('Prairie City','SD'),('Prairie Creek','IN'),('Prairie Du Chien','WI'),('Prairie Du Rocher','IL'),('Prairie Du Sac','WI'),('Prairie Farm','WI'),('Prairie Grove','AR'),('Prairie Hill','TX'),('Prairie Home','MO'),('Prairie Lea','TX'),('Prairie View','KS'),('Prairie View','TX'),('Prairie Village','KS'),('Prairieburg','IA'),('Prairieton','IN'),('Prairieville','LA'),('Prather','CA'),('Pratt','KS'),('Pratt','WV'),('Pratts','VA'),('Prattsburgh','NY'),('Prattsville','AR'),('Prattsville','NY'),('Prattville','AL'),('Pray','MT'),('Preble','IN'),('Preble','NY'),('Preemption','IL'),('Premier','WV'),('Premium','KY'),('Premont','TX'),('Prentice','WI'),('Prentiss','MS'),('Prescott','AR'),('Prescott','AZ'),('Prescott','IA'),('Prescott','KS'),('Prescott','MI'),('Prescott','WA'),('Prescott','WI'),('Prescott Valley','AZ'),('Presho','SD'),('Presidio','TX'),('Presque Isle','ME'),('Presque Isle','MI'),('Presque Isle','WI'),('Presto','PA'),('Preston','CT'),('Preston','GA'),('Preston','IA'),('Preston','ID'),('Preston','KY'),('Preston','MD'),('Preston','MN'),('Preston','MO'),('Preston','MS'),('Preston','OK'),('Preston','WA'),('Preston Hollow','NY'),('Preston Park','PA'),('Prestonsburg','KY'),('Pretty Prairie','KS'),('Prewitt','NM'),('Price','MD'),('Price','TX'),('Price','UT'),('Pricedale','PA'),('Prichard','WV'),('Priddy','TX'),('Pride','LA'),('Prides Crossing','MA'),('Priest River','ID'),('Prim','AR'),('Primghar','IA'),('Primm Springs','TN'),('Primrose','KY'),('Primrose','NE'),('Prince','WV'),('Prince Frederick','MD'),('Prince George','VA'),('Princess Anne','MD'),('Princeton','AL'),('Princeton','CA'),('Princeton','IA'),('Princeton','ID'),('Princeton','IL'),('Princeton','IN'),('Princeton','KS'),('Princeton','KY'),('Princeton','LA'),('Princeton','MA'),('Princeton','ME'),('Princeton','MN'),('Princeton','MO'),('Princeton','NC'),('Princeton','NJ'),('Princeton','OR'),('Princeton','TX'),('Princeton','WI'),('Princeton','WV'),('Princeville','HI'),('Princeville','IL'),('Princewick','WV'),('Prineville','OR'),('Pringle','SD'),('Prinsburg','MN'),('Printer','KY'),('Prior Lake','MN'),('Pritchett','CO'),('Proberta','CA'),('Procious','WV'),('Proctor','AR'),('Proctor','MT'),('Proctor','OK'),('Proctor','TX'),('Proctor','VT'),('Proctor','WV'),('Proctorsville','VT'),('Proctorville','NC'),('Proctorville','OH'),('Progreso','TX'),('Prole','IA'),('Promise City','IA'),('Prompton','PA'),('Prophetstown','IL'),('Prospect','CT'),('Prospect','KY'),('Prospect','NY'),('Prospect','OH'),('Prospect','OR'),('Prospect','PA'),('Prospect','TN'),('Prospect','VA'),('Prospect Harbor','ME'),('Prospect Heights','IL'),('Prospect Hill','NC'),('Prospect Park','PA'),('Prosper','TX'),('Prosperity','PA'),('Prosperity','SC'),('Prosperity','WV'),('Prosser','WA'),('Protection','KS'),('Protem','MO'),('Protivin','IA'),('Provencal','LA'),('Providence','KY'),('Providence','NC'),('Providence','RI'),('Providence','UT'),('Providence Forge','VA'),('Provincetown','MA'),('Provo','UT'),('Pruden','TN'),('Prudence Island','RI'),('Prudenville','MI'),('Prudhoe Bay','AK'),('Prue','OK'),('Pryor','MT'),('Pryor','OK'),('Puckett','MS'),('Pueblo','CO'),('Pueblo Of Acoma','NM'),('Puerto Real','PR'),('Pukalani','HI'),('Pukwana','SD'),('Pulaski','GA'),('Pulaski','IA'),('Pulaski','IL'),('Pulaski','MS'),('Pulaski','NY'),('Pulaski','PA'),('Pulaski','TN'),('Pulaski','VA'),('Pulaski','WI'),('Pullman','MI'),('Pullman','WA'),('Pullman','WV'),('Pulteney','NY'),('Pultneyville','NY'),('Pungoteague','VA'),('Punta Gorda','FL'),('Punta Santiago','PR'),('Punxsutawney','PA'),('Puposky','MN'),('Purcell','MO'),('Purcell','OK'),('Purcellville','VA'),('Purchase','NY'),('Purdin','MO'),('Purdon','TX'),('Purdum','NE'),('Purdy','MO'),('Purdys','NY'),('Purgitsville','WV'),('Purlear','NC'),('Purling','NY'),('Purmela','TX'),('Pursglove','WV'),('Purvis','MS'),('Puryear','TN'),('Put In Bay','OH'),('Putnam','CT'),('Putnam','IL'),('Putnam','OK'),('Putnam','TX'),('Putnam Hall','FL'),('Putnam Station','NY'),('Putnam Valley','NY'),('Putnamville','IN'),('Putney','GA'),('Putney','KY'),('Putney','VT'),('Puunene','HI'),('Puxico','MO'),('Puyallup','WA'),('Pyatt','AR'),('Pylesville','MD'),('Pyote','TX'),('Pyrites','NY'),('Quail','TX'),('Quakake','PA'),('Quaker City','OH'),('Quaker Hill','CT'),('Quaker Street','NY'),('Quakertown','NJ'),('Quakertown','PA'),('Quanah','TX'),('Quantico','MD'),('Quantico','VA'),('Quapaw','OK'),('Quarryville','PA'),('Quartzsite','AZ'),('Quasqueton','IA'),('Quay','NM'),('Quebeck','TN'),('Quebradillas','PR'),('Quechee','VT'),('Quecreek','PA'),('Queen','PA'),('Queen Anne','MD'),('Queen City','MO'),('Queen City','TX'),('Queen Creek','AZ'),('Queens Village','NY'),('Queensbury','NY'),('Queenstown','MD'),('Quemado','NM'),('Quemado','TX'),('Quenemo','KS'),('Quentin','PA'),('Questa','NM'),('Quicksburg','VA'),('Quilcene','WA'),('Quimby','IA'),('Quinault','WA'),('Quinby','VA'),('Quincy','CA'),('Quincy','FL'),('Quincy','IL'),('Quincy','IN'),('Quincy','KY'),('Quincy','MA'),('Quincy','MI'),('Quincy','MO'),('Quincy','OH'),('Quincy','PA'),('Quincy','WA'),('Quinebaug','CT'),('Quinhagak','AK'),('Quinlan','TX'),('Quinn','SD'),('Quinnesec','MI'),('Quinque','VA'),('Quinter','KS'),('Quinton','AL'),('Quinton','NJ'),('Quinton','OK'),('Quinton','VA'),('Quinwood','WV'),('Quitaque','TX'),('Quitman','AR'),('Quitman','GA'),('Quitman','LA'),('Quitman','MS'),('Quitman','TX'),('Qulin','MO'),('Quogue','NY'),('Rabun Gap','GA'),('Raccoon','KY'),('Raceland','LA'),('Rachel','WV'),('Racine','MN'),('Racine','MO'),('Racine','OH'),('Racine','WI'),('Racine','WV'),('Rackerby','CA'),('Radcliff','KY'),('Radcliffe','IA'),('Radersburg','MT'),('Radford','VA'),('Radiant','VA'),('Radisson','WI'),('Radium Springs','NM'),('Radnor','OH'),('Radom','IL'),('Raeford','NC'),('Ragan','NE'),('Ragland','AL'),('Ragland','WV'),('Ragley','LA'),('Ragsdale','IN'),('Rahway','NJ'),('Raiford','FL'),('Rail Road Flat','CA'),('Railroad','PA'),('Rainbow','TX'),('Rainbow Lake','NY'),('Rainelle','WV'),('Rainier','OR'),('Rainier','WA'),('Rains','SC'),('Rainsville','AL'),('Rainsville','NM'),('Raisin City','CA'),('Rake','IA'),('Raleigh','IL'),('Raleigh','MS'),('Raleigh','NC'),('Raleigh','ND'),('Raleigh','WV'),('Ralls','TX'),('Ralph','AL'),('Ralph','MI'),('Ralph','SD'),('Ralston','IA'),('Ralston','OK'),('Ralston','PA'),('Ralston','WY'),('Ramah','CO'),('Ramah','NM'),('Ramer','AL'),('Ramer','TN'),('Ramey','PA'),('Ramona','CA'),('Ramona','KS'),('Ramona','OK'),('Ramona','SD'),('Rampart','AK'),('Ramsay','MI'),('Ramsay','MT'),('Ramseur','NC'),('Ramsey','IL'),('Ramsey','IN'),('Ramsey','NJ'),('Ranburne','AL'),('Ranchester','WY'),('Ranchita','CA'),('Rancho Cordova','CA'),('Rancho Cucamonga','CA'),('Rancho Mirage','CA'),('Rancho Palos Verdes','CA'),('Rancho Santa Fe','CA'),('Rancho Santa Margarita','CA'),('Ranchos De Taos','NM'),('Rancocas','NJ'),('Rand','CO'),('Randalia','IA'),('Randall','IA'),('Randall','KS'),('Randall','MN'),('Randallstown','MD'),('Randle','WA'),('Randleman','NC'),('Randlett','OK'),('Randlett','UT'),('Randolph','AL'),('Randolph','IA'),('Randolph','KS'),('Randolph','MA'),('Randolph','ME'),('Randolph','MN'),('Randolph','MS'),('Randolph','NE'),('Randolph','NH'),('Randolph','NJ'),('Randolph','NY'),('Randolph','OH'),('Randolph','TX'),('Randolph','UT'),('Randolph','VA'),('Randolph','VT'),('Randolph','WI'),('Randolph Center','VT'),('Random Lake','WI'),('Randsburg','CA'),('Range','AL'),('Rangeley','ME'),('Rangely','CO'),('Ranger','GA'),('Ranger','TX'),('Ranger','WV'),('Ranier','MN'),('Rankin','IL'),('Rankin','TX'),('Ransom','IL'),('Ransom','KS'),('Ransom','KY'),('Ransom','PA'),('Ransom Canyon','TX'),('Ransomville','NY'),('Ranson','WV'),('Rantoul','IL'),('Rantoul','KS'),('Rapelje','MT'),('Raphine','VA'),('Rapid City','MI'),('Rapid City','SD'),('Rapid River','MI'),('Rapidan','VA'),('Rapids City','IL'),('Rappahannock Academy','VA'),('Raquette Lake','NY'),('Rarden','OH'),('Raritan','IL'),('Raritan','NJ'),('Ratcliff','AR'),('Ratcliff','TX'),('Rathdrum','ID'),('Ratliff City','OK'),('Raton','NM'),('Rattan','OK'),('Ravalli','MT'),('Raven','KY'),('Raven','VA'),('Ravena','NY'),('Ravencliff','WV'),('Ravendale','CA'),('Ravenden','AR'),('Ravenden Springs','AR'),('Ravenel','SC'),('Ravenna','KY'),('Ravenna','MI'),('Ravenna','NE'),('Ravenna','OH'),('Ravenna','TX'),('Ravensdale','WA'),('Ravenswood','WV'),('Ravenwood','MO'),('Ravia','OK'),('Ravine','PA'),('Rawl','WV'),('Rawlings','MD'),('Rawlings','VA'),('Rawlins','WY'),('Rawson','OH'),('Ray','MI'),('Ray','ND'),('Ray','OH'),('Ray Brook','NY'),('Ray City','GA'),('Rayland','OH'),('Rayle','GA'),('Raymond','CA'),('Raymond','IA'),('Raymond','IL'),('Raymond','KS'),('Raymond','ME'),('Raymond','MN'),('Raymond','MS'),('Raymond','MT'),('Raymond','NE'),('Raymond','NH'),('Raymond','OH'),('Raymond','SD'),('Raymond','WA'),('Raymondville','MO'),('Raymondville','NY'),('Raymondville','TX'),('Raymore','MO'),('Rayne','LA'),('Raynesford','MT'),('Raynham','MA'),('Raynham Center','MA'),('Raysal','WV'),('Rayville','LA'),('Rayville','MO'),('Raywick','KY'),('Raywood','TX'),('Rea','MO'),('Reader','WV'),('Readfield','ME'),('Readfield','WI'),('Reading','KS'),('Reading','MA'),('Reading','MI'),('Reading','MN'),('Reading','PA'),('Reading','VT'),('Reading Center','NY'),('Readington','NJ'),('Readlyn','IA'),('Reads Landing','MN'),('Readsboro','VT'),('Readstown','WI'),('Readville','MA'),('Readyville','TN'),('Reagan','TN'),('Reagan','TX'),('Realitos','TX'),('Reamstown','PA'),('Reardan','WA'),('Reasnor','IA'),('Rebecca','GA'),('Rebersburg','PA'),('Rebuck','PA'),('Recluse','WY'),('Rector','AR'),('Rector','PA'),('Rectortown','VA'),('Red Ash','VA'),('Red Bank','NJ'),('Red Banks','MS'),('Red Bay','AL'),('Red Bluff','CA'),('Red Boiling Springs','TN'),('Red Bud','IL'),('Red Cliff','CO'),('Red Cloud','NE'),('Red Creek','NY'),('Red Creek','WV'),('Red Devil','AK'),('Red Feather Lakes','CO'),('Red Hill','PA'),('Red Hook','NY'),('Red House','VA'),('Red House','WV'),('Red Jacket','WV'),('Red Lake Falls','MN'),('Red Level','AL'),('Red Lion','PA'),('Red Lodge','MT'),('Red Mountain','CA'),('Red Oak','GA'),('Red Oak','IA'),('Red Oak','NC'),('Red Oak','OK'),('Red Oak','TX'),('Red Oak','VA'),('Red River','NM'),('Red Rock','AZ'),('Red Rock','OK'),('Red Rock','TX'),('Red Springs','NC'),('Red Valley','AZ'),('Red Wing','MN'),('Redan','GA'),('Redbird','OK'),('Redby','MN'),('Redcrest','CA'),('Reddell','LA'),('Reddick','FL'),('Reddick','IL'),('Redding','CA'),('Redding','CT'),('Redding','IA'),('Redding Center','CT'),('Redding Ridge','CT'),('Redfield','AR'),('Redfield','IA'),('Redfield','KS'),('Redfield','NY'),('Redfield','SD'),('Redford','MI'),('Redford','MO'),('Redford','NY'),('Redford','TX'),('Redfox','KY'),('Redgranite','WI'),('Redig','SD'),('Redkey','IN'),('Redlake','MN'),('Redlands','CA'),('Redmon','IL'),('Redmond','OR'),('Redmond','UT'),('Redmond','WA'),('Redondo','WA'),('Redondo Beach','CA'),('Redrock','NM'),('Redstar','WV'),('Redstone','MT'),('Redvale','CO'),('Redwater','TX'),('Redway','CA'),('Redwood','MS'),('Redwood','NY'),('Redwood','VA'),('Redwood City','CA'),('Redwood Estates','CA'),('Redwood Falls','MN'),('Redwood Valley','CA'),('Ree Heights','SD'),('Reed','KY'),('Reed City','MI'),('Reed Point','MT'),('Reeder','ND'),('Reeders','PA'),('Reedley','CA'),('Reeds','MO'),('Reeds Spring','MO'),('Reedsburg','WI'),('Reedsport','OR'),('Reedsville','OH'),('Reedsville','PA'),('Reedsville','WI'),('Reedsville','WV'),('Reedville','VA'),('Reedy','WV'),('Reelsville','IN'),('Reese','MI'),('Reeseville','WI'),('Reesville','OH'),('Reeves','LA'),('Reevesville','SC'),('Reform','AL'),('Refton','PA'),('Refugio','TX'),('Regan','ND'),('Regent','ND'),('Regina','KY'),('Regina','NM'),('Register','GA'),('Rego Park','NY'),('Rehobeth','MD'),('Rehoboth','MA'),('Rehoboth','NM'),('Rehoboth Beach','DE'),('Rehrersburg','PA'),('Reidsville','GA'),('Reidsville','NC'),('Reidville','SC'),('Reinbeck','IA'),('Reinholds','PA'),('Reisterstown','MD'),('Reklaw','TX'),('Reliance','SD'),('Reliance','TN'),('Reliance','WY'),('Rembert','SC'),('Rembrandt','IA'),('Remer','MN'),('Remington','IN'),('Remington','VA'),('Remlap','AL'),('Remsen','IA'),('Remsen','NY'),('Remsenburg','NY'),('Remus','MI'),('Rena Lara','MS'),('Renault','IL'),('Renfrew','PA'),('Renfro Valley','KY'),('Renick','MO'),('Renick','WV'),('Renner','SD'),('Reno','NV'),('Reno','OH'),('Reno','PA'),('Renovo','PA'),('Rensselaer','IN'),('Rensselaer','NY'),('Rensselaer Falls','NY'),('Rensselaerville','NY'),('Rentiesville','OK'),('Renton','WA'),('Rentz','GA'),('Renville','MN'),('Renwick','IA'),('Represa','CA'),('Repton','AL'),('Republic','KS'),('Republic','MI'),('Republic','MO'),('Republic','OH'),('Republic','PA'),('Republic','WA'),('Republican City','NE'),('Resaca','GA'),('Rescue','CA'),('Rescue','VA'),('Research Triangle Park','NC'),('Reseda','CA'),('Reserve','LA'),('Reserve','MT'),('Reserve','NM'),('Reston','VA'),('Retsil','WA'),('Retsof','NY'),('Reubens','ID'),('Reva','SD'),('Reva','VA'),('Revelo','KY'),('Revere','MA'),('Revere','MN'),('Revere','MO'),('Revere','PA'),('Revillo','SD'),('Revloc','PA'),('Rew','PA'),('Rewey','WI'),('Rex','GA'),('Rex','NC'),('Rexburg','ID'),('Rexford','KS'),('Rexford','MT'),('Rexford','NY'),('Rexmont','PA'),('Rexville','NY'),('Reydell','AR'),('Reydon','OK'),('Reyno','AR'),('Reynolds','GA'),('Reynolds','IL'),('Reynolds','IN'),('Reynolds','MO'),('Reynolds','ND'),('Reynolds','NE'),('Reynolds Station','KY'),('Reynoldsburg','OH'),('Reynoldsville','PA'),('Reynoldsville','WV'),('Rhame','ND'),('Rheems','PA'),('Rhine','GA'),('Rhinebeck','NY'),('Rhinecliff','NY'),('Rhinehart','LA'),('Rhineland','MO'),('Rhinelander','WI'),('Rhoadesville','VA'),('Rhodelia','KY'),('Rhodell','WV'),('Rhodes','IA'),('Rhodes','MI'),('Rhodesdale','MD'),('Rhodhiss','NC'),('Rhododendron','OR'),('Rhome','TX'),('Rialto','CA'),('Rib Lake','WI'),('Ribera','NM'),('Rice','MN'),('Rice','TX'),('Rice','VA'),('Rice','WA'),('Rice Lake','WI'),('Riceboro','GA'),('Rices Landing','PA'),('Ricetown','KY'),('Riceville','IA'),('Riceville','PA'),('Riceville','TN'),('Rich Creek','VA'),('Rich Hill','MO'),('Rich Square','NC'),('Richards','MO'),('Richards','TX'),('Richardson','TX'),('Richardsville','VA'),('Richardton','ND'),('Richboro','PA'),('Richburg','NY'),('Richburg','SC'),('Richey','MT'),('Richeyville','PA'),('Richfield','ID'),('Richfield','KS'),('Richfield','NC'),('Richfield','OH'),('Richfield','PA'),('Richfield','UT'),('Richfield','WI'),('Richfield Springs','NY'),('Richford','NY'),('Richford','VT'),('Richgrove','CA'),('Richland','GA'),('Richland','IA'),('Richland','IN'),('Richland','MI'),('Richland','MO'),('Richland','MS'),('Richland','MT'),('Richland','NJ'),('Richland','NY'),('Richland','OR'),('Richland','PA'),('Richland','SC'),('Richland','TX'),('Richland','WA'),('Richland Center','WI'),('Richland Springs','TX'),('Richlands','NC'),('Richlands','VA'),('Richlandtown','PA'),('Richmond','CA'),('Richmond','IL'),('Richmond','IN'),('Richmond','KS'),('Richmond','KY'),('Richmond','MA'),('Richmond','ME'),('Richmond','MI'),('Richmond','MN'),('Richmond','MO'),('Richmond','OH'),('Richmond','TX'),('Richmond','UT'),('Richmond','VA'),('Richmond','VT'),('Richmond Dale','OH'),('Richmond Hill','GA'),('Richmond Hill','NY'),('Richmondville','NY'),('Richton','MS'),('Richton Park','IL'),('Richvale','CA'),('Richview','IL'),('Richville','MI'),('Richville','MN'),('Richville','NY'),('Richwood','MN'),('Richwood','NJ'),('Richwood','OH'),('Richwood','WV'),('Richwoods','MO'),('Ricketts','IA'),('Rickman','TN'),('Rickreall','OR'),('Rico','CO'),('Riddle','OR'),('Riddlesburg','PA'),('Riddleton','TN'),('Riderwood','MD'),('Ridge','MD'),('Ridge','NY'),('Ridge Farm','IL'),('Ridge Spring','SC'),('Ridgecrest','CA'),('Ridgecrest','NC'),('Ridgedale','MO'),('Ridgefield','CT'),('Ridgefield','NJ'),('Ridgefield','WA'),('Ridgefield Park','NJ'),('Ridgeland','MS'),('Ridgeland','SC'),('Ridgeland','WI'),('Ridgeley','WV'),('Ridgely','MD'),('Ridgely','TN'),('Ridgetop','TN'),('Ridgeview','SD'),('Ridgeview','WV'),('Ridgeville','IN'),('Ridgeville','SC'),('Ridgeville Corners','OH'),('Ridgeway','IA'),('Ridgeway','MO'),('Ridgeway','NC'),('Ridgeway','OH'),('Ridgeway','SC'),('Ridgeway','VA'),('Ridgeway','WI'),('Ridgeway','WV'),('Ridgewood','NJ'),('Ridgewood','NY'),('Ridgway','CO'),('Ridgway','IL'),('Ridgway','PA'),('Ridley Park','PA'),('Ridott','IL'),('Riegelsville','PA'),('Riegelwood','NC'),('Rienzi','MS'),('Riesel','TX'),('Rifle','CO'),('Rifton','NY'),('Riga','MI'),('Rigby','ID'),('Riggins','ID'),('Riley','IN'),('Riley','KS'),('Riley','OR'),('Rileyville','VA'),('Rillito','AZ'),('Rillton','PA'),('Rimersburg','PA'),('Rimforest','CA'),('Rimrock','AZ'),('Rinard','IL'),('Rincon','GA'),('Rincon','NM'),('Rincon','PR'),('Rindge','NH'),('Riner','VA'),('Rineyville','KY'),('Ringgold','GA'),('Ringgold','LA'),('Ringgold','PA'),('Ringgold','TX'),('Ringgold','VA'),('Ringle','WI'),('Ringling','MT'),('Ringling','OK'),('Ringoes','NJ'),('Ringold','OK'),('Ringsted','IA'),('Ringtown','PA'),('Ringwood','IL'),('Ringwood','NJ'),('Ringwood','OK'),('Rio','IL'),('Rio','WI'),('Rio','WV'),('Rio Blanco','PR'),('Rio Dell','CA'),('Rio Frio','TX'),('Rio Grande','NJ'),('Rio Grande','OH'),('Rio Grande','PR'),('Rio Grande City','TX'),('Rio Hondo','TX'),('Rio Linda','CA'),('Rio Medina','TX'),('Rio Nido','CA'),('Rio Oso','CA'),('Rio Rancho','NM'),('Rio Rico','AZ'),('Rio Verde','AZ'),('Rio Vista','CA'),('Rio Vista','TX'),('Rion','SC'),('Riparius','NY'),('Ripley','MS'),('Ripley','NY'),('Ripley','OH'),('Ripley','OK'),('Ripley','TN'),('Ripley','WV'),('Ripon','CA'),('Ripon','WI'),('Rippey','IA'),('Ripplemead','VA'),('Rippon','WV'),('Ripton','VT'),('Ririe','ID'),('Risco','MO'),('Rising City','NE'),('Rising Fawn','GA'),('Rising Star','TX'),('Rising Sun','IN'),('Rising Sun','MD'),('Risingsun','OH'),('Rison','AR'),('Rittman','OH'),('Ritzville','WA'),('Riva','MD'),('River','KY'),('River Edge','NJ'),('River Falls','AL'),('River Falls','WI'),('River Forest','IL'),('River Grove','IL'),('River Pines','CA'),('River Ranch','FL'),('River Rouge','MI'),('Riverbank','CA'),('Riverdale','CA'),('Riverdale','GA'),('Riverdale','IL'),('Riverdale','MD'),('Riverdale','MI'),('Riverdale','ND'),('Riverdale','NE'),('Riverdale','NJ'),('Riverhead','NY'),('Riverside','AL'),('Riverside','CA'),('Riverside','CT'),('Riverside','IA'),('Riverside','IL'),('Riverside','MI'),('Riverside','MO'),('Riverside','OR'),('Riverside','PA'),('Riverside','RI'),('Riverside','TX'),('Riverside','UT'),('Riverside','WA'),('Riverton','CT'),('Riverton','IA'),('Riverton','IL'),('Riverton','KS'),('Riverton','NE'),('Riverton','NJ'),('Riverton','UT'),('Riverton','WV'),('Riverton','WY'),('Rivervale','AR'),('Riverview','FL'),('Riverview','MI'),('Rives','MO'),('Rives','TN'),('Rives Junction','MI'),('Rivesville','WV'),('Riviera','TX'),('Rixeyville','VA'),('Rixford','PA'),('Roach','MO'),('Roachdale','IN'),('Roan Mountain','TN'),('Roann','IN'),('Roanoke','AL'),('Roanoke','IL'),('Roanoke','IN'),('Roanoke','LA'),('Roanoke','TX'),('Roanoke','VA'),('Roanoke Rapids','NC'),('Roans Prairie','TX'),('Roaring Branch','PA'),('Roaring Gap','NC'),('Roaring River','NC'),('Roaring Spring','PA'),('Roaring Springs','TX'),('Roark','KY'),('Robards','KY'),('Robbins','CA'),('Robbins','IL'),('Robbins','NC'),('Robbins','TN'),('Robbinston','ME'),('Robbinsville','NC'),('Robeline','LA'),('Robersonville','NC'),('Robert','LA'),('Robert Lee','TX'),('Roberta','GA'),('Roberts','ID'),('Roberts','IL'),('Roberts','MT'),('Roberts','WI'),('Robertsdale','AL'),('Robertsdale','PA'),('Robertson','WY'),('Robertsville','MO'),('Robertsville','OH'),('Robesonia','PA'),('Robins','IA'),('Robinson','IL'),('Robinson','KS'),('Robinson','ND'),('Robinson','PA'),('Robinson Creek','KY'),('Robinsonville','MS'),('Robson','WV'),('Robstown','TX'),('Roby','MO'),('Roby','TX'),('Roca','NE'),('Rochdale','MA'),('Rochelle','GA'),('Rochelle','IL'),('Rochelle','TX'),('Rochelle','VA'),('Rochelle Park','NJ'),('Rocheport','MO'),('Rochert','MN'),('Rochester','IL'),('Rochester','IN'),('Rochester','KY'),('Rochester','MA'),('Rochester','MI'),('Rochester','MN'),('Rochester','NH'),('Rochester','NY'),('Rochester','PA'),('Rochester','TX'),('Rochester','VT'),('Rochester','WA'),('Rochester','WI'),('Rochester Mills','PA'),('Rociada','NM'),('Rock','KS'),('Rock','MI'),('Rock','WV'),('Rock Camp','OH'),('Rock Cave','WV'),('Rock City','IL'),('Rock City Falls','NY'),('Rock Creek','MN'),('Rock Creek','OH'),('Rock Creek','WV'),('Rock Falls','IA'),('Rock Falls','IL'),('Rock Falls','WI'),('Rock Glen','PA'),('Rock Hall','MD'),('Rock Hill','NY'),('Rock Hill','SC'),('Rock Island','IL'),('Rock Island','TN'),('Rock Island','TX'),('Rock Island','WA'),('Rock Point','AZ'),('Rock Point','MD'),('Rock Port','MO'),('Rock Rapids','IA'),('Rock River','WY'),('Rock Spring','GA'),('Rock Springs','WI'),('Rock Springs','WY'),('Rock Stream','NY'),('Rock Tavern','NY'),('Rock Valley','IA'),('Rock View','WV'),('Rockaway','NJ'),('Rockaway Beach','MO'),('Rockaway Beach','OR'),('Rockaway Park','NY'),('Rockbridge','IL'),('Rockbridge','MO'),('Rockbridge','OH'),('Rockbridge Baths','VA'),('Rockdale','TX'),('Rockfall','CT'),('Rockfield','IN'),('Rockfield','KY'),('Rockford','AL'),('Rockford','IA'),('Rockford','IL'),('Rockford','MI'),('Rockford','MN'),('Rockford','OH'),('Rockford','TN'),('Rockford','WA'),('Rockham','SD'),('Rockhill Furnace','PA'),('Rockholds','KY'),('Rockhouse','KY'),('Rockingham','NC'),('Rocklake','ND'),('Rockland','DE'),('Rockland','ID'),('Rockland','MA'),('Rockland','ME'),('Rockland','MI'),('Rockland','WI'),('Rockledge','FL'),('Rockledge','GA'),('Rocklin','CA'),('Rockmart','GA'),('Rockport','IL'),('Rockport','IN'),('Rockport','KY'),('Rockport','MA'),('Rockport','ME'),('Rockport','TX'),('Rockport','WA'),('Rockport','WV'),('Rocksprings','TX'),('Rockton','IL'),('Rockton','PA'),('Rockvale','CO'),('Rockvale','TN'),('Rockville','IN'),('Rockville','MD'),('Rockville','MN'),('Rockville','MO'),('Rockville','NE'),('Rockville','RI'),('Rockville','UT'),('Rockville','VA'),('Rockville Centre','NY'),('Rockwall','TX'),('Rockwell','IA'),('Rockwell','NC'),('Rockwell City','IA'),('Rockwood','IL'),('Rockwood','ME'),('Rockwood','MI'),('Rockwood','PA'),('Rockwood','TN'),('Rockwood','TX'),('Rocky','OK'),('Rocky Comfort','MO'),('Rocky Face','GA'),('Rocky Ford','CO'),('Rocky Ford','GA'),('Rocky Gap','VA'),('Rocky Hill','CT'),('Rocky Hill','KY'),('Rocky Hill','NJ'),('Rocky Mount','MO'),('Rocky Mount','NC'),('Rocky Mount','VA'),('Rocky Point','NC'),('Rocky Point','NY'),('Rocky Ridge','MD'),('Rocky Ridge','OH'),('Rocky River','OH'),('Rodanthe','NC'),('Rodeo','CA'),('Rodeo','NM'),('Roderfield','WV'),('Rodessa','LA'),('Rodman','NY'),('Rodney','IA'),('Rodney','MI'),('Roduco','NC'),('Roe','AR'),('Roebling','NJ'),('Roebuck','SC'),('Roff','OK'),('Rogers','AR'),('Rogers','CT'),('Rogers','KY'),('Rogers','MN'),('Rogers','ND'),('Rogers','NE'),('Rogers','NM'),('Rogers','OH'),('Rogers','TX'),('Rogers City','MI'),('Rogerson','ID'),('Rogersville','AL'),('Rogersville','MO'),('Rogersville','PA'),('Rogersville','TN'),('Roggen','CO'),('Rogue River','OR'),('Rohnert Park','CA'),('Rohrersville','MD'),('Roland','AR'),('Roland','IA'),('Roland','OK'),('Rolesville','NC'),('Rolette','ND'),('Rolfe','IA'),('Roll','AZ'),('Rolla','KS'),('Rolla','MO'),('Rolla','ND'),('Rolling Fork','MS'),('Rolling Meadows','IL'),('Rolling Prairie','IN'),('Rollingbay','WA'),('Rollingstone','MN'),('Rollins','MT'),('Rollins Fork','VA'),('Rollinsford','NH'),('Rollinsville','CO'),('Roma','TX'),('Romance','AR'),('Romayor','TX'),('Rombauer','MO'),('Rome','GA'),('Rome','IA'),('Rome','IL'),('Rome','IN'),('Rome','MS'),('Rome','NY'),('Rome','OH'),('Rome','PA'),('Rome City','IN'),('Romeo','CO'),('Romeo','MI'),('Romeoville','IL'),('Romney','IN'),('Romney','WV'),('Romulus','MI'),('Romulus','NY'),('Ronald','WA'),('Ronan','MT'),('Ronceverte','WV'),('Ronco','PA'),('Ronda','NC'),('Ronkonkoma','NY'),('Ronks','PA'),('Roodhouse','IL'),('Roopville','GA'),('Roosevelt','AZ'),('Roosevelt','MN'),('Roosevelt','NJ'),('Roosevelt','NY'),('Roosevelt','OK'),('Roosevelt','TX'),('Roosevelt','UT'),('Roosevelt','WA'),('Roosevelt Roads','PR'),('Rooseveltown','NY'),('Rootstown','OH'),('Roper','NC'),('Ropesville','TX'),('Rosalia','KS'),('Rosalia','WA'),('Rosalie','NE'),('Rosamond','CA'),('Rosamond','IL'),('Rosanky','TX'),('Rosario','PR'),('Rosburg','WA'),('Roscoe','IL'),('Roscoe','MN'),('Roscoe','MO'),('Roscoe','MT'),('Roscoe','NY'),('Roscoe','PA'),('Roscoe','SD'),('Roscoe','TX'),('Roscommon','MI'),('Rose','NY'),('Rose','OK'),('Rose Bud','AR'),('Rose City','MI'),('Rose Creek','MN'),('Rose Hill','IA'),('Rose Hill','KS'),('Rose Hill','MS'),('Rose Hill','NC'),('Rose Hill','VA'),('Rose Lodge','OR'),('Roseau','MN'),('Roseboom','NY'),('Roseboro','NC'),('Rosebud','MO'),('Rosebud','MT'),('Rosebud','SD'),('Rosebud','TX'),('Roseburg','OR'),('Rosebush','MI'),('Rosedale','IN'),('Rosedale','LA'),('Rosedale','MD'),('Rosedale','MS'),('Rosedale','NY'),('Rosedale','VA'),('Rosedale','WV'),('Roseglen','ND'),('Roseland','FL'),('Roseland','LA'),('Roseland','NE'),('Roseland','NJ'),('Roseland','VA'),('Roselawn','IN'),('Roselle','IL'),('Roselle','NJ'),('Roselle Park','NJ'),('Rosemary Beach','FL'),('Rosemead','CA'),('Rosemont','NJ'),('Rosemont','WV'),('Rosemount','MN'),('Rosenberg','TX'),('Rosendale','MO'),('Rosendale','NY'),('Rosendale','WI'),('Rosenhayn','NJ'),('Rosepine','LA'),('Roseville','CA'),('Roseville','IL'),('Roseville','MI'),('Roseville','OH'),('Rosewood','OH'),('Rosharon','TX'),('Rosholt','SD'),('Rosholt','WI'),('Rosiclare','IL'),('Rosie','AR'),('Rosine','KY'),('Roslindale','MA'),('Roslyn','NY'),('Roslyn','SD'),('Roslyn','WA'),('Roslyn Heights','NY'),('Rosman','NC'),('Ross','CA'),('Ross','ND'),('Ross','OH'),('Ross','TX'),('Rossburg','OH'),('Rosser','TX'),('Rossford','OH'),('Rossiter','PA'),('Rosston','AR'),('Rosston','OK'),('Rosston','TX'),('Rossville','GA'),('Rossville','IL'),('Rossville','IN'),('Rossville','KS'),('Rossville','PA'),('Rossville','TN'),('Roswell','GA'),('Roswell','NM'),('Rotan','TX'),('Rothbury','MI'),('Rothsay','MN'),('Rothschild','WI'),('Rothville','MO'),('Rotonda West','FL'),('Rotterdam Junction','NY'),('Rougemont','NC'),('Rough And Ready','CA'),('Rougon','LA'),('Roulette','PA'),('Round Hill','VA'),('Round Lake','IL'),('Round Lake','MN'),('Round Lake','NY'),('Round Mountain','CA'),('Round Mountain','NV'),('Round Mountain','TX'),('Round O','SC'),('Round Pond','ME'),('Round Rock','AZ'),('Round Rock','TX'),('Round Top','NY'),('Round Top','TX'),('Roundhead','OH'),('Roundhill','KY'),('Roundup','MT'),('Rouses Point','NY'),('Rouseville','PA'),('Rousseau','KY'),('Rouzerville','PA'),('Rover','AR'),('Rowan','IA'),('Rowdy','KY'),('Rowe','MA'),('Rowe','NM'),('Rowe','VA'),('Rowena','SD'),('Rowena','TX'),('Rowesville','SC'),('Rowland','NC'),('Rowland','PA'),('Rowland Heights','CA'),('Rowlesburg','WV'),('Rowlett','TX'),('Rowley','IA'),('Rowley','MA'),('Roxana','IL'),('Roxana','KY'),('Roxboro','NC'),('Roxbury','CT'),('Roxbury','KS'),('Roxbury','ME'),('Roxbury','NY'),('Roxbury','PA'),('Roxbury','VT'),('Roxie','MS'),('Roxobel','NC'),('Roxton','TX'),('Roy','MT'),('Roy','NM'),('Roy','UT'),('Roy','WA'),('Royal','AR'),('Royal','IA'),('Royal','IL'),('Royal','NE'),('Royal Center','IN'),('Royal City','WA'),('Royal Oak','MD'),('Royal Oak','MI'),('Royalston','MA'),('Royalton','IL'),('Royalton','KY'),('Royalton','MN'),('Royersford','PA'),('Royse City','TX'),('Royston','GA'),('Rozel','KS'),('Rozet','WY'),('Rubicon','WI'),('Ruby','AK'),('Ruby','LA'),('Ruby','NY'),('Ruby','SC'),('Ruby','VA'),('Ruby Valley','NV'),('Ruckersville','VA'),('Rudd','IA'),('Rudolph','OH'),('Rudolph','WI'),('Rudy','AR'),('Rudyard','MI'),('Rudyard','MT'),('Rueter','MO'),('Rufe','OK'),('Ruffin','NC'),('Ruffin','SC'),('Ruffs Dale','PA'),('Rufus','OR'),('Rugby','ND'),('Rugby','TN'),('Ruidoso','NM'),('Ruidoso Downs','NM'),('Rule','TX'),('Ruleville','MS'),('Rulo','NE'),('Rumely','MI'),('Rumford','ME'),('Rumford','RI'),('Rumford Center','ME'),('Rumney','NH'),('Rumsey','CA'),('Rumsey','KY'),('Rumson','NJ'),('Runge','TX'),('Runnells','IA'),('Runnemede','NJ'),('Running Springs','CA'),('Rupert','GA'),('Rupert','ID'),('Rupert','VT'),('Rupert','WV'),('Rural Hall','NC'),('Rural Retreat','VA'),('Rural Ridge','PA'),('Rural Valley','PA'),('Rush','CO'),('Rush','KY'),('Rush','NY'),('Rush Center','KS'),('Rush City','MN'),('Rush Hill','MO'),('Rush Springs','OK'),('Rush Valley','UT'),('Rushford','MN'),('Rushford','NY'),('Rushland','PA'),('Rushmore','MN'),('Rushsylvania','OH'),('Rushville','IL'),('Rushville','IN'),('Rushville','MO'),('Rushville','NE'),('Rushville','NY'),('Rushville','OH'),('Rusk','TX'),('Ruskin','FL'),('Ruskin','NE'),('Ruso','ND'),('Russell','AR'),('Russell','IA'),('Russell','IL'),('Russell','KS'),('Russell','KY'),('Russell','MA'),('Russell','MN'),('Russell','NY'),('Russell','PA'),('Russell Springs','KY'),('Russells Point','OH'),('Russellton','PA'),('Russellville','AL'),('Russellville','AR'),('Russellville','IN'),('Russellville','KY'),('Russellville','MO'),('Russellville','OH'),('Russellville','SC'),('Russellville','TN'),('Russia','OH'),('Russian Mission','AK'),('Russiaville','IN'),('Rustburg','VA'),('Ruston','LA'),('Ruth','MI'),('Ruth','MS'),('Ruth','NV'),('Ruther Glen','VA'),('Rutherford','CA'),('Rutherford','NJ'),('Rutherford','TN'),('Rutherford College','NC'),('Rutherfordton','NC'),('Ruthton','MN'),('Ruthven','IA'),('Ruthville','VA'),('Rutland','IA'),('Rutland','IL'),('Rutland','MA'),('Rutland','ND'),('Rutland','OH'),('Rutland','SD'),('Rutland','VT'),('Rutledge','AL'),('Rutledge','GA'),('Rutledge','MO'),('Rutledge','TN'),('Ryan','IA'),('Ryan','OK'),('Rydal','GA'),('Ryde','CA'),('Ryder','ND'),('Ryderwood','WA'),('Rye','CO'),('Rye','NH'),('Rye','NY'),('Rye','TX'),('Rye Beach','NH'),('Ryegate','MT'),('Ryland','AL'),('S Coffeyville','OK'),('Sabael','NY'),('Sabana Grande','PR'),('Sabana Hoyos','PR'),('Sabana Seca','PR'),('Sabattus','ME'),('Sabetha','KS'),('Sabillasville','MD'),('Sabin','MN'),('Sabina','OH'),('Sabinal','TX'),('Sabine','WV'),('Sabine Pass','TX'),('Sabinsville','PA'),('Sabula','IA'),('Sac City','IA'),('Sacaton','AZ'),('Sachse','TX'),('Sackets Harbor','NY'),('Saco','ME'),('Saco','MT'),('Sacramento','CA'),('Sacramento','KY'),('Sacramento','NM'),('Sacramento','PA'),('Sacred Heart','MN'),('Sacul','TX'),('Saddle Brook','NJ'),('Saddle River','NJ'),('Saddlestring','WY'),('Sadieville','KY'),('Sadler','TX'),('Sadorus','IL'),('Sadsburyville','PA'),('Saegertown','PA'),('Safety Harbor','FL'),('Saffell','AR'),('Safford','AL'),('Safford','AZ'),('Sag Harbor','NY'),('Sagamore','MA'),('Sagamore','PA'),('Sagamore Beach','MA'),('Sagaponack','NY'),('Sage','AR'),('Saginaw','AL'),('Saginaw','MI'),('Saginaw','MN'),('Saginaw','MO'),('Saginaw','OR'),('Sagle','ID'),('Sagola','MI'),('Saguache','CO'),('Sahuarita','AZ'),('Sailor Springs','IL'),('Saint Agatha','ME'),('Saint Albans','ME'),('Saint Albans','MO'),('Saint Albans','NY'),('Saint Albans','VT'),('Saint Albans','WV'),('Saint Albans Bay','VT'),('Saint Amant','LA'),('Saint Ann','MO'),('Saint Anne','IL'),('Saint Ansgar','IA'),('Saint Anthony','IA'),('Saint Anthony','ID'),('Saint Anthony','IN'),('Saint Anthony','ND'),('Saint Augustine','FL'),('Saint Augustine','IL'),('Saint Benedict','LA'),('Saint Benedict','OR'),('Saint Benedict','PA'),('Saint Bernard','LA'),('Saint Bernice','IN'),('Saint Bonaventure','NY'),('Saint Boniface','PA'),('Saint Bonifacius','MN'),('Saint Catharine','KY'),('Saint Charles','AR'),('Saint Charles','IA'),('Saint Charles','ID'),('Saint Charles','IL'),('Saint Charles','KY'),('Saint Charles','MI'),('Saint Charles','MN'),('Saint Charles','MO'),('Saint Charles','SD'),('Saint Charles','VA'),('Saint Clair','MI'),('Saint Clair','MN'),('Saint Clair','MO'),('Saint Clair','PA'),('Saint Clair Shores','MI'),('Saint Clairsville','OH'),('Saint Cloud','FL'),('Saint Cloud','MN'),('Saint Cloud','WI'),('Saint Croix','IN'),('Saint Croix Falls','WI'),('Saint David','AZ'),('Saint David','IL'),('Saint David','ME'),('Saint Donatus','IA'),('Saint Edward','NE'),('Saint Elizabeth','MO'),('Saint Elmo','AL'),('Saint Elmo','IL'),('Saint Francis','AR'),('Saint Francis','KS'),('Saint Francis','KY'),('Saint Francis','ME'),('Saint Francis','MN'),('Saint Francis','SD'),('Saint Francisville','IL'),('Saint Francisville','LA'),('Saint Gabriel','LA'),('Saint George','GA'),('Saint George','KS'),('Saint George','SC'),('Saint George','UT'),('Saint George Island','AK'),('Saint Georges','DE'),('Saint Germain','WI'),('Saint Hedwig','TX'),('Saint Helen','MI'),('Saint Helena','CA'),('Saint Helena','NE'),('Saint Helena Island','SC'),('Saint Helens','KY'),('Saint Helens','OR'),('Saint Henry','OH'),('Saint Hilaire','MN'),('Saint Ignace','MI'),('Saint Ignatius','MT'),('Saint Inigoes','MD'),('Saint Jacob','IL'),('Saint James','LA'),('Saint James','MD'),('Saint James','MN'),('Saint James','MO'),('Saint James','NY'),('Saint James City','FL'),('Saint Jo','TX'),('Saint Joe','AR'),('Saint Joe','IN'),('Saint John','IN'),('Saint John','KS'),('Saint John','ND'),('Saint John','WA'),('Saint Johns','AZ'),('Saint Johns','FL'),('Saint Johns','MI'),('Saint Johns','OH'),('Saint Johns','PA'),('Saint Johnsbury','VT'),('Saint Johnsbury Center','VT'),('Saint Johnsville','NY'),('Saint Joseph','IL'),('Saint Joseph','LA'),('Saint Joseph','MI'),('Saint Joseph','MN'),('Saint Joseph','MO'),('Saint Joseph','TN'),('Saint Just','PR'),('Saint Landry','LA'),('Saint Lawrence','SD'),('Saint Leo','FL'),('Saint Leonard','MD'),('Saint Libory','IL'),('Saint Libory','NE'),('Saint Louis','MI'),('Saint Louis','MO'),('Saint Louis','OK'),('Saint Louis Park','MN'),('Saint Louisville','OH'),('Saint Lucas','IA'),('Saint Marie','MT'),('Saint Maries','ID'),('Saint Marks','FL'),('Saint Martin','MN'),('Saint Martinville','LA'),('Saint Mary','KY'),('Saint Mary','MO'),('Saint Mary Of The Woods','IN'),('Saint Marys','AK'),('Saint Marys','GA'),('Saint Marys','IA'),('Saint Marys','KS'),('Saint Marys','OH'),('Saint Marys','PA'),('Saint Marys','WV'),('Saint Marys City','MD'),('Saint Matthews','SC'),('Saint Maurice','LA'),('Saint Meinrad','IN'),('Saint Michael','AK'),('Saint Michael','MN'),('Saint Michael','ND'),('Saint Michael','PA'),('Saint Michaels','AZ'),('Saint Michaels','MD'),('Saint Nazianz','WI'),('Saint Olaf','IA'),('Saint Onge','SD'),('Saint Paris','OH'),('Saint Patrick','MO'),('Saint Paul','AR'),('Saint Paul','IA'),('Saint Paul','IN'),('Saint Paul','KS'),('Saint Paul','MN'),('Saint Paul','NE'),('Saint Paul','OR'),('Saint Paul','VA'),('Saint Paul Island','AK'),('Saint Paul Park','MN'),('Saint Pauls','NC'),('Saint Peter','IL'),('Saint Peter','MN'),('Saint Peters','MO'),('Saint Peters','PA'),('Saint Petersburg','FL'),('Saint Petersburg','PA'),('Saint Regis','MT'),('Saint Regis Falls','NY'),('Saint Robert','MO'),('Saint Rose','LA'),('Saint Simons Island','GA'),('Saint Stephen','MN'),('Saint Stephen','SC'),('Saint Stephens','AL'),('Saint Stephens','WY'),('Saint Stephens Church','VA'),('Saint Thomas','MO'),('Saint Thomas','ND'),('Saint Thomas','PA'),('Saint Vincent','MN'),('Saint Vrain','NM'),('Saint Xavier','MT'),('Sainte Genevieve','MO'),('Sainte Marie','IL'),('Salado','AR'),('Salado','TX'),('Salamanca','NY'),('Salamonia','IN'),('Salcha','AK'),('Sale City','GA'),('Sale Creek','TN'),('Salem','AL'),('Salem','AR'),('Salem','CT'),('Salem','FL'),('Salem','IA'),('Salem','IL'),('Salem','IN'),('Salem','KY'),('Salem','MA'),('Salem','MI'),('Salem','MO'),('Salem','NE'),('Salem','NH'),('Salem','NJ'),('Salem','NM'),('Salem','NY'),('Salem','OH'),('Salem','OR'),('Salem','SC'),('Salem','SD'),('Salem','UT'),('Salem','VA'),('Salem','WI'),('Salem','WV'),('Salemburg','NC'),('Salesville','OH'),('Salford','PA'),('Salfordville','PA'),('Salida','CA'),('Salida','CO'),('Salina','KS'),('Salina','OK'),('Salina','PA'),('Salina','UT'),('Salinas','CA'),('Salinas','PR'),('Saline','LA'),('Saline','MI'),('Salineno','TX'),('Salineville','OH'),('Salisbury','CT'),('Salisbury','MA'),('Salisbury','MD'),('Salisbury','MO'),('Salisbury','NC'),('Salisbury','NH'),('Salisbury','PA'),('Salisbury','VT'),('Salisbury Center','NY'),('Salisbury Mills','NY'),('Salix','IA'),('Salix','PA'),('Salkum','WA'),('Salley','SC'),('Sallis','MS'),('Sallisaw','OK'),('Salmon','ID'),('Salol','MN'),('Salome','AZ'),('Salona','PA'),('Salsbury Cove','ME'),('Salt Flat','TX'),('Salt Lake City','UT'),('Salt Lick','KY'),('Salt Point','NY'),('Salt Rock','WV'),('Salter Path','NC'),('Salters','SC'),('Saltese','MT'),('Saltillo','MS'),('Saltillo','PA'),('Saltillo','TN'),('Saltillo','TX'),('Salton City','CA'),('Saltsburg','PA'),('Saltville','VA'),('Saluda','NC'),('Saluda','SC'),('Saluda','VA'),('Salvisa','KY'),('Salvo','NC'),('Salyer','CA'),('Salyersville','KY'),('Sam Norwood','TX'),('Samantha','AL'),('Samaria','MI'),('Samburg','TN'),('Sammamish','WA'),('Samoa','CA'),('Samson','AL'),('San Acacia','NM'),('San Andreas','CA'),('San Angelo','TX'),('San Anselmo','CA'),('San Antonio','FL'),('San Antonio','NM'),('San Antonio','PR'),('San Antonio','TX'),('San Ardo','CA'),('San Augustine','TX'),('San Benito','TX'),('San Bernardino','CA'),('San Bruno','CA'),('San Carlos','AZ'),('San Carlos','CA'),('San Clemente','CA'),('San Cristobal','NM'),('San Diego','CA'),('San Diego','TX'),('San Dimas','CA'),('San Elizario','TX'),('San Felipe','TX'),('San Fernando','CA'),('San Fidel','NM'),('San Francisco','CA'),('San Gabriel','CA'),('San German','PR'),('San Geronimo','CA'),('San Gregorio','CA'),('San Isidro','TX'),('San Jacinto','CA'),('San Joaquin','CA'),('San Jon','NM'),('San Jose','CA'),('San Jose','IL'),('San Jose','NM'),('San Juan','PR'),('San Juan','TX'),('San Juan Bautista','CA'),('San Juan Capistrano','CA'),('San Juan Pueblo','NM'),('San Leandro','CA'),('San Lorenzo','CA'),('San Lorenzo','PR'),('San Lucas','CA'),('San Luis','AZ'),('San Luis','CO'),('San Luis Obispo','CA'),('San Luis Rey','CA'),('San Manuel','AZ'),('San Marcos','CA'),('San Marcos','TX'),('San Marino','CA'),('San Martin','CA'),('San Mateo','CA'),('San Mateo','FL'),('San Miguel','CA'),('San Miguel','NM'),('San Pablo','CA'),('San Pablo','CO'),('San Patricio','NM'),('San Pedro','CA'),('San Perlita','TX'),('San Pierre','IN'),('San Quentin','CA'),('San Rafael','CA'),('San Rafael','NM'),('San Ramon','CA'),('San Saba','TX'),('San Sebastian','PR'),('San Simeon','CA'),('San Simon','AZ'),('San Ygnacio','TX'),('San Ysidro','CA'),('San Ysidro','NM'),('Sanborn','IA'),('Sanborn','MN'),('Sanborn','ND'),('Sanborn','NY'),('Sanbornton','NH'),('Sanbornville','NH'),('Sand Coulee','MT'),('Sand Creek','MI'),('Sand Creek','WI'),('Sand Fork','WV'),('Sand Lake','MI'),('Sand Lake','NY'),('Sand Point','AK'),('Sand Springs','MT'),('Sand Springs','OK'),('Sandborn','IN'),('Sanders','AZ'),('Sanders','KY'),('Sanders','MT'),('Sanderson','FL'),('Sanderson','TX'),('Sandersville','GA'),('Sandersville','MS'),('Sandgap','KY'),('Sandhill','MS'),('Sandia','TX'),('Sandia Park','NM'),('Sandisfield','MA'),('Sandoval','IL'),('Sandown','NH'),('Sandpoint','ID'),('Sandston','VA'),('Sandstone','MN'),('Sandstone','WV'),('Sandusky','MI'),('Sandusky','NY'),('Sandusky','OH'),('Sandwich','IL'),('Sandwich','MA'),('Sandy','OR'),('Sandy','UT'),('Sandy Creek','NY'),('Sandy Hook','CT'),('Sandy Hook','KY'),('Sandy Hook','MS'),('Sandy Hook','VA'),('Sandy Lake','PA'),('Sandy Level','VA'),('Sandy Point','ME'),('Sandy Point','VA'),('Sandy Ridge','NC'),('Sandy Ridge','PA'),('Sandy Spring','MD'),('Sandy Springs','SC'),('Sandyville','OH'),('Sandyville','WV'),('Sanford','CO'),('Sanford','FL'),('Sanford','ME'),('Sanford','MI'),('Sanford','NC'),('Sanford','TX'),('Sanford','VA'),('Sanger','CA'),('Sanger','TX'),('Sangerfield','NY'),('Sangerville','ME'),('Sanibel','FL'),('Sanostee','NM'),('Santa','ID'),('Santa Ana','CA'),('Santa Anna','TX'),('Santa Barbara','CA'),('Santa Clara','CA'),('Santa Clara','NM'),('Santa Clara','UT'),('Santa Clarita','CA'),('Santa Claus','IN'),('Santa Cruz','CA'),('Santa Cruz','NM'),('Santa Elena','TX'),('Santa Fe','MO'),('Santa Fe','NM'),('Santa Fe','TN'),('Santa Fe','TX'),('Santa Fe Springs','CA'),('Santa Isabel','PR'),('Santa Margarita','CA'),('Santa Maria','CA'),('Santa Maria','TX'),('Santa Monica','CA'),('Santa Paula','CA'),('Santa Rita Park','CA'),('Santa Rosa','CA'),('Santa Rosa','NM'),('Santa Rosa','TX'),('Santa Rosa Beach','FL'),('Santa Teresa','NM'),('Santa Ynez','CA'),('Santa Ysabel','CA'),('Santaquin','UT'),('Santee','CA'),('Santee','SC'),('Santiago','MN'),('Santo','TX'),('Santo Domingo Pueblo','NM'),('Sapello','NM'),('Sapelo Island','GA'),('Sapphire','NC'),('Sapulpa','OK'),('Saragosa','TX'),('Sarah','MS'),('Sarah Ann','WV'),('Sarahsville','OH'),('Saraland','AL'),('Saranac','MI'),('Saranac','NY'),('Saranac Lake','NY'),('Sarasota','FL'),('Saratoga','AR'),('Saratoga','CA'),('Saratoga','IN'),('Saratoga','NC'),('Saratoga','TX'),('Saratoga','WY'),('Saratoga Springs','NY'),('Saratoga Springs','UT'),('Sarcoxie','MO'),('Sardinia','NY'),('Sardinia','OH'),('Sardinia','SC'),('Sardis','AL'),('Sardis','GA'),('Sardis','MS'),('Sardis','OH'),('Sardis','TN'),('Sarepta','LA'),('Sargeant','MN'),('Sargent','GA'),('Sargent','NE'),('Sargents','CO'),('Sargentville','ME'),('Sarita','TX'),('Sarles','ND'),('Sarona','WI'),('Saronville','NE'),('Sartell','MN'),('Sarver','PA'),('Sasabe','AZ'),('Sasakwa','OK'),('Sassafras','KY'),('Sassamansville','PA'),('Sasser','GA'),('Satanta','KS'),('Satartia','MS'),('Satellite Beach','FL'),('Satin','TX'),('Satsop','WA'),('Satsuma','AL'),('Satsuma','FL'),('Saucier','MS'),('Saugatuck','MI'),('Saugerties','NY'),('Saugus','MA'),('Sauk Centre','MN'),('Sauk City','WI'),('Sauk Rapids','MN'),('Saukville','WI'),('Saul','KY'),('Saulsbury','TN'),('Saulsville','WV'),('Sault Sainte Marie','MI'),('Saunderstown','RI'),('Saunemin','IL'),('Sauquoit','NY'),('Sausalito','CA'),('Sautee Nacoochee','GA'),('Savage','MD'),('Savage','MN'),('Savage','MT'),('Savanna','IL'),('Savanna','OK'),('Savannah','GA'),('Savannah','MO'),('Savannah','NY'),('Savannah','OH'),('Savannah','TN'),('Saverton','MO'),('Savery','WY'),('Savona','NY'),('Savonburg','KS'),('Savoonga','AK'),('Savoy','IL'),('Savoy','MA'),('Savoy','TX'),('Sawyer','KS'),('Sawyer','MI'),('Sawyer','MN'),('Sawyer','ND'),('Sawyer','OK'),('Sawyerville','AL'),('Sawyerville','IL'),('Saxapahaw','NC'),('Saxe','VA'),('Saxeville','WI'),('Saxis','VA'),('Saxon','WI'),('Saxon','WV'),('Saxonburg','PA'),('Saxton','PA'),('Saxtons River','VT'),('Saybrook','IL'),('Saylorsburg','PA'),('Sayner','WI'),('Sayre','AL'),('Sayre','OK'),('Sayre','PA'),('Sayreville','NJ'),('Sayville','NY'),('Scales Mound','IL'),('Scalf','KY'),('Scaly Mountain','NC'),('Scammon','KS'),('Scammon Bay','AK'),('Scandia','KS'),('Scandia','MN'),('Scandinavia','WI'),('Scappoose','OR'),('Scarborough','ME'),('Scarbro','WV'),('Scarsdale','NY'),('Scarville','IA'),('Scenery Hill','PA'),('Scenic','SD'),('Schaefferstown','PA'),('Schaghticoke','NY'),('Schaller','IA'),('Schaumburg','IL'),('Schell City','MO'),('Scheller','IL'),('Schellsburg','PA'),('Schenectady','NY'),('Schenevus','NY'),('Schenley','PA'),('Schererville','IN'),('Schertz','TX'),('Schiller Park','IL'),('Schlater','MS'),('Schleswig','IA'),('Schley','VA'),('Schnecksville','PA'),('Schneider','IN'),('Schnellville','IN'),('Schodack Landing','NY'),('Schoenchen','KS'),('Schofield','WI'),('Schofield Barracks','HI'),('Schoharie','NY'),('Schoolcraft','MI'),('Schooleys Mountain','NJ'),('Schriever','LA'),('Schroeder','MN'),('Schroon Lake','NY'),('Schulenburg','TX'),('Schulter','OK'),('Schurz','NV'),('Schuyler','NE'),('Schuyler','VA'),('Schuyler Falls','NY'),('Schuyler Lake','NY'),('Schuylerville','NY'),('Schuylkill Haven','PA'),('Schwenksville','PA'),('Schwertner','TX'),('Science Hill','KY'),('Scio','NY'),('Scio','OH'),('Scio','OR'),('Sciota','IL'),('Sciota','PA'),('Scioto Furnace','OH'),('Scipio','IN'),('Scipio','UT'),('Scipio Center','NY'),('Scituate','MA'),('Scobey','MS'),('Scobey','MT'),('Scooba','MS'),('Scotch Plains','NJ'),('Scotia','CA'),('Scotia','NE'),('Scotia','SC'),('Scotland','AR'),('Scotland','CT'),('Scotland','GA'),('Scotland','IN'),('Scotland','MD'),('Scotland','PA'),('Scotland','SD'),('Scotland','TX'),('Scotland Neck','NC'),('Scotrun','PA'),('Scott','AR'),('Scott','LA'),('Scott','MS'),('Scott','OH'),('Scott Air Force Base','IL'),('Scott Bar','CA'),('Scott City','KS'),('Scott City','MO'),('Scott Depot','WV'),('Scottdale','GA'),('Scottdale','PA'),('Scottown','OH'),('Scotts','MI'),('Scotts','NC'),('Scotts Hill','TN'),('Scotts Mills','OR'),('Scotts Valley','CA'),('Scottsbluff','NE'),('Scottsboro','AL'),('Scottsburg','IN'),('Scottsburg','NY'),('Scottsburg','OR'),('Scottsburg','VA'),('Scottsdale','AZ'),('Scottsmoor','FL'),('Scottsville','KY'),('Scottsville','NY'),('Scottsville','TX'),('Scottsville','VA'),('Scottville','IL'),('Scottville','MI'),('Scottville','NC'),('Scranton','AR'),('Scranton','IA'),('Scranton','KS'),('Scranton','NC'),('Scranton','ND'),('Scranton','PA'),('Scranton','SC'),('Screven','GA'),('Scribner','NE'),('Scroggins','TX'),('Scuddy','KY'),('Scurry','TX'),('Sea Cliff','NY'),('Sea Girt','NJ'),('Sea Island','GA'),('Sea Isle City','NJ'),('Seabeck','WA'),('Seaboard','NC'),('Seabrook','NH'),('Seabrook','SC'),('Seabrook','TX'),('Seadrift','TX'),('Seaford','DE'),('Seaford','NY'),('Seaford','VA'),('Seaforth','MN'),('Seagoville','TX'),('Seagraves','TX'),('Seagrove','NC'),('Seahurst','WA'),('Seal Beach','CA'),('Seal Cove','ME'),('Seal Harbor','ME'),('Seal Rock','OR'),('Seale','AL'),('Sealevel','NC'),('Sealston','VA'),('Sealy','TX'),('Seaman','OH'),('Seanor','PA'),('Searchlight','NV'),('Searcy','AR'),('Searles','MN'),('Sears','MI'),('Searsboro','IA'),('Searsmont','ME'),('Searsport','ME'),('Seaside','CA'),('Seaside','OR'),('Seaside Heights','NJ'),('Seaside Park','NJ'),('Seaton','IL'),('Seatonville','IL'),('Seattle','WA'),('Seaview','VA'),('Seaview','WA'),('Sebago','ME'),('Sebago Lake','ME'),('Sebasco Estates','ME'),('Sebastian','FL'),('Sebastian','TX'),('Sebastopol','CA'),('Sebastopol','MS'),('Sebec','ME'),('Sebeka','MN'),('Sebewaing','MI'),('Sebree','KY'),('Sebring','FL'),('Sebring','OH'),('Secaucus','NJ'),('Seco','KY'),('Second Mesa','AZ'),('Secondcreek','WV'),('Secor','IL'),('Secretary','MD'),('Section','AL'),('Sedalia','CO'),('Sedalia','IN'),('Sedalia','KY'),('Sedalia','MO'),('Sedalia','NC'),('Sedalia','OH'),('Sedan','KS'),('Sedan','NM'),('Sedgewickville','MO'),('Sedgwick','AR'),('Sedgwick','CO'),('Sedgwick','KS'),('Sedgwick','ME'),('Sedley','VA'),('Sedona','AZ'),('Sedro Woolley','WA'),('Seekonk','MA'),('Seeley','CA'),('Seeley Lake','MT'),('Seelyville','IN'),('Seffner','FL'),('Seguin','TX'),('Seiad Valley','CA'),('Seibert','CO'),('Seiling','OK'),('Sekiu','WA'),('Selah','WA'),('Selawik','AK'),('Selby','SD'),('Selbyville','DE'),('Selbyville','WV'),('Selden','KS'),('Selden','NY'),('Seldovia','AK'),('Selfridge','ND'),('Seligman','AZ'),('Seligman','MO'),('Selinsgrove','PA'),('Selkirk','NY'),('Sellers','SC'),('Sellersburg','IN'),('Sellersville','PA'),('Sells','AZ'),('Selma','AL'),('Selma','CA'),('Selma','IA'),('Selma','IN'),('Selma','NC'),('Selma','OR'),('Selma','VA'),('Selman City','TX'),('Selmer','TN'),('Seltzer','PA'),('Seminary','MS'),('Seminole','AL'),('Seminole','FL'),('Seminole','OK'),('Seminole','PA'),('Seminole','TX'),('Semmes','AL'),('Semora','NC'),('Senath','MO'),('Senatobia','MS'),('Seneca','IL'),('Seneca','KS'),('Seneca','MO'),('Seneca','NE'),('Seneca','OR'),('Seneca','PA'),('Seneca','SC'),('Seneca','SD'),('Seneca','WI'),('Seneca Castle','NY'),('Seneca Falls','NY'),('Seneca Rocks','WV'),('Senecaville','OH'),('Seney','MI'),('Senoia','GA'),('Sentinel','OK'),('Sentinel Butte','ND'),('Sequatchie','TN'),('Sequim','WA'),('Sequoia National Park','CA'),('Serafina','NM'),('Serena','IL'),('Sergeant Bluff','IA'),('Sergeantsville','NJ'),('Servia','IN'),('Sesser','IL'),('Seth','WV'),('Seven Mile','OH'),('Seven Springs','NC'),('Seven Valleys','PA'),('Severance','CO'),('Severance','NY'),('Severn','MD'),('Severn','NC'),('Severn','VA'),('Severna Park','MD'),('Severy','KS'),('Sevier','UT'),('Sevierville','TN'),('Seville','FL'),('Seville','GA'),('Seville','OH'),('Sewanee','TN'),('Seward','AK'),('Seward','IL'),('Seward','NE'),('Seward','PA'),('Sewaren','NJ'),('Sewell','NJ'),('Sewickley','PA'),('Sextons Creek','KY'),('Sextonville','WI'),('Seymour','CT'),('Seymour','IA'),('Seymour','IL'),('Seymour','IN'),('Seymour','MO'),('Seymour','TN'),('Seymour','TX'),('Seymour','WI'),('Shabbona','IL'),('Shacklefords','VA'),('Shade','OH'),('Shade Gap','PA'),('Shady Cove','OR'),('Shady Dale','GA'),('Shady Grove','FL'),('Shady Grove','PA'),('Shady Point','OK'),('Shady Side','MD'),('Shady Spring','WV'),('Shady Valley','TN'),('Shadyside','OH'),('Shafer','MN'),('Shafter','CA'),('Shaftsburg','MI'),('Shaftsbury','VT'),('Shageluk','AK'),('Shakopee','MN'),('Shaktoolik','AK'),('Shalimar','FL'),('Shallotte','NC'),('Shallowater','TX'),('Shambaugh','IA'),('Shamokin','PA'),('Shamokin Dam','PA'),('Shamrock','OK'),('Shamrock','TX'),('Shandaken','NY'),('Shandon','CA'),('Shandon','OH'),('Shaniko','OR'),('Shanks','WV'),('Shanksville','PA'),('Shannock','RI'),('Shannon','AL'),('Shannon','GA'),('Shannon','IL'),('Shannon','MS'),('Shannon','NC'),('Shannon City','IA'),('Shapleigh','ME'),('Sharon','CT'),('Sharon','GA'),('Sharon','KS'),('Sharon','MA'),('Sharon','MS'),('Sharon','ND'),('Sharon','OK'),('Sharon','PA'),('Sharon','SC'),('Sharon','TN'),('Sharon','VT'),('Sharon','WI'),('Sharon Center','OH'),('Sharon Grove','KY'),('Sharon Hill','PA'),('Sharon Springs','KS'),('Sharon Springs','NY'),('Sharpes','FL'),('Sharples','WV'),('Sharps','VA'),('Sharps Chapel','TN'),('Sharpsburg','GA'),('Sharpsburg','IA'),('Sharpsburg','KY'),('Sharpsburg','MD'),('Sharpsburg','NC'),('Sharpsburg','OH'),('Sharpsville','IN'),('Sharpsville','PA'),('Sharptown','MD'),('Shartlesville','PA'),('Shasta','CA'),('Shasta Lake','CA'),('Shattuck','OK'),('Shauck','OH'),('Shaver Lake','CA'),('Shavertown','PA'),('Shaw','MS'),('Shaw A F B','SC'),('Shaw Island','WA'),('Shawanee','TN'),('Shawanese','PA'),('Shawano','WI'),('Shawboro','NC'),('Shawmut','ME'),('Shawmut','MT'),('Shawnee','CO'),('Shawnee','KS'),('Shawnee','OH'),('Shawnee','OK'),('Shawnee','WY'),('Shawnee Mission','KS'),('Shawnee On Delaware','PA'),('Shawneetown','IL'),('Shawsville','VA'),('Shawville','PA'),('Sheakleyville','PA'),('Sheboygan','WI'),('Sheboygan Falls','WI'),('Shedd','OR'),('Sheep Ranch','CA'),('Sheep Springs','NM'),('Sheffield','AL'),('Sheffield','IA'),('Sheffield','IL'),('Sheffield','MA'),('Sheffield','PA'),('Sheffield','TX'),('Sheffield','VT'),('Sheffield Lake','OH'),('Shelbiana','KY'),('Shelbina','MO'),('Shelburn','IN'),('Shelburne','VT'),('Shelburne Falls','MA'),('Shelby','AL'),('Shelby','IA'),('Shelby','IN'),('Shelby','MI'),('Shelby','MS'),('Shelby','MT'),('Shelby','NC'),('Shelby','NE'),('Shelby','OH'),('Shelby Gap','KY'),('Shelbyville','IL'),('Shelbyville','IN'),('Shelbyville','KY'),('Shelbyville','MI'),('Shelbyville','MO'),('Shelbyville','TN'),('Shelbyville','TX'),('Sheldahl','IA'),('Sheldon','IA'),('Sheldon','IL'),('Sheldon','MO'),('Sheldon','ND'),('Sheldon','SC'),('Sheldon','VT'),('Sheldon','WI'),('Sheldon Springs','VT'),('Sheldonville','MA'),('Shell','WY'),('Shell Knob','MO'),('Shell Lake','WI'),('Shell Rock','IA'),('Shelley','ID'),('Shellman','GA'),('Shellsburg','IA'),('Shelly','MN'),('Shelocta','PA'),('Shelter Island','NY'),('Shelter Island Heights','NY'),('Shelton','CT'),('Shelton','NE'),('Shelton','WA'),('Shenandoah','IA'),('Shenandoah','PA'),('Shenandoah','VA'),('Shenandoah Junction','WV'),('Shenorock','NY'),('Shepardsville','IN'),('Shepherd','MI'),('Shepherd','MT'),('Shepherd','TX'),('Shepherdstown','WV'),('Shepherdsville','KY'),('Sheppard Afb','TX'),('Sheppton','PA'),('Sherard','MS'),('Sherborn','MA'),('Sherburn','MN'),('Sherburne','NY'),('Sheridan','AR'),('Sheridan','CA'),('Sheridan','IL'),('Sheridan','IN'),('Sheridan','ME'),('Sheridan','MI'),('Sheridan','MO'),('Sheridan','MT'),('Sheridan','NY'),('Sheridan','OR'),('Sheridan','TX'),('Sheridan','WY'),('Sheridan Lake','CO'),('Sherman','CT'),('Sherman','IL'),('Sherman','ME'),('Sherman','MS'),('Sherman','NY'),('Sherman','TX'),('Sherman Oaks','CA'),('Shermans Dale','PA'),('Sherrard','IL'),('Sherrill','AR'),('Sherrill','IA'),('Sherrill','NY'),('Sherrills Ford','NC'),('Sherrodsville','OH'),('Sherwood','AR'),('Sherwood','MD'),('Sherwood','MI'),('Sherwood','ND'),('Sherwood','OH'),('Sherwood','OR'),('Sherwood','TN'),('Sherwood','WI'),('Shevlin','MN'),('Sheyenne','ND'),('Shickley','NE'),('Shickshinny','PA'),('Shidler','OK'),('Shields','ND'),('Shiloh','GA'),('Shiloh','NC'),('Shiloh','NJ'),('Shiloh','OH'),('Shiloh','TN'),('Shiner','TX'),('Shingle Springs','CA'),('Shinglehouse','PA'),('Shingleton','MI'),('Shingletown','CA'),('Shinhopple','NY'),('Shinnston','WV'),('Shiocton','WI'),('Shipman','IL'),('Shipman','VA'),('Shippensburg','PA'),('Shippenville','PA'),('Shippingport','PA'),('Shiprock','NM'),('Shipshewana','IN'),('Shirland','IL'),('Shirley','AR'),('Shirley','IL'),('Shirley','IN'),('Shirley','MA'),('Shirley','NY'),('Shirley','WV'),('Shirley Basin','WY'),('Shirley Mills','ME'),('Shirleysburg','PA'),('Shiro','TX'),('Shishmaref','AK'),('Shoals','IN'),('Shoals','WV'),('Shobonier','IL'),('Shock','WV'),('Shoemakersville','PA'),('Shohola','PA'),('Shokan','NY'),('Shongaloo','LA'),('Shonto','AZ'),('Shook','MO'),('Shoreham','NY'),('Shoreham','VT'),('Shorewood','IL'),('Short Creek','WV'),('Short Hills','NJ'),('Shorter','AL'),('Shorterville','AL'),('Shortsville','NY'),('Shortt Gap','VA'),('Shoshone','CA'),('Shoshone','ID'),('Shoshoni','WY'),('Shoup','ID'),('Show Low','AZ'),('Showell','MD'),('Shreve','OH'),('Shreveport','LA'),('Shrewsbury','MA'),('Shrewsbury','NJ'),('Shrewsbury','PA'),('Shrub Oak','NY'),('Shubert','NE'),('Shubuta','MS'),('Shullsburg','WI'),('Shumway','IL'),('Shungnak','AK'),('Shunk','PA'),('Shuqualak','MS'),('Shushan','NY'),('Shutesbury','MA'),('Siasconset','MA'),('Sibley','IA'),('Sibley','IL'),('Sibley','LA'),('Sibley','MO'),('Sibley','MS'),('Sicily Island','LA'),('Sicklerville','NJ'),('Side Lake','MN'),('Sidell','IL'),('Sidman','PA'),('Sidnaw','MI'),('Sidney','AR'),('Sidney','IA'),('Sidney','IL'),('Sidney','KY'),('Sidney','MI'),('Sidney','MT'),('Sidney','NE'),('Sidney','NY'),('Sidney','OH'),('Sidney','TX'),('Sidney Center','NY'),('Sidon','MS'),('Sieper','LA'),('Sierra Blanca','TX'),('Sierra City','CA'),('Sierra Madre','CA'),('Sierra Vista','AZ'),('Sierraville','CA'),('Sigel','IL'),('Sigel','PA'),('Signal Hill','CA'),('Signal Mountain','TN'),('Sigourney','IA'),('Sigurd','UT'),('Sikes','LA'),('Sikeston','MO'),('Silas','AL'),('Siler','KY'),('Siler City','NC'),('Silerton','TN'),('Siletz','OR'),('Silex','MO'),('Siloam','GA'),('Siloam','NC'),('Siloam Springs','AR'),('Silsbee','TX'),('Silt','CO'),('Siluria','AL'),('Silva','MO'),('Silvana','WA'),('Silver','TX'),('Silver Bay','MN'),('Silver Bay','NY'),('Silver Beach','MA'),('Silver City','IA'),('Silver City','MS'),('Silver City','NM'),('Silver City','NV'),('Silver Creek','GA'),('Silver Creek','MN'),('Silver Creek','MS'),('Silver Creek','NE'),('Silver Creek','NY'),('Silver Creek','WA'),('Silver Gate','MT'),('Silver Grove','KY'),('Silver Lake','IN'),('Silver Lake','KS'),('Silver Lake','MN'),('Silver Lake','NH'),('Silver Lake','NY'),('Silver Lake','OR'),('Silver Lake','WI'),('Silver Plume','CO'),('Silver Point','TN'),('Silver Spring','MD'),('Silver Spring','PA'),('Silver Springs','FL'),('Silver Springs','NV'),('Silver Springs','NY'),('Silver Star','MT'),('Silverado','CA'),('Silverdale','PA'),('Silverdale','WA'),('Silverhill','AL'),('Silverlake','WA'),('Silverpeak','NV'),('Silverstreet','SC'),('Silverthorne','CO'),('Silverton','CO'),('Silverton','ID'),('Silverton','OR'),('Silverton','TX'),('Silverwood','MI'),('Silvis','IL'),('Simi Valley','CA'),('Simla','CO'),('Simmesport','LA'),('Simms','MT'),('Simms','TX'),('Simon','WV'),('Simonton','TX'),('Simpson','IL'),('Simpson','KS'),('Simpson','LA'),('Simpson','NC'),('Simpson','WV'),('Simpsonville','KY'),('Simpsonville','MD'),('Simpsonville','SC'),('Sims','AR'),('Sims','IL'),('Sims','NC'),('Simsboro','LA'),('Simsbury','CT'),('Sinai','SD'),('Sinclair','ME'),('Sinclair','WY'),('Sinclairville','NY'),('Singer','LA'),('Singers Glen','VA'),('Sinking Spring','OH'),('Sinks Grove','WV'),('Sinnamahoning','PA'),('Sinsinawa','WI'),('Sinton','TX'),('Sioux Center','IA'),('Sioux City','IA'),('Sioux Falls','SD'),('Sioux Rapids','IA'),('Sipesville','PA'),('Sipsey','AL'),('Siren','WI'),('Sisseton','SD'),('Sister Bay','WI'),('Sisters','OR'),('Sistersville','WV'),('Sitka','AK'),('Sitka','KY'),('Six Lakes','MI'),('Six Mile','SC'),('Six Mile Run','PA'),('Sixes','OR'),('Sizerock','KY'),('Skagway','AK'),('Skamokawa','WA'),('Skandia','MI'),('Skaneateles','NY'),('Skaneateles Falls','NY'),('Skanee','MI'),('Skellytown','TX'),('Skelton','WV'),('Skiatook','OK'),('Skidmore','MO'),('Skidmore','TX'),('Skillman','NJ'),('Skippack','PA'),('Skippers','VA'),('Skipperville','AL'),('Skipwith','VA'),('Skokie','IL'),('Skowhegan','ME'),('Skull Valley','AZ'),('Skwentna','AK'),('Skyforest','CA'),('Skykomish','WA'),('Skyland','NC'),('Skytop','PA'),('Slab Fork','WV'),('Slade','KY'),('Slagle','LA'),('Slanesville','WV'),('Slate Hill','NY'),('Slate Run','PA'),('Slate Spring','MS'),('Slatedale','PA'),('Slater','CO'),('Slater','IA'),('Slater','MO'),('Slater','SC'),('Slatersville','RI'),('Slaterville Springs','NY'),('Slatington','PA'),('Slaton','TX'),('Slatyfork','WV'),('Slaughter','LA'),('Slaughters','KY'),('Slayden','TN'),('Slayton','MN'),('Sledge','MS'),('Sleepy Eye','MN'),('Sleetmute','AK'),('Slemp','KY'),('Slick','OK'),('Slickville','PA'),('Slidell','LA'),('Slidell','TX'),('Sligo','PA'),('Slinger','WI'),('Slingerlands','NY'),('Slippery Rock','PA'),('Sloan','IA'),('Sloan','NV'),('Sloansville','NY'),('Sloatsburg','NY'),('Slocomb','AL'),('Slocum','RI'),('Sloughhouse','CA'),('Slovan','PA'),('Smackover','AR'),('Smallwood','NY'),('Smarr','GA'),('Smartt','TN'),('Smartville','CA'),('Smelterville','ID'),('Smethport','PA'),('Smicksburg','PA'),('Smilax','KY'),('Smiley','TX'),('Smith','NV'),('Smith Center','KS'),('Smith Lake','NM'),('Smith Mills','KY'),('Smith River','CA'),('Smithboro','IL'),('Smithboro','NY'),('Smithburg','WV'),('Smithdale','MS'),('Smithers','WV'),('Smithfield','IL'),('Smithfield','KY'),('Smithfield','ME'),('Smithfield','NC'),('Smithfield','NE'),('Smithfield','OH'),('Smithfield','PA'),('Smithfield','RI'),('Smithfield','UT'),('Smithfield','VA'),('Smithfield','WV'),('Smithland','IA'),('Smithland','KY'),('Smithmill','PA'),('Smiths Creek','MI'),('Smiths Grove','KY'),('Smiths Station','AL'),('Smithsburg','MD'),('Smithshire','IL'),('Smithton','IL'),('Smithton','MO'),('Smithton','PA'),('Smithtown','NY'),('Smithville','AR'),('Smithville','GA'),('Smithville','IN'),('Smithville','MO'),('Smithville','MS'),('Smithville','OH'),('Smithville','OK'),('Smithville','TN'),('Smithville','TX'),('Smithville','WV'),('Smithville Flats','NY'),('Smithwick','SD'),('Smoaks','SC'),('Smock','PA'),('Smokerun','PA'),('Smoketown','PA'),('Smoot','WV'),('Smoot','WY'),('Smyer','TX'),('Smyrna','DE'),('Smyrna','GA'),('Smyrna','MI'),('Smyrna','NC'),('Smyrna','NY'),('Smyrna','SC'),('Smyrna','TN'),('Smyrna Mills','ME'),('Sneads','FL'),('Sneads Ferry','NC'),('Sneedville','TN'),('Snelling','CA'),('Snellville','GA'),('Snohomish','WA'),('Snook','TX'),('Snoqualmie','WA'),('Snoqualmie Pass','WA'),('Snover','MI'),('Snow','OK'),('Snow Camp','NC'),('Snow Hill','MD'),('Snow Hill','NC'),('Snow Lake','AR'),('Snow Shoe','PA'),('Snowflake','AZ'),('Snowmass','CO'),('Snowmass Village','CO'),('Snowshoe','WV'),('Snowville','UT'),('Snyder','CO'),('Snyder','NE'),('Snyder','OK'),('Snyder','TX'),('Snydersburg','PA'),('Snydertown','PA'),('Soap Lake','WA'),('Sobieski','WI'),('Social Circle','GA'),('Society Hill','SC'),('Socorro','NM'),('Sod','WV'),('Soda Springs','CA'),('Soda Springs','ID'),('Soddy Daisy','TN'),('Sodus','MI'),('Sodus','NY'),('Sodus Point','NY'),('Solana Beach','CA'),('Solano','NM'),('Soldier','IA'),('Soldier','KS'),('Soldier','KY'),('Soldiers Grove','WI'),('Soldotna','AK'),('Solebury','PA'),('Soledad','CA'),('Solen','ND'),('Solgohachia','AR'),('Solo','MO'),('Solomon','AZ'),('Solomon','KS'),('Solomons','MD'),('Solon','IA'),('Solon','ME'),('Solon','OH'),('Solon Springs','WI'),('Solsberry','IN'),('Solsville','NY'),('Solvang','CA'),('Solway','MN'),('Somerdale','NJ'),('Somerdale','OH'),('Somers','CT'),('Somers','IA'),('Somers','MT'),('Somers','NY'),('Somers','WI'),('Somers Point','NJ'),('Somerset','CA'),('Somerset','CO'),('Somerset','IN'),('Somerset','KY'),('Somerset','MA'),('Somerset','MI'),('Somerset','NJ'),('Somerset','OH'),('Somerset','PA'),('Somerset','TX'),('Somerset','VA'),('Somerset','WI'),('Somerset Center','MI'),('Somersville','CT'),('Somersworth','NH'),('Somerton','AZ'),('Somerville','AL'),('Somerville','IN'),('Somerville','MA'),('Somerville','NJ'),('Somerville','OH'),('Somerville','TN'),('Somerville','TX'),('Somerville','VA'),('Somes Bar','CA'),('Somis','CA'),('Somonauk','IL'),('Sondheimer','LA'),('Sonoita','AZ'),('Sonoma','CA'),('Sonora','CA'),('Sonora','KY'),('Sonora','TX'),('Sontag','MS'),('Sonyea','NY'),('Sopchoppy','FL'),('Soper','OK'),('Soperton','GA'),('Sophia','NC'),('Sophia','WV'),('Soquel','CA'),('Sorento','IL'),('Sorrento','FL'),('Sorrento','LA'),('Sorrento','ME'),('Soso','MS'),('Soudan','MN'),('Souderton','PA'),('Soulsbyville','CA'),('Sound Beach','NY'),('Sour Lake','TX'),('Souris','ND'),('South Acworth','NH'),('South Amana','IA'),('South Amboy','NJ'),('South Barre','MA'),('South Barre','VT'),('South Bay','FL'),('South Beach','OR'),('South Beloit','IL'),('South Bend','IN'),('South Bend','NE'),('South Bend','TX'),('South Bend','WA'),('South Berwick','ME'),('South Bethlehem','NY'),('South Bloomingville','OH'),('South Boardman','MI'),('South Boston','VA'),('South Bound Brook','NJ'),('South Branch','MI'),('South Bristol','ME'),('South Britain','CT'),('South Burlington','VT'),('South Butler','NY'),('South Byron','NY'),('South Cairo','NY'),('South Canaan','PA'),('South Carrollton','KY'),('South Carver','MA'),('South Casco','ME'),('South Charleston','OH'),('South Chatham','MA'),('South China','ME'),('South Cle Elum','WA'),('South Colby','WA'),('South Colton','NY'),('South Dartmouth','MA'),('South Dayton','NY'),('South Deerfield','MA'),('South Dennis','MA'),('South Dennis','NJ'),('South Dos Palos','CA'),('South Easton','MA'),('South Egremont','MA'),('South El Monte','CA'),('South Elgin','IL'),('South English','IA'),('South Fallsburg','NY'),('South Fork','CO'),('South Fork','MO'),('South Fork','PA'),('South Freeport','ME'),('South Fulton','TN'),('South Gardiner','ME'),('South Gate','CA'),('South Gibson','PA'),('South Glastonbury','CT'),('South Glens Falls','NY'),('South Grafton','MA'),('South Greenfield','MO'),('South Hackensack','NJ'),('South Hadley','MA'),('South Hamilton','MA'),('South Hampton','NH'),('South Harwich','MA'),('South Haven','KS'),('South Haven','MI'),('South Haven','MN'),('South Heart','ND'),('South Heights','PA'),('South Hero','VT'),('South Hill','VA'),('South Holland','IL'),('South Houston','TX'),('South Hutchinson','KS'),('South International Falls','MN'),('South Jamesport','NY'),('South Jordan','UT'),('South Kent','CT'),('South Kortright','NY'),('South Lake Tahoe','CA'),('South Lancaster','MA'),('South Lebanon','OH'),('South Lee','MA'),('South Lima','NY'),('South Londonderry','VT'),('South Lyme','CT'),('South Lyon','MI'),('South Milford','IN'),('South Mills','NC'),('South Milwaukee','WI'),('South Montrose','PA'),('South Mountain','PA'),('South Naknek','AK'),('South New Berlin','NY'),('South Newbury','NH'),('South Newfane','VT'),('South Orange','NJ'),('South Orleans','MA'),('South Otselic','NY'),('South Ozone Park','NY'),('South Padre Island','TX'),('South Paris','ME'),('South Park','PA'),('South Pasadena','CA'),('South Pekin','IL'),('South Pittsburg','TN'),('South Plainfield','NJ'),('South Plains','TX'),('South Plymouth','NY'),('South Point','OH'),('South Pomfret','VT'),('South Portland','ME'),('South Portsmouth','KY'),('South Prairie','WA'),('South Range','MI'),('South Range','WI'),('South Richmond Hill','NY'),('South River','NJ'),('South Rockwood','MI'),('South Roxana','IL'),('South Royalton','VT'),('South Ryegate','VT'),('South Saint Paul','MN'),('South Salem','NY'),('South Salem','OH'),('South San Francisco','CA'),('South Seaville','NJ'),('South Shore','KY'),('South Shore','SD'),('South Sioux City','NE'),('South Solon','OH'),('South Sterling','PA'),('South Strafford','VT'),('South Sutton','NH'),('South Tamworth','NH'),('South Thomaston','ME'),('South Union','KY'),('South Vienna','OH'),('South Wales','NY'),('South Walpole','MA'),('South Wayne','WI'),('South Webster','OH'),('South Wellfleet','MA'),('South West City','MO'),('South Weymouth','MA'),('South Whitley','IN'),('South Williamson','KY'),('South Willington','CT'),('South Wilmington','IL'),('South Windham','CT'),('South Windham','ME'),('South Windsor','CT'),('South Woodstock','CT'),('South Woodstock','VT'),('South Yarmouth','MA'),('Southampton','MA'),('Southampton','NY'),('Southampton','PA'),('Southard','OK'),('Southaven','MS'),('Southborough','MA'),('Southbridge','MA'),('Southbury','CT'),('Southeastern','PA'),('Southern Md Facility','MD'),('Southern Pines','NC'),('Southfield','MA'),('Southfield','MI'),('Southfields','NY'),('Southgate','MI'),('Southington','CT'),('Southington','OH'),('Southlake','TX'),('Southmayd','TX'),('Southmont','NC'),('Southold','NY'),('Southport','CT'),('Southport','ME'),('Southport','NC'),('Southside','TN'),('Southside','WV'),('Southview','PA'),('Southwest','PA'),('Southwest Harbor','ME'),('Southwick','MA'),('Southworth','WA'),('Spade','TX'),('Spalding','MI'),('Spalding','NE'),('Spanaway','WA'),('Spangle','WA'),('Spangler','PA'),('Spanish Fork','UT'),('Spanish Fort','AL'),('Spanishburg','WV'),('Sparkill','NY'),('Sparkman','AR'),('Sparks','GA'),('Sparks','NE'),('Sparks','NV'),('Sparks','OK'),('Sparks Glencoe','MD'),('Sparland','IL'),('Sparr','FL'),('Sparrow Bush','NY'),('Sparrows Point','MD'),('Sparta','GA'),('Sparta','IL'),('Sparta','KY'),('Sparta','MI'),('Sparta','MO'),('Sparta','NC'),('Sparta','NJ'),('Sparta','OH'),('Sparta','TN'),('Sparta','VA'),('Sparta','WI'),('Spartanburg','SC'),('Spartansburg','PA'),('Spavinaw','OK'),('Spearfish','SD'),('Spearman','TX'),('Spearsville','LA'),('Spearville','KS'),('Speculator','NY'),('Speed','NC'),('Speedway','IN'),('Speedwell','TN'),('Speedwell','VA'),('Speer','IL'),('Spelter','WV'),('Spencer','IA'),('Spencer','ID'),('Spencer','IN'),('Spencer','MA'),('Spencer','NC'),('Spencer','NE'),('Spencer','NY'),('Spencer','OH'),('Spencer','OK'),('Spencer','SD'),('Spencer','TN'),('Spencer','VA'),('Spencer','WI'),('Spencer','WV'),('Spencerport','NY'),('Spencertown','NY'),('Spencerville','IN'),('Spencerville','MD'),('Spencerville','OH'),('Spencerville','OK'),('Speonk','NY'),('Sperry','IA'),('Sperry','OK'),('Sperryville','VA'),('Spiceland','IN'),('Spicer','MN'),('Spicewood','TX'),('Spickard','MO'),('Spillville','IA'),('Spindale','NC'),('Spinnerstown','PA'),('Spirit Lake','IA'),('Spirit Lake','ID'),('Spiritwood','ND'),('Spiro','OK'),('Spivey','KS'),('Splendora','TX'),('Spofford','NH'),('Spokane','MO'),('Spokane','WA'),('Spooner','WI'),('Spotswood','NJ'),('Spotsylvania','VA'),('Spottsville','KY'),('Spout Spring','VA'),('Spraggs','PA'),('Sprague','NE'),('Sprague','WA'),('Sprague','WV'),('Sprague River','OR'),('Spragueville','IA'),('Sprakers','NY'),('Sprankle Mills','PA'),('Spray','OR'),('Spreckels','CA'),('Spring','TX'),('Spring Arbor','MI'),('Spring Branch','TX'),('Spring Brook','NY'),('Spring Church','PA'),('Spring City','PA'),('Spring City','TN'),('Spring City','UT'),('Spring Creek','NV'),('Spring Creek','PA'),('Spring Creek','TN'),('Spring Dale','WV'),('Spring Gap','MD'),('Spring Garden','AL'),('Spring Glen','NY'),('Spring Glen','PA'),('Spring Green','WI'),('Spring Grove','IL'),('Spring Grove','MN'),('Spring Grove','PA'),('Spring Grove','VA'),('Spring Hill','FL'),('Spring Hill','KS'),('Spring Hill','TN'),('Spring Hope','NC'),('Spring House','PA'),('Spring Lake','MI'),('Spring Lake','MN'),('Spring Lake','NC'),('Spring Lake','NJ'),('Spring Mills','PA'),('Spring Mount','PA'),('Spring Park','MN'),('Spring Run','PA'),('Spring Valley','CA'),('Spring Valley','IL'),('Spring Valley','MN'),('Spring Valley','NY'),('Spring Valley','OH'),('Spring Valley','WI'),('Springboro','OH'),('Springboro','PA'),('Springbrook','IA'),('Springbrook','WI'),('Springdale','AR'),('Springdale','MT'),('Springdale','PA'),('Springdale','UT'),('Springdale','WA'),('Springer','NM'),('Springer','OK'),('Springerton','IL'),('Springerville','AZ'),('Springfield','AR'),('Springfield','CO'),('Springfield','GA'),('Springfield','ID'),('Springfield','IL'),('Springfield','KY'),('Springfield','LA'),('Springfield','MA'),('Springfield','ME'),('Springfield','MN'),('Springfield','MO'),('Springfield','NE'),('Springfield','NH'),('Springfield','NJ'),('Springfield','OH'),('Springfield','OR'),('Springfield','PA'),('Springfield','SC'),('Springfield','SD'),('Springfield','TN'),('Springfield','VA'),('Springfield','VT'),('Springfield','WI'),('Springfield','WV'),('Springfield Center','NY'),('Springfield Gardens','NY'),('Springhill','LA'),('Springlake','TX'),('Springport','IN'),('Springport','MI'),('Springs','PA'),('Springtown','PA'),('Springtown','TX'),('Springvale','ME'),('Springview','NE'),('Springville','AL'),('Springville','CA'),('Springville','IA'),('Springville','IN'),('Springville','NY'),('Springville','PA'),('Springville','TN'),('Springville','UT'),('Springwater','NY'),('Sproul','PA'),('Spruce','MI'),('Spruce Creek','PA'),('Spruce Head','ME'),('Spruce Pine','AL'),('Spruce Pine','NC'),('Spur','TX'),('Spurgeon','IN'),('Spurger','TX'),('Spurlockville','WV'),('Squaw Lake','MN'),('Squaw Valley','CA'),('Squire','WV'),('Squires','MO'),('Squirrel Island','ME'),('St Clairsville','PA'),('St Columbans','NE'),('Staatsburg','NY'),('Stacy','MN'),('Stacy','NC'),('Stacyville','IA'),('Stacyville','ME'),('Stafford','CT'),('Stafford','KS'),('Stafford','NY'),('Stafford','OH'),('Stafford','TX'),('Stafford','VA'),('Stafford Springs','CT'),('Staffordsville','KY'),('Staffordsville','VA'),('Staffordville','CT'),('Stahlstown','PA'),('Staley','NC'),('Stambaugh','KY'),('Stambaugh','MI'),('Stamford','CT'),('Stamford','NE'),('Stamford','NY'),('Stamford','TX'),('Stamford','VT'),('Stamping Ground','KY'),('Stamps','AR'),('Stanaford','WV'),('Stanardsville','VA'),('Stanberry','MO'),('Stanchfield','MN'),('Standard','CA'),('Standard','IL'),('Standish','CA'),('Standish','ME'),('Standish','MI'),('Stanfield','AZ'),('Stanfield','NC'),('Stanfield','OR'),('Stanford','CA'),('Stanford','IL'),('Stanford','IN'),('Stanford','KY'),('Stanford','MT'),('Stanfordville','NY'),('Stanhope','IA'),('Stanhope','NJ'),('Stanley','IA'),('Stanley','ID'),('Stanley','KY'),('Stanley','NC'),('Stanley','ND'),('Stanley','NM'),('Stanley','NY'),('Stanley','VA'),('Stanley','WI'),('Stanleytown','VA'),('Stanton','AL'),('Stanton','CA'),('Stanton','IA'),('Stanton','KY'),('Stanton','MI'),('Stanton','MO'),('Stanton','ND'),('Stanton','NE'),('Stanton','NJ'),('Stanton','TN'),('Stanton','TX'),('Stantonsburg','NC'),('Stantonville','TN'),('Stanville','KY'),('Stanwood','IA'),('Stanwood','MI'),('Stanwood','WA'),('Staplehurst','NE'),('Staples','MN'),('Staples','TX'),('Stapleton','AL'),('Stapleton','GA'),('Stapleton','NE'),('Star','ID'),('Star','MS'),('Star','NC'),('Star','TX'),('Star City','AR'),('Star City','IN'),('Star Junction','PA'),('Star Lake','NY'),('Star Lake','WI'),('Star Prairie','WI'),('Star Tannery','VA'),('Starbuck','MN'),('Starbuck','WA'),('Starford','PA'),('Stark','KS'),('Stark City','MO'),('Starke','FL'),('Starks','LA'),('Starksboro','VT'),('Starkville','MS'),('Starkweather','ND'),('Starlight','PA'),('Starr','SC'),('Starrucca','PA'),('Start','LA'),('Startex','SC'),('Startup','WA'),('State Center','IA'),('State College','PA'),('State Farm','VA'),('State Line','IN'),('State Line','MS'),('State Line','PA'),('State Park','SC'),('State Road','NC'),('State University','AR'),('Stateline','NV'),('Staten Island','NY'),('Statenville','GA'),('Statesboro','GA'),('Statesville','NC'),('Statham','GA'),('Staunton','IL'),('Staunton','IN'),('Staunton','VA'),('Stayton','OR'),('Steamboat Rock','IA'),('Steamboat Springs','CO'),('Steamburg','NY'),('Stearns','KY'),('Stebbins','AK'),('Stedman','NC'),('Steedman','MO'),('Steele','AL'),('Steele','KY'),('Steele','MO'),('Steele','ND'),('Steele City','NE'),('Steeles Tavern','VA'),('Steeleville','IL'),('Steelville','MO'),('Steen','MN'),('Steens','MS'),('Steep Falls','ME'),('Steger','IL'),('Stehekin','WA'),('Steilacoom','WA'),('Steinauer','NE'),('Steinhatchee','FL'),('Stella','MO'),('Stella','NC'),('Stella','NE'),('Stella Niagara','NY'),('Stem','NC'),('Stendal','IN'),('Stennis Space Center','MS'),('Stephan','SD'),('Stephen','MN'),('Stephens','AR'),('Stephens','GA'),('Stephens City','VA'),('Stephenson','MI'),('Stephenson','VA'),('Stephenson','WV'),('Stephensport','KY'),('Stephentown','NY'),('Stephenville','TX'),('Steptoe','WA'),('Sterling','AK'),('Sterling','CO'),('Sterling','CT'),('Sterling','IL'),('Sterling','KS'),('Sterling','MA'),('Sterling','MI'),('Sterling','ND'),('Sterling','NE'),('Sterling','NY'),('Sterling','OH'),('Sterling','OK'),('Sterling','PA'),('Sterling','UT'),('Sterling','VA'),('Sterling City','TX'),('Sterling Forest','NY'),('Sterling Heights','MI'),('Sterlington','LA'),('Sterrett','AL'),('Stet','MO'),('Stetson','ME'),('Stetsonville','WI'),('Steuben','ME'),('Steuben','WI'),('Steubenville','OH'),('Stevens','PA'),('Stevens Point','WI'),('Stevens Village','AK'),('Stevensburg','VA'),('Stevenson','AL'),('Stevenson','CT'),('Stevenson','MD'),('Stevenson','WA'),('Stevenson Ranch','CA'),('Stevensville','MD'),('Stevensville','MI'),('Stevensville','MT'),('Stevensville','PA'),('Stevensville','VA'),('Stevinson','CA'),('Steward','IL'),('Stewardson','IL'),('Stewart','MN'),('Stewart','MS'),('Stewart','OH'),('Stewart','TN'),('Stewarts Point','CA'),('Stewartstown','PA'),('Stewartsville','MO'),('Stewartsville','NJ'),('Stewartville','MN'),('Stickney','SD'),('Stidham','OK'),('Stigler','OK'),('Stilesville','IN'),('Still Pond','MD'),('Still River','MA'),('Stillman Valley','IL'),('Stillmore','GA'),('Stillwater','ME'),('Stillwater','MN'),('Stillwater','NJ'),('Stillwater','NY'),('Stillwater','OH'),('Stillwater','OK'),('Stillwater','PA'),('Stilwell','KS'),('Stilwell','OK'),('Stinesville','IN'),('Stinnett','KY'),('Stinnett','TX'),('Stinson Beach','CA'),('Stinson Lake','NH'),('Stirling','NJ'),('Stirling City','CA'),('Stirum','ND'),('Stites','ID'),('Stittville','NY'),('Stitzer','WI'),('Stockbridge','GA'),('Stockbridge','MA'),('Stockbridge','MI'),('Stockbridge','VT'),('Stockbridge','WI'),('Stockdale','OH'),('Stockdale','PA'),('Stockdale','TX'),('Stockertown','PA'),('Stockett','MT'),('Stockholm','ME'),('Stockholm','NJ'),('Stockholm','SD'),('Stockholm','WI'),('Stockland','IL'),('Stockport','IA'),('Stockport','OH'),('Stockton','AL'),('Stockton','CA'),('Stockton','GA'),('Stockton','IA'),('Stockton','IL'),('Stockton','KS'),('Stockton','MD'),('Stockton','MN'),('Stockton','MO'),('Stockton','NJ'),('Stockton','NY'),('Stockton','UT'),('Stockton Springs','ME'),('Stockville','NE'),('Stockwell','IN'),('Stoddard','NH'),('Stoddard','WI'),('Stokes','NC'),('Stokesdale','NC'),('Stollings','WV'),('Stone','KY'),('Stone Creek','OH'),('Stone Harbor','NJ'),('Stone Lake','WI'),('Stone Mountain','GA'),('Stone Park','IL'),('Stone Ridge','NY'),('Stoneboro','PA'),('Stonefort','IL'),('Stoneham','CO'),('Stoneham','MA'),('Stoneham','ME'),('Stoneville','MS'),('Stoneville','NC'),('Stonewall','LA'),('Stonewall','MS'),('Stonewall','NC'),('Stonewall','OK'),('Stonewall','TX'),('Stoney Fork','KY'),('Stonington','CT'),('Stonington','IL'),('Stonington','ME'),('Stony Brook','NY'),('Stony Creek','NY'),('Stony Creek','VA'),('Stony Point','NC'),('Stony Point','NY'),('Stony Ridge','OH'),('Stonyford','CA'),('Stopover','KY'),('Storden','MN'),('Storm Lake','IA'),('Stormville','NY'),('Storrie','CA'),('Storrs Mansfield','CT'),('Story','AR'),('Story','WY'),('Story City','IA'),('Stotts City','MO'),('Stottville','NY'),('Stoughton','MA'),('Stoughton','WI'),('Stout','IA'),('Stout','OH'),('Stoutland','MO'),('Stoutsville','MO'),('Stoutsville','OH'),('Stovall','NC'),('Stover','MO'),('Stow','MA'),('Stow','NY'),('Stow','OH'),('Stowe','VT'),('Stowell','TX'),('Stoy','IL'),('Stoystown','PA'),('Strabane','PA'),('Strafford','MO'),('Strafford','NH'),('Strafford','VT'),('Strandburg','SD'),('Strandquist','MN'),('Strang','NE'),('Strang','OK'),('Strasburg','CO'),('Strasburg','IL'),('Strasburg','MO'),('Strasburg','ND'),('Strasburg','OH'),('Strasburg','PA'),('Strasburg','VA'),('Stratford','CA'),('Stratford','CT'),('Stratford','IA'),('Stratford','NJ'),('Stratford','NY'),('Stratford','OK'),('Stratford','SD'),('Stratford','TX'),('Stratford','VA'),('Stratford','WA'),('Stratford','WI'),('Stratham','NH'),('Strathcona','MN'),('Strathmere','NJ'),('Strathmore','CA'),('Strattanville','PA'),('Stratton','CO'),('Stratton','ME'),('Stratton','NE'),('Stratton','OH'),('Straughn','IN'),('Strausstown','PA'),('Strawberry','AR'),('Strawberry','CA'),('Strawberry Plains','TN'),('Strawberry Point','IA'),('Strawberry Valley','CA'),('Strawn','IL'),('Strawn','TX'),('Streamwood','IL'),('Streator','IL'),('Street','MD'),('Streeter','ND'),('Streetman','TX'),('Streetsboro','OH'),('Stringer','MS'),('Stringtown','OK'),('Stroh','IN'),('Stromsburg','NE'),('Strong','AR'),('Strong','ME'),('Strong City','KS'),('Stronghurst','IL'),('Strongs','MI'),('Strongstown','PA'),('Strongsville','OH'),('Stroud','OK'),('Stroudsburg','PA'),('Strum','WI'),('Strunk','KY'),('Struthers','OH'),('Stryker','MT'),('Stryker','OH'),('Strykersville','NY'),('Stuart','FL'),('Stuart','IA'),('Stuart','NE'),('Stuart','OK'),('Stuart','VA'),('Stuarts Draft','VA'),('Studio City','CA'),('Studley','VA'),('Stump Creek','PA'),('Stumpy Point','NC'),('Sturbridge','MA'),('Sturdivant','MO'),('Sturgeon','MO'),('Sturgeon','PA'),('Sturgeon Bay','WI'),('Sturgeon Lake','MN'),('Sturgis','KY'),('Sturgis','MI'),('Sturgis','MS'),('Sturgis','SD'),('Sturkie','AR'),('Sturtevant','WI'),('Stuttgart','AR'),('Stuyvesant','NY'),('Stuyvesant Falls','NY'),('Suamico','WI'),('Subiaco','AR'),('Sublette','IL'),('Sublette','KS'),('Sublime','TX'),('Sublimity','OR'),('Suburb Maryland Fac','MD'),('Succasunna','NJ'),('Success','AR'),('Success','MO'),('Suches','GA'),('Sudan','TX'),('Sudbury','MA'),('Sudlersville','MD'),('Suffern','NY'),('Suffield','CT'),('Suffolk','VA'),('Sugar City','CO'),('Sugar City','ID'),('Sugar Grove','IL'),('Sugar Grove','NC'),('Sugar Grove','OH'),('Sugar Grove','PA'),('Sugar Grove','VA'),('Sugar Grove','WV'),('Sugar Hill','NH'),('Sugar Land','TX'),('Sugar Loaf','NY'),('Sugar Run','PA'),('Sugar Tree','TN'),('Sugar Valley','GA'),('Sugarcreek','OH'),('Sugarloaf','CA'),('Sugarloaf','PA'),('Sugartown','LA'),('Suisun City','CA'),('Suitland','MD'),('Sula','MT'),('Sulligent','AL'),('Sullivan','IL'),('Sullivan','IN'),('Sullivan','KY'),('Sullivan','ME'),('Sullivan','MO'),('Sullivan','NH'),('Sullivan','OH'),('Sullivan','WI'),('Sullivan City','TX'),('Sullivans Island','SC'),('Sully','IA'),('Sulphur','IN'),('Sulphur','KY'),('Sulphur','LA'),('Sulphur','OK'),('Sulphur Bluff','TX'),('Sulphur Rock','AR'),('Sulphur Springs','AR'),('Sulphur Springs','IN'),('Sulphur Springs','OH'),('Sulphur Springs','TX'),('Sultan','WA'),('Sultana','CA'),('Sumas','WA'),('Sumatra','FL'),('Sumatra','MT'),('Sumava Resorts','IN'),('Sumerco','WV'),('Sumerduck','VA'),('Sumiton','AL'),('Summer Lake','OR'),('Summer Shade','KY'),('Summerdale','AL'),('Summerdale','PA'),('Summerfield','FL'),('Summerfield','IL'),('Summerfield','KS'),('Summerfield','LA'),('Summerfield','NC'),('Summerfield','OH'),('Summerfield','TX'),('Summerhill','PA'),('Summerland','CA'),('Summerland Key','FL'),('Summers','AR'),('Summersville','KY'),('Summersville','MO'),('Summersville','WV'),('Summerton','SC'),('Summertown','TN'),('Summerville','GA'),('Summerville','OR'),('Summerville','PA'),('Summerville','SC'),('Summit','AR'),('Summit','MS'),('Summit','NJ'),('Summit','NY'),('Summit','SD'),('Summit','UT'),('Summit Argo','IL'),('Summit Hill','PA'),('Summit Lake','WI'),('Summit Point','WV'),('Summit Station','OH'),('Summit Station','PA'),('Summitville','IN'),('Summitville','NY'),('Summitville','OH'),('Summitville','TN'),('Sumner','GA'),('Sumner','IA'),('Sumner','IL'),('Sumner','ME'),('Sumner','MI'),('Sumner','MO'),('Sumner','MS'),('Sumner','NE'),('Sumner','TX'),('Sumner','WA'),('Sumneytown','PA'),('Sumpter','OR'),('Sumrall','MS'),('Sumter','SC'),('Sumterville','FL'),('Sun','LA'),('Sun City','AZ'),('Sun City','CA'),('Sun City','FL'),('Sun City','KS'),('Sun City Center','FL'),('Sun City West','AZ'),('Sun Prairie','WI'),('Sun River','MT'),('Sun Valley','AZ'),('Sun Valley','CA'),('Sun Valley','ID'),('Sun Valley','NV'),('Sunapee','NH'),('Sunbright','TN'),('Sunburg','MN'),('Sunburst','MT'),('Sunbury','NC'),('Sunbury','OH'),('Sunbury','PA'),('Suncook','NH'),('Sundance','WY'),('Sunderland','MA'),('Sunderland','MD'),('Sundown','TX'),('Sunfield','MI'),('Sunflower','AL'),('Sunflower','MS'),('Sunland','CA'),('Sunland Park','NM'),('Sunman','IN'),('Sunny Side','GA'),('Sunnyside','NY'),('Sunnyside','UT'),('Sunnyside','WA'),('Sunnyvale','CA'),('Sunnyvale','TX'),('Sunol','CA'),('Sunray','TX'),('Sunrise Beach','MO'),('Sunset','LA'),('Sunset','ME'),('Sunset','SC'),('Sunset','TX'),('Sunset Beach','CA'),('Sunset Beach','NC'),('Sunshine','LA'),('Sunspot','NM'),('Supai','AZ'),('Superior','AZ'),('Superior','IA'),('Superior','MT'),('Superior','NE'),('Superior','WI'),('Superior','WY'),('Suplee','PA'),('Supply','NC'),('Suquamish','WA'),('Surfside','CA'),('Surgoinsville','TN'),('Suring','WI'),('Surprise','AZ'),('Surprise','NE'),('Surprise','NY'),('Surrency','GA'),('Surrey','ND'),('Surry','ME'),('Surry','VA'),('Surveyor','WV'),('Susan','VA'),('Susanville','CA'),('Susquehanna','PA'),('Sussex','NJ'),('Sussex','VA'),('Sussex','WI'),('Sutersville','PA'),('Sutherland','IA'),('Sutherland','NE'),('Sutherland','VA'),('Sutherland Springs','TX'),('Sutherlin','OR'),('Sutherlin','VA'),('Sutter','CA'),('Sutter','IL'),('Sutter Creek','CA'),('Sutton','AK'),('Sutton','MA'),('Sutton','ND'),('Sutton','NE'),('Sutton','VT'),('Sutton','WV'),('Suttons Bay','MI'),('Suwanee','GA'),('Suwannee','FL'),('Swain','NY'),('Swainsboro','GA'),('Swaledale','IA'),('Swampscott','MA'),('Swan','IA'),('Swan Lake','MS'),('Swan Lake','NY'),('Swan River','MN'),('Swan Valley','ID'),('Swanlake','ID'),('Swannanoa','NC'),('Swanquarter','NC'),('Swans Island','ME'),('Swansboro','NC'),('Swansea','MA'),('Swansea','SC'),('Swanton','MD'),('Swanton','NE'),('Swanton','OH'),('Swanton','VT'),('Swanville','MN'),('Swanzey','NH'),('Swarthmore','PA'),('Swartswood','NJ'),('Swartz','LA'),('Swartz Creek','MI'),('Swatara','MN'),('Swayzee','IN'),('Swea City','IA'),('Swedesboro','NJ'),('Swedesburg','IA'),('Sweeden','KY'),('Sweeny','TX'),('Sweet','ID'),('Sweet Briar','VA'),('Sweet Grass','MT'),('Sweet Home','AR'),('Sweet Home','OR'),('Sweet Home','TX'),('Sweet Springs','MO'),('Sweet Valley','PA'),('Sweet Water','AL'),('Sweetser','IN'),('Sweetwater','OK'),('Sweetwater','TN'),('Sweetwater','TX'),('Swengel','PA'),('Swepsonville','NC'),('Swift','MN'),('Swifton','AR'),('Swiftown','MS'),('Swiftwater','PA'),('Swink','CO'),('Swink','OK'),('Swisher','IA'),('Swiss','WV'),('Swisshome','OR'),('Switchback','WV'),('Switz City','IN'),('Switzer','WV'),('Swoope','VA'),('Swords Creek','VA'),('Sybertsville','PA'),('Sycamore','AL'),('Sycamore','GA'),('Sycamore','IL'),('Sycamore','KS'),('Sycamore','OH'),('Sycamore','PA'),('Sycamore','SC'),('Sycamore Valley','OH'),('Sydney','FL'),('Sykeston','ND'),('Sykesville','MD'),('Sykesville','PA'),('Sylacauga','AL'),('Sylmar','CA'),('Sylva','NC'),('Sylvan Beach','MI'),('Sylvan Beach','NY'),('Sylvan Grove','KS'),('Sylvania','AL'),('Sylvania','GA'),('Sylvania','OH'),('Sylvania','PA'),('Sylvester','GA'),('Sylvester','TX'),('Sylvester','WV'),('Sylvia','KS'),('Symsonia','KY'),('Syosset','NY'),('Syracuse','IN'),('Syracuse','KS'),('Syracuse','MO'),('Syracuse','NE'),('Syracuse','NY'),('Syracuse','OH'),('Syracuse','UT'),('Syria','VA'),('Taberg','NY'),('Tabernacle','NJ'),('Tabernash','CO'),('Tabiona','UT'),('Table Grove','IL'),('Table Rock','NE'),('Tabor','IA'),('Tabor','SD'),('Tabor City','NC'),('Tacna','AZ'),('Tacoma','WA'),('Taconic','CT'),('Taconite','MN'),('Tad','WV'),('Taft','CA'),('Taft','OK'),('Taft','TN'),('Taft','TX'),('Tafton','PA'),('Taftsville','VT'),('Taftville','CT'),('Tahlequah','OK'),('Tahoe City','CA'),('Tahoe Vista','CA'),('Tahoka','TX'),('Taholah','WA'),('Tahoma','CA'),('Tahuya','WA'),('Taiban','NM'),('Takoma Park','MD'),('Takotna','AK'),('Talala','OK'),('Talbot','IN'),('Talbott','TN'),('Talbotton','GA'),('Talco','TX'),('Talcott','WV'),('Talent','OR'),('Talihina','OK'),('Talisheek','LA'),('Talkeetna','AK'),('Talking Rock','GA'),('Tall Timbers','MD'),('Talladega','AL'),('Tallahassee','FL'),('Tallapoosa','GA'),('Tallapoosa','MO'),('Tallassee','AL'),('Tallassee','TN'),('Tallevast','FL'),('Tallmadge','OH'),('Tallman','NY'),('Tallmansville','WV'),('Tallula','IL'),('Tallulah','LA'),('Tallulah Falls','GA'),('Talmage','CA'),('Talmage','KS'),('Talmage','NE'),('Talmage','PA'),('Talmage','UT'),('Talmo','GA'),('Talmoon','MN'),('Taloga','OK'),('Talpa','TX'),('Tama','IA'),('Tamaqua','PA'),('Tamarack','MN'),('Tamaroa','IL'),('Tamassee','SC'),('Tamiment','PA'),('Tamms','IL'),('Tampa','FL'),('Tampa','KS'),('Tampico','IL'),('Tamworth','NH'),('Tanacross','AK'),('Tanana','AK'),('Taneytown','MD'),('Taneyville','MO'),('Tangent','OR'),('Tangerine','FL'),('Tangier','VA'),('Tangipahoa','LA'),('Tanner','AL'),('Tannersville','NY'),('Tannersville','PA'),('Tannersville','VA'),('Taopi','MN'),('Taos','NM'),('Taos Ski Valley','NM'),('Tappahannock','VA'),('Tappan','NY'),('Tappen','ND'),('Tar Heel','NC'),('Tarawa Terrace','NC'),('Tarboro','NC'),('Tarentum','PA'),('Tariffville','CT'),('Tarkio','MO'),('Tarlton','OH'),('Tarpley','TX'),('Tarpon Springs','FL'),('Tarrs','PA'),('Tarrytown','GA'),('Tarrytown','NY'),('Tarzan','TX'),('Tarzana','CA'),('Tasley','VA'),('Taswell','IN'),('Tatamy','PA'),('Tate','GA'),('Tateville','KY'),('Tatitlek','AK'),('Tatum','NM'),('Tatum','SC'),('Tatum','TX'),('Tatums','OK'),('Taunton','MA'),('Taunton','MN'),('Tavares','FL'),('Tavernier','FL'),('Tawas City','MI'),('Taylor','AR'),('Taylor','AZ'),('Taylor','LA'),('Taylor','MI'),('Taylor','MO'),('Taylor','MS'),('Taylor','ND'),('Taylor','NE'),('Taylor','PA'),('Taylor','TX'),('Taylor','WI'),('Taylor Ridge','IL'),('Taylor Springs','IL'),('Taylors','SC'),('Taylors Falls','MN'),('Taylors Island','MD'),('Taylorstown','PA'),('Taylorsville','CA'),('Taylorsville','GA'),('Taylorsville','IN'),('Taylorsville','KY'),('Taylorsville','MS'),('Taylorsville','NC'),('Taylorville','IL'),('Tazewell','TN'),('Tazewell','VA'),('Tchula','MS'),('Tea','SD'),('Teaberry','KY'),('Teachey','NC'),('Teague','TX'),('Teaneck','NJ'),('Teasdale','UT'),('Teays','WV'),('Tebbetts','MO'),('Tecate','CA'),('Techny','IL'),('Tecopa','CA'),('Tecumseh','KS'),('Tecumseh','MI'),('Tecumseh','MO'),('Tecumseh','NE'),('Tecumseh','OK'),('Teec Nos Pos','AZ'),('Teeds Grove','IA'),('Tefft','IN'),('Tehachapi','CA'),('Tehama','CA'),('Tehuacana','TX'),('Teigen','MT'),('Tekamah','NE'),('Tekoa','WA'),('Tekonsha','MI'),('Telegraph','TX'),('Telephone','TX'),('Telferner','TX'),('Telford','PA'),('Telford','TN'),('Tell','TX'),('Tell City','IN'),('Teller','AK'),('Tellico Plains','TN'),('Telluride','CO'),('Telogia','FL'),('Temecula','CA'),('Tempe','AZ'),('Temperance','MI'),('Temperanceville','VA'),('Temple','GA'),('Temple','ME'),('Temple','NH'),('Temple','OK'),('Temple','PA'),('Temple','TX'),('Temple Bar Marina','AZ'),('Temple City','CA'),('Temple Hills','MD'),('Templeton','CA'),('Templeton','IA'),('Templeton','IN'),('Templeton','MA'),('Templeton','PA'),('Templeville','MD'),('Ten Mile','TN'),('Ten Sleep','WY'),('Tenafly','NJ'),('Tenaha','TX'),('Tenakee Springs','AK'),('Tenants Harbor','ME'),('Tendoy','ID'),('Tenino','WA'),('Tenmile','OR'),('Tennent','NJ'),('Tennessee','IL'),('Tennessee Colony','TX'),('Tennessee Ridge','TN'),('Tennga','GA'),('Tennille','GA'),('Tennyson','IN'),('Tennyson','TX'),('Tensed','ID'),('Tenstrike','MN'),('Tererro','NM'),('Terlingua','TX'),('Terlton','OK'),('Termo','CA'),('Terra Alta','WV'),('Terra Bella','CA'),('Terra Ceia','FL'),('Terrace Park','OH'),('Terral','OK'),('Terre Haute','IN'),('Terre Hill','PA'),('Terrebonne','OR'),('Terrell','NC'),('Terrell','TX'),('Terreton','ID'),('Terril','IA'),('Terry','MS'),('Terry','MT'),('Terryville','CT'),('Tescott','KS'),('Tesuque','NM'),('Teterboro','NJ'),('Teton','ID'),('Teton Village','WY'),('Tetonia','ID'),('Teutopolis','IL'),('Tewksbury','MA'),('Texarkana','AR'),('Texarkana','TX'),('Texas City','TX'),('Texhoma','OK'),('Texico','IL'),('Texico','NM'),('Texline','TX'),('Texola','OK'),('Thackerville','OK'),('Thatcher','AZ'),('Thatcher','ID'),('Thawville','IL'),('Thaxton','MS'),('Thaxton','VA'),('Thayer','IA'),('Thayer','IL'),('Thayer','IN'),('Thayer','KS'),('Thayer','MO'),('Thayne','WY'),('The Colony','TX'),('The Dalles','OR'),('The Lakes','NV'),('The Plains','OH'),('The Plains','VA'),('The Rock','GA'),('The Sea Ranch','CA'),('The Villages','FL'),('Thebes','IL'),('Thedford','NE'),('Thelma','KY'),('Thendara','NY'),('Theodore','AL'),('Theodosia','MO'),('Theresa','NY'),('Theresa','WI'),('Theriot','LA'),('Thermal','CA'),('Thermopolis','WY'),('Thetford','VT'),('Thetford Center','VT'),('Thibodaux','LA'),('Thicket','TX'),('Thida','AR'),('Thief River Falls','MN'),('Thiells','NY'),('Thiensville','WI'),('Thomas','OK'),('Thomas','WV'),('Thomasboro','IL'),('Thomaston','AL'),('Thomaston','CT'),('Thomaston','GA'),('Thomaston','ME'),('Thomaston','TX'),('Thomastown','MS'),('Thomasville','AL'),('Thomasville','GA'),('Thomasville','NC'),('Thomasville','PA'),('Thompson','CT'),('Thompson','IA'),('Thompson','MO'),('Thompson','ND'),('Thompson','OH'),('Thompson','PA'),('Thompson','UT'),('Thompson Falls','MT'),('Thompson Ridge','NY'),('Thompsons','TX'),('Thompsons Station','TN'),('Thompsontown','PA'),('Thompsonville','IL'),('Thompsonville','MI'),('Thompsonville','NY'),('Thomson','GA'),('Thomson','IL'),('Thonotosassa','FL'),('Thor','IA'),('Thoreau','NM'),('Thorn Hill','TN'),('Thornburg','IA'),('Thornburg','VA'),('Thorndale','PA'),('Thorndale','TX'),('Thorndike','MA'),('Thorndike','ME'),('Thorne Bay','AK'),('Thornfield','MO'),('Thornton','AR'),('Thornton','CA'),('Thornton','IA'),('Thornton','IL'),('Thornton','KY'),('Thornton','NH'),('Thornton','PA'),('Thornton','TX'),('Thornton','WA'),('Thornton','WV'),('Thorntown','IN'),('Thornville','OH'),('Thornwood','NY'),('Thorofare','NJ'),('Thorp','WA'),('Thorp','WI'),('Thorpe','WV'),('Thorsby','AL'),('Thousand Island Park','NY'),('Thousand Oaks','CA'),('Thousand Palms','CA'),('Thousandsticks','KY'),('Thrall','TX'),('Three Bridges','NJ'),('Three Forks','MT'),('Three Lakes','WI'),('Three Mile Bay','NY'),('Three Oaks','MI'),('Three Rivers','CA'),('Three Rivers','MA'),('Three Rivers','MI'),('Three Rivers','TX'),('Three Springs','PA'),('Throckmorton','TX'),('Thurman','IA'),('Thurman','OH'),('Thurmond','NC'),('Thurmond','WV'),('Thurmont','MD'),('Thurston','NE'),('Thurston','OH'),('Thurston','OR'),('Tibbie','AL'),('Tichnor','AR'),('Tickfaw','LA'),('Ticonderoga','NY'),('Tidewater','OR'),('Tidioute','PA'),('Tie Plant','MS'),('Tie Siding','WY'),('Tierra Amarilla','NM'),('Tieton','WA'),('Tiff','MO'),('Tiff City','MO'),('Tiffin','IA'),('Tiffin','OH'),('Tifton','GA'),('Tiger','GA'),('Tigerton','WI'),('Tigerville','SC'),('Tignall','GA'),('Tigrett','TN'),('Tijeras','NM'),('Tilden','IL'),('Tilden','NE'),('Tilden','TX'),('Tilghman','MD'),('Tiline','KY'),('Tillamook','OR'),('Tillar','AR'),('Tillatoba','MS'),('Tilleda','WI'),('Tiller','OR'),('Tillery','NC'),('Tillman','SC'),('Tillson','NY'),('Tilly','AR'),('Tilton','IL'),('Tilton','NH'),('Tiltonsville','OH'),('Timber','OR'),('Timber Lake','SD'),('Timberlake','NC'),('Timberon','NM'),('Timberville','VA'),('Timblin','PA'),('Timbo','AR'),('Timewell','IL'),('Timmonsville','SC'),('Timnath','CO'),('Timpson','TX'),('Tina','MO'),('Tingley','IA'),('Tinker Afb','OK'),('Tinley Park','IL'),('Tinnie','NM'),('Tinsley','MS'),('Tintah','MN'),('Tioga','LA'),('Tioga','ND'),('Tioga','PA'),('Tioga','TX'),('Tioga','WV'),('Tioga Center','NY'),('Tiona','PA'),('Tionesta','PA'),('Tiplersville','MS'),('Tipp City','OH'),('Tippecanoe','IN'),('Tippecanoe','OH'),('Tippo','MS'),('Tipton','CA'),('Tipton','IA'),('Tipton','IN'),('Tipton','KS'),('Tipton','MI'),('Tipton','MO'),('Tipton','OK'),('Tipton','PA'),('Tipton','TN'),('Tiptonville','TN'),('Tire Hill','PA'),('Tiro','OH'),('Tisch Mills','WI'),('Tishomingo','MS'),('Tishomingo','OK'),('Tiskilwa','IL'),('Titonka','IA'),('Titus','AL'),('Titusville','FL'),('Titusville','NJ'),('Titusville','PA'),('Tiverton','RI'),('Tivoli','NY'),('Tivoli','TX'),('Toa Alta','PR'),('Toa Baja','PR'),('Toano','VA'),('Toast','NC'),('Tobaccoville','NC'),('Tobias','NE'),('Tobyhanna','PA'),('Toccoa','GA'),('Toccoa Falls','GA'),('Toccopola','MS'),('Todd','NC'),('Todd','PA'),('Toddville','IA'),('Toddville','MD'),('Toeterville','IA'),('Tofte','MN'),('Togiak','AK'),('Tohatchi','NM'),('Toivola','MI'),('Tok','AK'),('Tokeland','WA'),('Tokio','ND'),('Tokio','TX'),('Toksook Bay','AK'),('Tolar','TX'),('Toledo','IA'),('Toledo','IL'),('Toledo','OH'),('Toledo','OR'),('Toledo','WA'),('Tolland','CT'),('Tollesboro','KY'),('Tolleson','AZ'),('Tolley','ND'),('Tollhouse','CA'),('Tolna','ND'),('Tolono','IL'),('Tolovana Park','OR'),('Tolstoy','SD'),('Tolu','KY'),('Toluca','IL'),('Toluca Lake','CA'),('Tom Bean','TX'),('Tomah','WI'),('Tomahawk','KY'),('Tomahawk','WI'),('Tomales','CA'),('Tomball','TX'),('Tombstone','AZ'),('Tome','NM'),('Tomkins Cove','NY'),('Tompkinsville','KY'),('Toms Brook','VA'),('Toms River','NJ'),('Tonalea','AZ'),('Tonasket','WA'),('Tonawanda','NY'),('Toney','AL'),('Tonganoxie','KS'),('Tonica','IL'),('Tonkawa','OK'),('Tonopah','AZ'),('Tonopah','NV'),('Tontitown','AR'),('Tonto Basin','AZ'),('Tontogany','OH'),('Tony','WI'),('Tooele','UT'),('Toomsboro','GA'),('Toomsuba','MS'),('Toone','TN'),('Topanga','CA'),('Topawa','AZ'),('Topaz','CA'),('Topeka','IL'),('Topeka','IN'),('Topeka','KS'),('Topinabee','MI'),('Topmost','KY'),('Topock','AZ'),('Toponas','CO'),('Toppenish','WA'),('Topping','VA'),('Topsfield','MA'),('Topsfield','ME'),('Topsham','ME'),('Topsham','VT'),('Topton','NC'),('Topton','PA'),('Toquerville','UT'),('Tornado','WV'),('Tornillo','TX'),('Toronto','KS'),('Toronto','OH'),('Toronto','SD'),('Torrance','CA'),('Torrance','PA'),('Torreon','NM'),('Torrey','UT'),('Torrington','CT'),('Torrington','WY'),('Tortilla Flat','AZ'),('Toston','MT'),('Totowa','NJ'),('Totz','KY'),('Touchet','WA'),('Tougaloo','MS'),('Toughkenamon','PA'),('Toulon','IL'),('Toutle','WA'),('Tovey','IL'),('Tow','TX'),('Towaco','NJ'),('Towanda','IL'),('Towanda','KS'),('Towanda','PA'),('Towaoc','CO'),('Tower','MI'),('Tower','MN'),('Tower City','ND'),('Tower City','PA'),('Tower Hill','IL'),('Town Creek','AL'),('Towner','ND'),('Townley','AL'),('Townsend','DE'),('Townsend','GA'),('Townsend','MA'),('Townsend','MT'),('Townsend','TN'),('Townsend','VA'),('Townsend','WI'),('Townshend','VT'),('Township Of Washington','NJ'),('Townsville','NC'),('Townville','PA'),('Townville','SC'),('Towson','MD'),('Toxey','AL'),('Toyah','TX'),('Toyahvale','TX'),('Trabuco Canyon','CA'),('Tracy','CA'),('Tracy','IA'),('Tracy','MN'),('Tracy City','TN'),('Tracys Landing','MD'),('Tracyton','WA'),('Trade','TN'),('Traer','IA'),('Trafalgar','IN'),('Trafford','AL'),('Trafford','PA'),('Trail','MN'),('Trail','OR'),('Trail City','SD'),('Tram','KY'),('Trampas','NM'),('Tranquility','NJ'),('Tranquillity','CA'),('Transfer','PA'),('Transylvania','LA'),('Traphill','NC'),('Trappe','MD'),('Trapper Creek','AK'),('Traskwood','AR'),('Travelers Rest','SC'),('Traver','CA'),('Traverse City','MI'),('Travis Afb','CA'),('Treadwell','NY'),('Trebloc','MS'),('Treece','KS'),('Trego','MT'),('Trego','WI'),('Treichlers','PA'),('Treloar','MO'),('Trementina','NM'),('Tremont','IL'),('Tremont','MS'),('Tremont','PA'),('Tremont City','OH'),('Tremonton','UT'),('Trempealeau','WI'),('Trenary','MI'),('Trent','SD'),('Trent','TX'),('Trenton','AL'),('Trenton','FL'),('Trenton','GA'),('Trenton','IL'),('Trenton','KY'),('Trenton','MI'),('Trenton','MO'),('Trenton','NC'),('Trenton','ND'),('Trenton','NE'),('Trenton','NJ'),('Trenton','OH'),('Trenton','SC'),('Trenton','TN'),('Trenton','TX'),('Trenton','UT'),('Tres Piedras','NM'),('Tres Pinos','CA'),('Tresckow','PA'),('Trevett','ME'),('Trevilians','VA'),('Trevor','WI'),('Trevorton','PA'),('Trexlertown','PA'),('Treynor','IA'),('Trezevant','TN'),('Triadelphia','WV'),('Triangle','VA'),('Tribes Hill','NY'),('Tribune','KS'),('Tridell','UT'),('Trilby','FL'),('Trilla','IL'),('Trimble','MO'),('Trimble','OH'),('Trimble','TN'),('Trimont','MN'),('Trinchera','CO'),('Trinidad','CA'),('Trinidad','CO'),('Trinidad','TX'),('Trinity','AL'),('Trinity','NC'),('Trinity','TX'),('Trinity Center','CA'),('Trinway','OH'),('Trion','GA'),('Tripler Army Medical Center','HI'),('Triplett','MO'),('Tripoli','IA'),('Tripoli','WI'),('Tripp','SD'),('Triumph','IL'),('Trivoli','IL'),('Trona','CA'),('Tropic','UT'),('Trosky','MN'),('Trosper','KY'),('Troup','TX'),('Troupsburg','NY'),('Trout','LA'),('Trout Creek','MI'),('Trout Creek','MT'),('Trout Creek','NY'),('Trout Lake','MI'),('Trout Lake','WA'),('Trout Run','PA'),('Troutdale','OR'),('Troutdale','VA'),('Troutman','NC'),('Troutville','PA'),('Troutville','VA'),('Troxelville','PA'),('Troy','AL'),('Troy','ID'),('Troy','IL'),('Troy','IN'),('Troy','KS'),('Troy','ME'),('Troy','MI'),('Troy','MO'),('Troy','MT'),('Troy','NC'),('Troy','NH'),('Troy','NY'),('Troy','OH'),('Troy','PA'),('Troy','SC'),('Troy','TN'),('Troy','TX'),('Troy','VA'),('Troy','VT'),('Troy','WV'),('Troy Grove','IL'),('Troy Mills','IA'),('Truchas','NM'),('Truckee','CA'),('Truesdale','IA'),('Trufant','MI'),('Trujillo Alto','PR'),('Truman','MN'),('Trumann','AR'),('Trumansburg','NY'),('Trumbauersville','PA'),('Trumbull','CT'),('Trumbull','NE'),('Truro','IA'),('Truro','MA'),('Trussville','AL'),('Truth Or Consequences','NM'),('Truxton','MO'),('Truxton','NY'),('Tryon','NC'),('Tryon','NE'),('Tryon','OK'),('Tsaile','AZ'),('Tualatin','OR'),('Tuba City','AZ'),('Tubac','AZ'),('Tuckahoe','NJ'),('Tuckahoe','NY'),('Tuckasegee','NC'),('Tucker','AR'),('Tucker','GA'),('Tuckerman','AR'),('Tucson','AZ'),('Tucumcari','NM'),('Tujunga','CA'),('Tula','MS'),('Tulare','CA'),('Tulare','SD'),('Tularosa','NM'),('Tulelake','CA'),('Tuleta','TX'),('Tulia','TX'),('Tullahoma','TN'),('Tullos','LA'),('Tully','NY'),('Tulsa','OK'),('Tuluksak','AK'),('Tumacacori','AZ'),('Tumbling Shoals','AR'),('Tumtum','WA'),('Tumwater','WA'),('Tunas','MO'),('Tunbridge','VT'),('Tunica','LA'),('Tunica','MS'),('Tunkhannock','PA'),('Tunnel','NY'),('Tunnel City','WI'),('Tunnel Hill','GA'),('Tunnelton','IN'),('Tunnelton','WV'),('Tuntutuliak','AK'),('Tununak','AK'),('Tuolumne','CA'),('Tupelo','AR'),('Tupelo','MS'),('Tupelo','OK'),('Tupman','CA'),('Tupper Lake','NY'),('Tuppers Plains','OH'),('Turbeville','SC'),('Turbotville','PA'),('Turin','GA'),('Turin','NY'),('Turkey','NC'),('Turkey','TX'),('Turkey City','PA'),('Turkey Creek','LA'),('Turlock','CA'),('Turner','AR'),('Turner','ME'),('Turner','MI'),('Turner','MT'),('Turner','OR'),('Turners','MO'),('Turners Falls','MA'),('Turners Station','KY'),('Turnersburg','NC'),('Turnerville','GA'),('Turney','MO'),('Turon','KS'),('Turpin','OK'),('Turrell','AR'),('Turtle Creek','PA'),('Turtle Creek','WV'),('Turtle Lake','ND'),('Turtle Lake','WI'),('Turtlepoint','PA'),('Turtletown','TN'),('Turton','SD'),('Tuscaloosa','AL'),('Tuscarawas','OH'),('Tuscarora','MD'),('Tuscarora','NV'),('Tuscarora','PA'),('Tuscola','IL'),('Tuscola','MI'),('Tuscola','TX'),('Tuscumbia','AL'),('Tuscumbia','MO'),('Tuskahoma','OK'),('Tuskegee','AL'),('Tuskegee Institute','AL'),('Tussy','OK'),('Tustin','CA'),('Tustin','MI'),('Tuthill','SD'),('Tutor Key','KY'),('Tuttle','ND'),('Tuttle','OK'),('Tutwiler','MS'),('Tuxedo','NC'),('Tuxedo Park','NY'),('Twain','CA'),('Twain Harte','CA'),('Twelve Mile','IN'),('Twentynine Palms','CA'),('Twig','MN'),('Twilight','WV'),('Twin Bridges','CA'),('Twin Bridges','MT'),('Twin Brooks','SD'),('Twin City','GA'),('Twin Falls','ID'),('Twin Lake','MI'),('Twin Lakes','CO'),('Twin Lakes','MN'),('Twin Lakes','WI'),('Twin Mountain','NH'),('Twin Oaks','OK'),('Twin Peaks','CA'),('Twin Rocks','PA'),('Twin Valley','MN'),('Twining','MI'),('Twinsburg','OH'),('Twisp','WA'),('Two Buttes','CO'),('Two Dot','MT'),('Two Harbors','MN'),('Two Rivers','AK'),('Two Rivers','WI'),('Ty Ty','GA'),('Tyaskin','MD'),('Tybee Island','GA'),('Tye','TX'),('Tygh Valley','OR'),('Tyler','AL'),('Tyler','MN'),('Tyler','TX'),('Tyler Hill','PA'),('Tylersburg','PA'),('Tylersport','PA'),('Tylersville','PA'),('Tylerton','MD'),('Tylertown','MS'),('Tynan','TX'),('Tyndall','SD'),('Tyner','IN'),('Tyner','KY'),('Tyner','NC'),('Tyngsboro','MA'),('Tyonek','AK'),('Tyringham','MA'),('Tyro','KS'),('Tyro','VA'),('Tyrone','GA'),('Tyrone','NM'),('Tyrone','NY'),('Tyrone','OK'),('Tyrone','PA'),('Tyronza','AR'),('U S A F Academy','CO'),('Ubly','MI'),('Ucon','ID'),('Udall','KS'),('Udall','MO'),('Udell','IA'),('Uehling','NE'),('Uhrichsville','OH'),('Ukiah','CA'),('Ukiah','OR'),('Uledi','PA'),('Ulen','MN'),('Ullin','IL'),('Ulm','AR'),('Ulm','MT'),('Ulman','MO'),('Ulmer','SC'),('Ulster','PA'),('Ulster Park','NY'),('Ulysses','KS'),('Ulysses','KY'),('Ulysses','NE'),('Ulysses','PA'),('Umatilla','FL'),('Umatilla','OR'),('Umbarger','TX'),('Umpire','AR'),('Umpqua','OR'),('Una','SC'),('Unadilla','GA'),('Unadilla','NE'),('Unadilla','NY'),('Unalakleet','AK'),('Unalaska','AK'),('Uncasville','CT'),('Uncle Sam','LA'),('Underhill','VT'),('Underhill Center','VT'),('Underwood','IA'),('Underwood','IN'),('Underwood','MN'),('Underwood','ND'),('Underwood','WA'),('Uneeda','WV'),('Unicoi','TN'),('Union','IA'),('Union','IL'),('Union','KY'),('Union','ME'),('Union','MI'),('Union','MO'),('Union','MS'),('Union','NE'),('Union','NH'),('Union','NJ'),('Union','OR'),('Union','SC'),('Union','WA'),('Union','WV'),('Union Bridge','MD'),('Union Center','SD'),('Union Center','WI'),('Union Church','MS'),('Union City','CA'),('Union City','GA'),('Union City','IN'),('Union City','MI'),('Union City','NJ'),('Union City','OH'),('Union City','OK'),('Union City','PA'),('Union City','TN'),('Union Dale','PA'),('Union Furnace','OH'),('Union Grove','AL'),('Union Grove','NC'),('Union Grove','WI'),('Union Hall','VA'),('Union Hill','IL'),('Union Hill','NY'),('Union Lake','MI'),('Union Mills','IN'),('Union Mills','NC'),('Union Pier','MI'),('Union Point','GA'),('Union Springs','AL'),('Union Springs','NY'),('Union Star','KY'),('Union Star','MO'),('Uniondale','IN'),('Uniondale','NY'),('Uniontown','AL'),('Uniontown','AR'),('Uniontown','KS'),('Uniontown','KY'),('Uniontown','MO'),('Uniontown','OH'),('Uniontown','PA'),('Uniontown','WA'),('Unionville','CT'),('Unionville','IA'),('Unionville','IN'),('Unionville','MD'),('Unionville','MI'),('Unionville','MO'),('Unionville','NY'),('Unionville','OH'),('Unionville','PA'),('Unionville','TN'),('Unionville','VA'),('Unionville Center','OH'),('Uniopolis','OH'),('United','PA'),('Unity','IL'),('Unity','ME'),('Unity','OR'),('Unity','WI'),('Unity House','PA'),('Unityville','PA'),('Universal','IN'),('Universal City','CA'),('Universal City','TX'),('University','MS'),('University Center','MI'),('University Of Richmond','VA'),('University Park','IA'),('University Park','PA'),('University Place','WA'),('Upatoi','GA'),('Upham','ND'),('Upland','CA'),('Upland','IN'),('Upland','NE'),('Upper Black Eddy','PA'),('Upper Darby','PA'),('Upper Fairmount','MD'),('Upper Falls','MD'),('Upper Jay','NY'),('Upper Lake','CA'),('Upper Marlboro','MD'),('Upper Sandusky','OH'),('Upper Tract','WV'),('Upperco','MD'),('Upperglade','WV'),('Upperstrasburg','PA'),('Upperville','VA'),('Upsala','MN'),('Upson','WI'),('Upton','KY'),('Upton','MA'),('Upton','NY'),('Upton','WY'),('Urania','LA'),('Urbana','AR'),('Urbana','IA'),('Urbana','IL'),('Urbana','IN'),('Urbana','MO'),('Urbana','OH'),('Urbandale','IA'),('Urbanna','VA'),('Uriah','AL'),('Urich','MO'),('Ursa','IL'),('Ursina','PA'),('Usk','WA'),('Ute','IA'),('Ute Park','NM'),('Utica','IL'),('Utica','KS'),('Utica','KY'),('Utica','MI'),('Utica','MN'),('Utica','MO'),('Utica','MS'),('Utica','NE'),('Utica','NY'),('Utica','OH'),('Utica','PA'),('Utica','SD'),('Utopia','TX'),('Utuado','PR'),('Uvalda','GA'),('Uvalde','TX'),('Uwchland','PA'),('Uxbridge','MA'),('Vacaville','CA'),('Vacherie','LA'),('Vader','WA'),('Vadito','NM'),('Vado','NM'),('Vaiden','MS'),('Vail','AZ'),('Vail','CO'),('Vail','IA'),('Vails Gate','NY'),('Valatie','NY'),('Valders','WI'),('Valdese','NC'),('Valdez','AK'),('Valdez','NM'),('Valdosta','GA'),('Vale','NC'),('Vale','OR'),('Vale','SD'),('Valencia','CA'),('Valencia','PA'),('Valentine','AZ'),('Valentine','NE'),('Valentine','TX'),('Valentines','VA'),('Valera','TX'),('Valhalla','NY'),('Valhermoso Springs','AL'),('Valier','IL'),('Valier','MT'),('Valier','PA'),('Valle Crucis','NC'),('Vallecito','CA'),('Vallecitos','NM'),('Vallejo','CA'),('Valles Mines','MO'),('Valley','AL'),('Valley','NE'),('Valley','WA'),('Valley Bend','WV'),('Valley Center','CA'),('Valley Center','KS'),('Valley City','ND'),('Valley City','OH'),('Valley Cottage','NY'),('Valley Falls','KS'),('Valley Falls','NY'),('Valley Farms','AZ'),('Valley Ford','CA'),('Valley Forge','PA'),('Valley Grove','WV'),('Valley Head','AL'),('Valley Head','WV'),('Valley Lee','MD'),('Valley Mills','TX'),('Valley Park','MO'),('Valley Park','MS'),('Valley Spring','TX'),('Valley Springs','AR'),('Valley Springs','CA'),('Valley Springs','SD'),('Valley Stream','NY'),('Valley View','PA'),('Valley View','TX'),('Valley Village','CA'),('Valleyford','WA'),('Valliant','OK'),('Vallonia','IN'),('Valmeyer','IL'),('Valmora','NM'),('Valmy','NV'),('Valparaiso','FL'),('Valparaiso','IN'),('Valparaiso','NE'),('Valrico','FL'),('Valyermo','CA'),('Van','TX'),('Van','WV'),('Van Alstyne','TX'),('Van Buren','AR'),('Van Buren','IN'),('Van Buren','ME'),('Van Buren','MO'),('Van Buren','OH'),('Van Buren Point','NY'),('Van Dyne','WI'),('Van Etten','NY'),('Van Horn','TX'),('Van Horne','IA'),('Van Hornesville','NY'),('Van Lear','KY'),('Van Meter','IA'),('Van Nuys','CA'),('Van Orin','IL'),('Van Tassell','WY'),('Van Vleck','TX'),('Van Vleet','MS'),('Van Voorhis','PA'),('Van Wert','IA'),('Van Wert','OH'),('Van Wyck','SC'),('Vance','AL'),('Vance','MS'),('Vance','SC'),('Vanceboro','ME'),('Vanceboro','NC'),('Vanceburg','KY'),('Vancleave','MS'),('Vancleve','KY'),('Vancourt','TX'),('Vancouver','WA'),('Vandalia','IL'),('Vandalia','MI'),('Vandalia','MO'),('Vandalia','MT'),('Vandalia','OH'),('Vandemere','NC'),('Vanderbilt','MI'),('Vanderbilt','PA'),('Vanderbilt','TX'),('Vanderbilt Beach','FL'),('Vandergrift','PA'),('Vanderpool','TX'),('Vandervoort','AR'),('Vanderwagen','NM'),('Vandiver','AL'),('Vanduser','MO'),('Vanleer','TN'),('Vanlue','OH'),('Vanndale','AR'),('Vansant','VA'),('Vantage','WA'),('Vanzant','MO'),('Vardaman','MS'),('Varina','IA'),('Varna','IL'),('Varnado','LA'),('Varnell','GA'),('Varney','KY'),('Varney','WV'),('Varnville','SC'),('Varysburg','NY'),('Vashon','WA'),('Vass','NC'),('Vassalboro','ME'),('Vassar','KS'),('Vassar','MI'),('Vaucluse','SC'),('Vaughan','MS'),('Vaughan','NC'),('Vaughn','MT'),('Vaughn','NM'),('Vaughn','WA'),('Vaughnsville','OH'),('Vauxhall','NJ'),('Veblen','SD'),('Veedersburg','IN'),('Vega','TX'),('Vega Alta','PR'),('Vega Baja','PR'),('Veguita','NM'),('Velarde','NM'),('Velma','OK'),('Velpen','IN'),('Velva','ND'),('Venango','NE'),('Venango','PA'),('Vendor','AR'),('Venedocia','OH'),('Veneta','OR'),('Venetia','PA'),('Venetie','AK'),('Venice','CA'),('Venice','FL'),('Venice','IL'),('Venice','LA'),('Ventnor City','NJ'),('Ventress','LA'),('Ventura','CA'),('Ventura','IA'),('Venus','FL'),('Venus','PA'),('Venus','TX'),('Vera','OK'),('Veradale','WA'),('Verbank','NY'),('Verbena','AL'),('Verden','OK'),('Verdi','NV'),('Verdigre','NE'),('Verdon','NE'),('Verdugo City','CA'),('Verdunville','WV'),('Vergas','MN'),('Vergennes','IL'),('Vergennes','VT'),('Veribest','TX'),('Vermilion','IL'),('Vermilion','OH'),('Vermillion','KS'),('Vermillion','MN'),('Vermillion','SD'),('Vermont','IL'),('Vermontville','MI'),('Vermontville','NY'),('Vernal','UT'),('Vernalis','CA'),('Verndale','MN'),('Verner','WV'),('Vernon','AL'),('Vernon','AZ'),('Vernon','CO'),('Vernon','FL'),('Vernon','IL'),('Vernon','IN'),('Vernon','MI'),('Vernon','NJ'),('Vernon','NY'),('Vernon','TX'),('Vernon','UT'),('Vernon','VT'),('Vernon Center','MN'),('Vernon Center','NY'),('Vernon Hill','VA'),('Vernon Hills','IL'),('Vernon Rockville','CT'),('Vernonia','OR'),('Vero Beach','FL'),('Verona','IL'),('Verona','KY'),('Verona','MO'),('Verona','MS'),('Verona','ND'),('Verona','NJ'),('Verona','NY'),('Verona','OH'),('Verona','PA'),('Verona','VA'),('Verona','WI'),('Verona Beach','NY'),('Verplanck','NY'),('Versailles','CT'),('Versailles','IL'),('Versailles','IN'),('Versailles','KY'),('Versailles','MO'),('Versailles','NY'),('Versailles','OH'),('Vershire','VT'),('Vesper','WI'),('Vest','KY'),('Vesta','MN'),('Vesta','VA'),('Vestaburg','MI'),('Vestaburg','PA'),('Vestal','NY'),('Vesuvius','VA'),('Veteran','WY'),('Vevay','IN'),('Veyo','UT'),('Vian','OK'),('Viborg','SD'),('Viburnum','MO'),('Vicco','KY'),('Vichy','MO'),('Vici','OK'),('Vickery','OH'),('Vicksburg','MI'),('Vicksburg','MS'),('Vicksburg','PA'),('Victor','CA'),('Victor','CO'),('Victor','IA'),('Victor','ID'),('Victor','MT'),('Victor','NY'),('Victor','WV'),('Victoria','IL'),('Victoria','KS'),('Victoria','MN'),('Victoria','MS'),('Victoria','TX'),('Victoria','VA'),('Victorville','CA'),('Victory Mills','NY'),('Vida','MT'),('Vida','OR'),('Vidal','CA'),('Vidalia','GA'),('Vidalia','LA'),('Vidor','TX'),('Vienna','GA'),('Vienna','IL'),('Vienna','MD'),('Vienna','ME'),('Vienna','MO'),('Vienna','NJ'),('Vienna','OH'),('Vienna','SD'),('Vienna','VA'),('Vienna','WV'),('Vieques','PR'),('Viewtown','VA'),('Viking','MN'),('Vilas','CO'),('Vilas','NC'),('Villa Grande','CA'),('Villa Grove','CO'),('Villa Grove','IL'),('Villa Maria','PA'),('Villa Park','CA'),('Villa Park','IL'),('Villa Rica','GA'),('Villa Ridge','IL'),('Villa Ridge','MO'),('Village','VA'),('Village Mills','TX'),('Village Of Nagog Woods','MA'),('Villalba','PR'),('Villamont','VA'),('Villanova','PA'),('Villanueva','NM'),('Villard','MN'),('Villas','NJ'),('Ville Platte','LA'),('Villisca','IA'),('Vilonia','AR'),('Vina','AL'),('Vina','CA'),('Vinalhaven','ME'),('Vincennes','IN'),('Vincent','AL'),('Vincent','IA'),('Vincent','KY'),('Vincent','OH'),('Vine Grove','KY'),('Vineburg','CA'),('Vinegar Bend','AL'),('Vineland','NJ'),('Vinemont','AL'),('Vineyard Haven','MA'),('Vining','IA'),('Vining','MN'),('Vinita','OK'),('Vinson','OK'),('Vinton','CA'),('Vinton','IA'),('Vinton','LA'),('Vinton','OH'),('Vinton','VA'),('Vintondale','PA'),('Viola','AR'),('Viola','DE'),('Viola','IA'),('Viola','ID'),('Viola','IL'),('Viola','KS'),('Viola','TN'),('Viola','WI'),('Violet','LA'),('Violet Hill','AR'),('Viper','KY'),('Virden','IL'),('Virgie','KY'),('Virgil','KS'),('Virgil','SD'),('Virgilina','VA'),('Virgin','UT'),('Virginia','IL'),('Virginia','MN'),('Virginia','NE'),('Virginia Beach','VA'),('Virginia City','MT'),('Virginia City','NV'),('Virginville','PA'),('Viroqua','WI'),('Visalia','CA'),('Vista','CA'),('Vista','MO'),('Vivian','LA'),('Vivian','SD'),('Voca','TX'),('Volant','PA'),('Volborg','MT'),('Volcano','CA'),('Volcano','HI'),('Volga','IA'),('Volga','SD'),('Volga','WV'),('Volin','SD'),('Voltaire','ND'),('Voluntown','CT'),('Von Ormy','TX'),('Vona','CO'),('Vonore','TN'),('Voorhees','NJ'),('Voorheesville','NY'),('Voss','TX'),('Vossburg','MS'),('Votaw','TX'),('Vowinckel','PA'),('Vredenburgh','AL'),('Vulcan','MI'),('Vulcan','MO'),('Waban','MA'),('Wabash','AR'),('Wabash','IN'),('Wabasha','MN'),('Wabasso','FL'),('Wabasso','MN'),('Wabbaseka','AR'),('Wabeno','WI'),('Waccabuc','NY'),('Wachapreague','VA'),('Wacissa','FL'),('Waco','GA'),('Waco','KY'),('Waco','MO'),('Waco','NC'),('Waco','NE'),('Waco','TX'),('Waconia','MN'),('Waddell','AZ'),('Waddington','NY'),('Waddy','KY'),('Wade','NC'),('Wadena','IA'),('Wadena','MN'),('Wadesboro','NC'),('Wadesville','IN'),('Wading River','NY'),('Wadley','AL'),('Wadley','GA'),('Wadmalaw Island','SC'),('Wadsworth','IL'),('Wadsworth','NV'),('Wadsworth','OH'),('Wadsworth','TX'),('Waelder','TX'),('Wagarville','AL'),('Wagener','SC'),('Waggoner','IL'),('Wagner','SD'),('Wagon Mound','NM'),('Wagoner','OK'),('Wagontown','PA'),('Wagram','NC'),('Wahiawa','HI'),('Wahkiacus','WA'),('Wahkon','MN'),('Wahoo','NE'),('Wahpeton','ND'),('Waialua','HI'),('Waianae','HI'),('Waikoloa','HI'),('Wailuku','HI'),('Waimanalo','HI'),('Waimea','HI'),('Wainscott','NY'),('Wainwright','AK'),('Wainwright','OK'),('Waipahu','HI'),('Waite','ME'),('Waite Park','MN'),('Waiteville','WV'),('Waitsburg','WA'),('Waitsfield','VT'),('Waka','TX'),('Wakarusa','IN'),('Wakarusa','KS'),('Wake','VA'),('Wake Forest','NC'),('Wake Island','HI'),('Wakeeney','KS'),('Wakefield','KS'),('Wakefield','LA'),('Wakefield','MA'),('Wakefield','MI'),('Wakefield','NE'),('Wakefield','OH'),('Wakefield','RI'),('Wakefield','VA'),('Wakeman','OH'),('Wakita','OK'),('Wakonda','SD'),('Wakpala','SD'),('Walbridge','OH'),('Walburg','TX'),('Walcott','AR'),('Walcott','IA'),('Walcott','ND'),('Walcott','WY'),('Walden','CO'),('Walden','NY'),('Waldenburg','AR'),('Waldo','AR'),('Waldo','FL'),('Waldo','KS'),('Waldo','OH'),('Waldo','WI'),('Waldoboro','ME'),('Waldorf','MD'),('Waldorf','MN'),('Waldport','OR'),('Waldron','AR'),('Waldron','IN'),('Waldron','KS'),('Waldron','MI'),('Waldron','MO'),('Waldron','WA'),('Waldwick','NJ'),('Wales','AK'),('Wales','MA'),('Wales','ND'),('Wales','UT'),('Wales','WI'),('Wales Center','NY'),('Waleska','GA'),('Walford','IA'),('Walhalla','MI'),('Walhalla','ND'),('Walhalla','SC'),('Walhonding','OH'),('Walker','IA'),('Walker','KS'),('Walker','KY'),('Walker','LA'),('Walker','MN'),('Walker','MO'),('Walker','SD'),('Walker','WV'),('Walker Valley','NY'),('Walkersville','MD'),('Walkersville','WV'),('Walkerton','IN'),('Walkerton','VA'),('Walkertown','NC'),('Walkerville','MI'),('Wall','SD'),('Wall','TX'),('Wall Lake','IA'),('Walla Walla','WA'),('Wallace','CA'),('Wallace','ID'),('Wallace','IN'),('Wallace','KS'),('Wallace','MI'),('Wallace','NC'),('Wallace','NE'),('Wallace','SC'),('Wallace','SD'),('Wallace','WV'),('Wallaceton','PA'),('Wallagrass','ME'),('Walland','TN'),('Wallback','WV'),('Wallburg','NC'),('Walled Lake','MI'),('Waller','TX'),('Walling','TN'),('Wallingford','CT'),('Wallingford','IA'),('Wallingford','KY'),('Wallingford','PA'),('Wallingford','VT'),('Wallington','NJ'),('Wallins Creek','KY'),('Wallis','TX'),('Wallisville','TX'),('Wallkill','NY'),('Walloon Lake','MI'),('Wallops Island','VA'),('Wallowa','OR'),('Wallpack Center','NJ'),('Walls','MS'),('Wallsburg','UT'),('Wallula','WA'),('Walnut','CA'),('Walnut','IA'),('Walnut','IL'),('Walnut','KS'),('Walnut','MS'),('Walnut Bottom','PA'),('Walnut Cove','NC'),('Walnut Creek','CA'),('Walnut Creek','OH'),('Walnut Grove','AL'),('Walnut Grove','CA'),('Walnut Grove','MN'),('Walnut Grove','MO'),('Walnut Grove','MS'),('Walnut Hill','IL'),('Walnut Ridge','AR'),('Walnut Shade','MO'),('Walnut Springs','TX'),('Walnutport','PA'),('Walpole','MA'),('Walpole','ME'),('Walpole','NH'),('Walsenburg','CO'),('Walsh','CO'),('Walsh','IL'),('Walshville','IL'),('Walston','PA'),('Walstonburg','NC'),('Walterboro','SC'),('Walters','OK'),('Waltersburg','PA'),('Walterville','OR'),('Walthall','MS'),('Waltham','MA'),('Waltham','MN'),('Walthill','NE'),('Walthourville','GA'),('Walton','IN'),('Walton','KS'),('Walton','KY'),('Walton','NE'),('Walton','NY'),('Walton','OR'),('Walton','WV'),('Waltonville','IL'),('Walworth','NY'),('Walworth','WI'),('Wamego','KS'),('Wampsville','NY'),('Wampum','PA'),('Wamsutter','WY'),('Wana','WV'),('Wanakena','NY'),('Wanamingo','MN'),('Wanaque','NJ'),('Wanatah','IN'),('Wanblee','SD'),('Wanchese','NC'),('Wanda','MN'),('Waneta','KY'),('Wanette','OK'),('Wann','OK'),('Wannaska','MN'),('Wantagh','NY'),('Wapakoneta','OH'),('Wapanucka','OK'),('Wapato','WA'),('Wapella','IL'),('Wapello','IA'),('Wapiti','WY'),('Wappapello','MO'),('Wappingers Falls','NY'),('Wapwallopen','PA'),('War','WV'),('Warba','MN'),('Warbranch','KY'),('Ward','AL'),('Ward','AR'),('Ward','CO'),('Ward','SC'),('Ward Cove','AK'),('Warda','TX'),('Wardell','MO'),('Warden','WA'),('Wardensville','WV'),('Wardsboro','VT'),('Wardtown','VA'),('Wardville','OK'),('Ware','MA'),('Ware Neck','VA'),('Ware Shoals','SC'),('Wareham','MA'),('Waresboro','GA'),('Waretown','NJ'),('Warfield','KY'),('Warfield','VA'),('Warfordsburg','PA'),('Waring','TX'),('Warm Springs','AR'),('Warm Springs','GA'),('Warm Springs','MT'),('Warm Springs','OR'),('Warm Springs','VA'),('Warminster','PA'),('Warne','NC'),('Warner','NH'),('Warner','OK'),('Warner','SD'),('Warner Robins','GA'),('Warner Springs','CA'),('Warners','NY'),('Warnerville','NY'),('Warnock','OH'),('Warren','AR'),('Warren','ID'),('Warren','IL'),('Warren','IN'),('Warren','MA'),('Warren','ME'),('Warren','MI'),('Warren','MN'),('Warren','NH'),('Warren','NJ'),('Warren','OH'),('Warren','OR'),('Warren','PA'),('Warren','RI'),('Warren','TX'),('Warren','VT'),('Warren Center','PA'),('Warrendale','PA'),('Warrens','WI'),('Warrensburg','IL'),('Warrensburg','MO'),('Warrensburg','NY'),('Warrensville','NC'),('Warrenton','GA'),('Warrenton','MO'),('Warrenton','NC'),('Warrenton','OR'),('Warrenton','TX'),('Warrenton','VA'),('Warrenville','IL'),('Warrenville','SC'),('Warrington','PA'),('Warrior','AL'),('Warriormine','WV'),('Warriors Mark','PA'),('Warroad','MN'),('Warsaw','IL'),('Warsaw','IN'),('Warsaw','KY'),('Warsaw','MN'),('Warsaw','MO'),('Warsaw','NC'),('Warsaw','NY'),('Warsaw','OH'),('Warsaw','VA'),('Wartburg','TN'),('Warthen','GA'),('Wartrace','TN'),('Warwick','GA'),('Warwick','MA'),('Warwick','MD'),('Warwick','ND'),('Warwick','NY'),('Warwick','RI'),('Wasco','CA'),('Wasco','IL'),('Wasco','OR'),('Wascott','WI'),('Waseca','MN'),('Washburn','IL'),('Washburn','ME'),('Washburn','MO'),('Washburn','ND'),('Washburn','TN'),('Washburn','WI'),('Washington','AR'),('Washington','CA'),('Washington','CT'),('Washington','DC'),('Washington','GA'),('Washington','IA'),('Washington','IL'),('Washington','IN'),('Washington','KS'),('Washington','KY'),('Washington','LA'),('Washington','ME'),('Washington','MI'),('Washington','MO'),('Washington','MS'),('Washington','NC'),('Washington','NE'),('Washington','NH'),('Washington','NJ'),('Washington','OK'),('Washington','PA'),('Washington','TX'),('Washington','UT'),('Washington','VA'),('Washington','VT'),('Washington','WV'),('Washington Boro','PA'),('Washington Court House','OH'),('Washington Crossing','PA'),('Washington Depot','CT'),('Washington Grove','MD'),('Washington Island','WI'),('Washington Mills','NY'),('Washington Navy Yard','DC'),('Washingtonville','NY'),('Washingtonville','OH'),('Washingtonville','PA'),('Washita','OK'),('Washoe Valley','NV'),('Washougal','WA'),('Washta','IA'),('Washtucna','WA'),('Wasilla','AK'),('Waskish','MN'),('Waskom','TX'),('Wasola','MO'),('Wassaic','NY'),('Wasta','SD'),('Wataga','IL'),('Watauga','SD'),('Watauga','TN'),('Watchung','NJ'),('Water Mill','NY'),('Water Valley','KY'),('Water Valley','MS'),('Water Valley','TX'),('Water View','VA'),('Waterboro','ME'),('Waterbury','CT'),('Waterbury','NE'),('Waterbury','VT'),('Waterbury Center','VT'),('Waterfall','PA'),('Waterflow','NM'),('Waterford','CA'),('Waterford','CT'),('Waterford','ME'),('Waterford','MI'),('Waterford','MS'),('Waterford','NY'),('Waterford','OH'),('Waterford','PA'),('Waterford','VA'),('Waterford','WI'),('Waterford Works','NJ'),('Waterloo','AL'),('Waterloo','IA'),('Waterloo','IL'),('Waterloo','IN'),('Waterloo','NE'),('Waterloo','NY'),('Waterloo','OH'),('Waterloo','SC'),('Waterloo','WI'),('Waterman','IL'),('Waterport','NY'),('Waterproof','LA'),('Waters','MI'),('Watersmeet','MI'),('Watertown','CT'),('Watertown','MA'),('Watertown','MN'),('Watertown','NY'),('Watertown','OH'),('Watertown','SD'),('Watertown','TN'),('Watertown','WI'),('Waterville','IA'),('Waterville','KS'),('Waterville','ME'),('Waterville','MN'),('Waterville','NY'),('Waterville','OH'),('Waterville','PA'),('Waterville','VT'),('Waterville','WA'),('Waterville Valley','NH'),('Watervliet','MI'),('Watervliet','NY'),('Watford City','ND'),('Wathena','KS'),('Watkins','CO'),('Watkins','IA'),('Watkins','MN'),('Watkins Glen','NY'),('Watkinsville','GA'),('Watonga','OK'),('Watrous','NM'),('Watseka','IL'),('Watson','AL'),('Watson','AR'),('Watson','IL'),('Watson','LA'),('Watson','MN'),('Watson','MO'),('Watson','OK'),('Watsontown','PA'),('Watsonville','CA'),('Watton','MI'),('Watts','OK'),('Wattsburg','PA'),('Wattsville','AL'),('Wattsville','VA'),('Waubay','SD'),('Waubun','MN'),('Wauchula','FL'),('Waucoma','IA'),('Wauconda','IL'),('Wauconda','WA'),('Waukau','WI'),('Waukee','IA'),('Waukegan','IL'),('Waukena','CA'),('Waukesha','WI'),('Waukomis','OK'),('Waukon','IA'),('Wauna','WA'),('Waunakee','WI'),('Wauneta','NE'),('Waupaca','WI'),('Waupun','WI'),('Wauregan','CT'),('Waurika','OK'),('Wausa','NE'),('Wausau','FL'),('Wausau','WI'),('Wausaukee','WI'),('Wauseon','OH'),('Wautoma','WI'),('Wauzeka','WI'),('Waveland','IN'),('Waveland','MS'),('Waverley','MA'),('Waverly','AL'),('Waverly','FL'),('Waverly','GA'),('Waverly','IA'),('Waverly','IL'),('Waverly','KS'),('Waverly','KY'),('Waverly','MN'),('Waverly','MO'),('Waverly','NE'),('Waverly','NY'),('Waverly','OH'),('Waverly','PA'),('Waverly','TN'),('Waverly','VA'),('Waverly','WA'),('Waverly','WV'),('Waverly Hall','GA'),('Waves','NC'),('Wawaka','IN'),('Wawarsing','NY'),('Waxahachie','TX'),('Waxhaw','NC'),('Wayan','ID'),('Waycross','GA'),('Wayland','IA'),('Wayland','KY'),('Wayland','MA'),('Wayland','MI'),('Wayland','MO'),('Wayland','NY'),('Wayland','OH'),('Waymart','PA'),('Wayne','IL'),('Wayne','ME'),('Wayne','MI'),('Wayne','NE'),('Wayne','NJ'),('Wayne','NY'),('Wayne','OH'),('Wayne','OK'),('Wayne','PA'),('Wayne','WV'),('Wayne City','IL'),('Waynesboro','GA'),('Waynesboro','MS'),('Waynesboro','PA'),('Waynesboro','TN'),('Waynesboro','VA'),('Waynesburg','KY'),('Waynesburg','OH'),('Waynesburg','PA'),('Waynesfield','OH'),('Waynesville','GA'),('Waynesville','IL'),('Waynesville','MO'),('Waynesville','NC'),('Waynesville','OH'),('Waynetown','IN'),('Waynoka','OK'),('Wayside','MS'),('Wayside','TX'),('Wayside','WV'),('Wayzata','MN'),('Weare','NH'),('Weatherby','MO'),('Weatherford','OK'),('Weatherford','TX'),('Weatherly','PA'),('Weatogue','CT'),('Weaubleau','MO'),('Weaver','AL'),('Weaverville','CA'),('Weaverville','NC'),('Webb','AL'),('Webb','IA'),('Webb','MS'),('Webb City','MO'),('Webber','KS'),('Webbers Falls','OK'),('Webberville','MI'),('Webbville','KY'),('Weber City','VA'),('Webster','FL'),('Webster','IA'),('Webster','IN'),('Webster','KY'),('Webster','MA'),('Webster','MN'),('Webster','NC'),('Webster','ND'),('Webster','NY'),('Webster','PA'),('Webster','SD'),('Webster','TX'),('Webster','WI'),('Webster City','IA'),('Webster Springs','WV'),('Websterville','VT'),('Wedderburn','OR'),('Wedgefield','SC'),('Wedowee','AL'),('Wedron','IL'),('Weed','CA'),('Weed','NM'),('Weedsport','NY'),('Weedville','PA'),('Weehawken','NJ'),('Weeksbury','KY'),('Weems','VA'),('Weeping Water','NE'),('Weesatche','TX'),('Weidman','MI'),('Weikert','PA'),('Weimar','CA'),('Weimar','TX'),('Weiner','AR'),('Weinert','TX'),('Weippe','ID'),('Weir','KS'),('Weir','MS'),('Weir','TX'),('Weirsdale','FL'),('Weirton','WV'),('Weiser','ID'),('Welaka','FL'),('Welch','MN'),('Welch','OK'),('Welch','TX'),('Welch','WV'),('Welches','OR'),('Welchs Creek','KY'),('Welcome','MD'),('Welcome','MN'),('Welcome','NC'),('Weld','ME'),('Welda','KS'),('Weldon','CA'),('Weldon','IA'),('Weldon','IL'),('Weldon','NC'),('Weldona','CO'),('Weleetka','OK'),('Wellborn','FL'),('Wellborn','TX'),('Wellersburg','PA'),('Wellesley','MA'),('Wellesley Hills','MA'),('Wellesley Island','NY'),('Wellfleet','MA'),('Wellfleet','NE'),('Wellford','SC'),('Welling','OK'),('Wellington','AL'),('Wellington','CO'),('Wellington','IL'),('Wellington','KS'),('Wellington','KY'),('Wellington','MO'),('Wellington','NV'),('Wellington','OH'),('Wellington','TX'),('Wellington','UT'),('Wellman','IA'),('Wellman','TX'),('Wellpinit','WA'),('Wells','ME'),('Wells','MI'),('Wells','MN'),('Wells','NV'),('Wells','NY'),('Wells','TX'),('Wells','VT'),('Wells Bridge','NY'),('Wells River','VT'),('Wells Tannery','PA'),('Wellsboro','PA'),('Wellsburg','IA'),('Wellsburg','NY'),('Wellsburg','WV'),('Wellston','MI'),('Wellston','OH'),('Wellston','OK'),('Wellsville','KS'),('Wellsville','MO'),('Wellsville','NY'),('Wellsville','OH'),('Wellsville','PA'),('Wellsville','UT'),('Wellton','AZ'),('Welsh','LA'),('Welton','IA'),('Wenatchee','WA'),('Wendel','CA'),('Wendel','PA'),('Wendell','ID'),('Wendell','MA'),('Wendell','MN'),('Wendell','NC'),('Wendell Depot','MA'),('Wenden','AZ'),('Wendover','KY'),('Wendover','UT'),('Wenham','MA'),('Wenona','IL'),('Wenonah','NJ'),('Wentworth','MO'),('Wentworth','NC'),('Wentworth','NH'),('Wentworth','SD'),('Wentzville','MO'),('Weogufka','AL'),('Weott','CA'),('Wernersville','PA'),('Wesco','MO'),('Weskan','KS'),('Weslaco','TX'),('Wesley','AR'),('Wesley','IA'),('Wesley','ME'),('Wesley Chapel','FL'),('Wessington','SD'),('Wessington Springs','SD'),('Wesson','MS'),('West','MS'),('West','TX'),('West Alexander','PA'),('West Alexandria','OH'),('West Alton','MO'),('West Augusta','VA'),('West Babylon','NY'),('West Baden Springs','IN'),('West Baldwin','ME'),('West Barnstable','MA'),('West Bend','IA'),('West Bend','WI'),('West Berlin','NJ'),('West Bethel','ME'),('West Blocton','AL'),('West Bloomfield','MI'),('West Bloomfield','NY'),('West Boothbay Harbor','ME'),('West Boxford','MA'),('West Boylston','MA'),('West Branch','IA'),('West Branch','MI'),('West Bridgewater','MA'),('West Brookfield','MA'),('West Brooklyn','IL'),('West Burke','VT'),('West Burlington','IA'),('West Burlington','NY'),('West Camp','NY'),('West Charleston','VT'),('West Chatham','MA'),('West Chazy','NY'),('West Chester','IA'),('West Chester','OH'),('West Chester','PA'),('West Chesterfield','MA'),('West Chesterfield','NH'),('West Chicago','IL'),('West Chop','MA'),('West Clarksville','NY'),('West College Corner','IN'),('West Columbia','SC'),('West Columbia','TX'),('West Columbia','WV'),('West Concord','MN'),('West Copake','NY'),('West Cornwall','CT'),('West Covina','CA'),('West Coxsackie','NY'),('West Creek','NJ'),('West Danville','VT'),('West Davenport','NY'),('West Decatur','PA'),('West Dennis','MA'),('West Des Moines','IA'),('West Dover','VT'),('West Dummerston','VT'),('West Eaton','NY'),('West Edmeston','NY'),('West Elizabeth','PA'),('West Elkton','OH'),('West End','NC'),('West Enfield','ME'),('West Fairlee','VT'),('West Falls','NY'),('West Falmouth','MA'),('West Fargo','ND'),('West Farmington','ME'),('West Farmington','OH'),('West Finley','PA'),('West Fork','AR'),('West Forks','ME'),('West Frankfort','IL'),('West Friendship','MD'),('West Fulton','NY'),('West Glacier','MT'),('West Glover','VT'),('West Granby','CT'),('West Green','GA'),('West Greene','AL'),('West Greenwich','RI'),('West Groton','MA'),('West Grove','IA'),('West Grove','PA'),('West Halifax','VT'),('West Hamlin','WV'),('West Harrison','IN'),('West Harrison','NY'),('West Hartford','CT'),('West Hartford','VT'),('West Hartland','CT'),('West Harwich','MA'),('West Hatfield','MA'),('West Haven','CT'),('West Haverstraw','NY'),('West Helena','AR'),('West Hempstead','NY'),('West Henrietta','NY'),('West Hickory','PA'),('West Hills','CA'),('West Hollywood','CA'),('West Hurley','NY'),('West Hyannisport','MA'),('West Islip','NY'),('West Jefferson','NC'),('West Jefferson','OH'),('West Jordan','UT'),('West Kennebunk','ME'),('West Kill','NY'),('West Kingston','RI'),('West Lafayette','IN'),('West Lafayette','OH'),('West Lebanon','IN'),('West Lebanon','NH'),('West Lebanon','NY'),('West Lebanon','PA'),('West Leisenring','PA'),('West Leyden','NY'),('West Liberty','IA'),('West Liberty','IL'),('West Liberty','KY'),('West Liberty','OH'),('West Liberty','WV'),('West Linn','OR'),('West Long Branch','NJ'),('West Louisville','KY'),('West Manchester','OH'),('West Mansfield','OH'),('West Mclean','VA'),('West Medford','MA'),('West Memphis','AR'),('West Middlesex','PA'),('West Middleton','IN'),('West Middletown','PA'),('West Mifflin','PA'),('West Milford','NJ'),('West Milford','WV'),('West Millbury','MA'),('West Millgrove','OH'),('West Milton','OH'),('West Milton','PA'),('West Mineral','KS'),('West Minot','ME'),('West Monroe','LA'),('West Monroe','NY'),('West Mystic','CT'),('West New York','NJ'),('West Newbury','MA'),('West Newbury','VT'),('West Newfield','ME'),('West Newton','IN'),('West Newton','MA'),('West Newton','PA'),('West Nottingham','NH'),('West Nyack','NY'),('West Olive','MI'),('West Oneonta','NY'),('West Orange','NJ'),('West Ossipee','NH'),('West Paducah','KY'),('West Palm Beach','FL'),('West Paris','ME'),('West Park','NY'),('West Pawlet','VT'),('West Peterborough','NH'),('West Pittsburg','PA'),('West Plains','MO'),('West Point','AR'),('West Point','CA'),('West Point','GA'),('West Point','IA'),('West Point','IL'),('West Point','KY'),('West Point','MS'),('West Point','NE'),('West Point','NY'),('West Point','OH'),('West Point','PA'),('West Point','TX'),('West Point','VA'),('West Poland','ME'),('West Portsmouth','OH'),('West Richland','WA'),('West Ridge','AR'),('West River','MD'),('West Rockport','ME'),('West Roxbury','MA'),('West Rupert','VT'),('West Rushville','OH'),('West Rutland','VT'),('West Sacramento','CA'),('West Salem','IL'),('West Salem','OH'),('West Salem','WI'),('West Salisbury','PA'),('West Sand Lake','NY'),('West Sayville','NY'),('West Shokan','NY'),('West Simsbury','CT'),('West Somerset','KY'),('West Springfield','MA'),('West Springfield','PA'),('West Stewartstown','NH'),('West Stockbridge','MA'),('West Stockholm','NY'),('West Suffield','CT'),('West Sunbury','PA'),('West Swanzey','NH'),('West Terre Haute','IN'),('West Tisbury','MA'),('West Topsham','VT'),('West Townsend','MA'),('West Townshend','VT'),('West Union','IA'),('West Union','IL'),('West Union','MN'),('West Union','OH'),('West Union','SC'),('West Union','WV'),('West Unity','OH'),('West Valley','NY'),('West Van Lear','KY'),('West Wardsboro','VT'),('West Wareham','MA'),('West Warren','MA'),('West Warwick','RI'),('West Wendover','NV'),('West Willow','PA'),('West Windsor','NJ'),('West Winfield','NY'),('West Yarmouth','MA'),('West Yellowstone','MT'),('West York','IL'),('Westboro','MO'),('Westboro','WI'),('Westborough','MA'),('Westbrook','CT'),('Westbrook','ME'),('Westbrook','MN'),('Westbrook','TX'),('Westbrookville','NY'),('Westbury','NY'),('Westby','MT'),('Westby','WI'),('Westchester','IL'),('Westcliffe','CO'),('Westdale','NY'),('Westerlo','NY'),('Westerly','RI'),('Western','NE'),('Western Grove','AR'),('Western Springs','IL'),('Westernport','MD'),('Westernville','NY'),('Westerville','NE'),('Westerville','OH'),('Westfall','OR'),('Westfield','IA'),('Westfield','IL'),('Westfield','IN'),('Westfield','MA'),('Westfield','ME'),('Westfield','NC'),('Westfield','NJ'),('Westfield','NY'),('Westfield','PA'),('Westfield','VT'),('Westfield','WI'),('Westfield Center','OH'),('Westfir','OR'),('Westford','MA'),('Westford','NY'),('Westford','VT'),('Westgate','IA'),('Westhampton','NY'),('Westhampton Beach','NY'),('Westhoff','TX'),('Westhope','ND'),('Westlake','LA'),('Westlake','OH'),('Westlake','OR'),('Westlake Village','CA'),('Westland','MI'),('Westland','PA'),('Westley','CA'),('Westminster','CA'),('Westminster','CO'),('Westminster','MA'),('Westminster','MD'),('Westminster','SC'),('Westminster','TX'),('Westminster','VT'),('Westminster Station','VT'),('Westmont','IL'),('Westmoreland','KS'),('Westmoreland','NH'),('Westmoreland','NY'),('Westmoreland','TN'),('Westmoreland City','PA'),('Westmorland','CA'),('Weston','CO'),('Weston','CT'),('Weston','GA'),('Weston','ID'),('Weston','MA'),('Weston','MI'),('Weston','MO'),('Weston','NE'),('Weston','OH'),('Weston','OR'),('Weston','PA'),('Weston','TX'),('Weston','VT'),('Weston','WV'),('Weston','WY'),('Westons Mills','NY'),('Westover','AL'),('Westover','MD'),('Westover','PA'),('Westphalia','IA'),('Westphalia','IN'),('Westphalia','KS'),('Westphalia','MI'),('Westphalia','MO'),('Westpoint','IN'),('Westpoint','TN'),('Westport','CA'),('Westport','CT'),('Westport','IN'),('Westport','KY'),('Westport','MA'),('Westport','NY'),('Westport','PA'),('Westport','SD'),('Westport','TN'),('Westport','WA'),('Westport Point','MA'),('Westside','IA'),('Westtown','NY'),('Westtown','PA'),('Westview','KY'),('Westville','FL'),('Westville','IL'),('Westville','IN'),('Westville','NJ'),('Westville','OH'),('Westville','OK'),('Westville','SC'),('Westwego','LA'),('Westwood','CA'),('Westwood','MA'),('Westwood','NJ'),('Wethersfield','CT'),('Wetmore','CO'),('Wetmore','KS'),('Wetmore','MI'),('Wetumka','OK'),('Wetumpka','AL'),('Wever','IA'),('Wevertown','NY'),('Wewahitchka','FL'),('Wewoka','OK'),('Wexford','PA'),('Weyanoke','LA'),('Weyauwega','WI'),('Weyerhaeuser','WI'),('Weyers Cave','VA'),('Weymouth','MA'),('Whaleyville','MD'),('Wharncliffe','WV'),('Wharton','NJ'),('Wharton','OH'),('Wharton','TX'),('Wharton','WV'),('What Cheer','IA'),('Whately','MA'),('Whatley','AL'),('Wheat Ridge','CO'),('Wheatcroft','KY'),('Wheatfield','IN'),('Wheatland','CA'),('Wheatland','IA'),('Wheatland','IN'),('Wheatland','MO'),('Wheatland','ND'),('Wheatland','OK'),('Wheatland','PA'),('Wheatland','WY'),('Wheatley','AR'),('Wheaton','IL'),('Wheaton','MN'),('Wheaton','MO'),('Wheeler','IL'),('Wheeler','IN'),('Wheeler','MI'),('Wheeler','MS'),('Wheeler','OR'),('Wheeler','TX'),('Wheeler','WI'),('Wheeler Army Airfield','HI'),('Wheelersburg','OH'),('Wheeling','IL'),('Wheeling','MO'),('Wheeling','WV'),('Wheelock','TX'),('Wheelwright','KY'),('Wheelwright','MA'),('Whelen Springs','AR'),('Whick','KY'),('Whigham','GA'),('Whippany','NJ'),('Whipple','OH'),('Whippleville','NY'),('Whiskeytown','CA'),('Whitakers','NC'),('White','GA'),('White','PA'),('White','SD'),('White Bird','ID'),('White Bluff','TN'),('White Castle','LA'),('White City','KS'),('White City','OR'),('White Cloud','KS'),('White Cloud','MI'),('White Cottage','OH'),('White Deer','PA'),('White Deer','TX'),('White Earth','MN'),('White Earth','ND'),('White Hall','AR'),('White Hall','IL'),('White Hall','MD'),('White Hall','VA'),('White Haven','PA'),('White Heath','IL'),('White Horse Beach','MA'),('White House','TN'),('White Lake','MI'),('White Lake','NY'),('White Lake','SD'),('White Lake','WI'),('White Marsh','MD'),('White Marsh','VA'),('White Mills','KY'),('White Mills','PA'),('White Mountain','AK'),('White Mountain Lake','AZ'),('White Oak','GA'),('White Oak','NC'),('White Oak','TX'),('White Oak','WV'),('White Owl','SD'),('White Pigeon','MI'),('White Pine','MI'),('White Pine','TN'),('White Plains','GA'),('White Plains','KY'),('White Plains','MD'),('White Plains','NC'),('White Plains','NY'),('White Plains','VA'),('White Post','VA'),('White River','SD'),('White River Junction','VT'),('White Rock','SC'),('White Salmon','WA'),('White Sands Missile Range','NM'),('White Springs','FL'),('White Stone','SC'),('White Stone','VA'),('White Sulphur Springs','MT'),('White Sulphur Springs','NY'),('White Sulphur Springs','WV'),('White Swan','WA'),('White Water','CA'),('Whiteclay','NE'),('Whiteface','TX'),('Whitefield','ME'),('Whitefield','NH'),('Whitefield','OK'),('Whitefish','MT'),('Whiteford','MD'),('Whitehall','MI'),('Whitehall','MT'),('Whitehall','NY'),('Whitehall','PA'),('Whitehall','WI'),('Whitehorse','SD'),('Whitehouse','NJ'),('Whitehouse','OH'),('Whitehouse','TX'),('Whitehouse Station','NJ'),('Whiteland','IN'),('Whitelaw','WI'),('Whiteman Air Force Base','MO'),('Whiteoak','MO'),('Whiteriver','AZ'),('Whiterocks','UT'),('Whites City','NM'),('Whites Creek','TN'),('Whitesboro','NJ'),('Whitesboro','NY'),('Whitesboro','OK'),('Whitesboro','TX'),('Whitesburg','GA'),('Whitesburg','KY'),('Whitesburg','TN'),('Whiteside','MO'),('Whiteside','TN'),('Whitestone','NY'),('Whitestown','IN'),('Whitesville','KY'),('Whitesville','NY'),('Whitesville','WV'),('Whitetail','MT'),('Whitethorn','CA'),('Whitetop','VA'),('Whiteville','NC'),('Whiteville','TN'),('Whitewater','CO'),('Whitewater','KS'),('Whitewater','MO'),('Whitewater','MT'),('Whitewater','WI'),('Whitewood','SD'),('Whitewood','VA'),('Whitewright','TX'),('Whitfield','MS'),('Whitharral','TX'),('Whiting','IA'),('Whiting','IN'),('Whiting','KS'),('Whiting','ME'),('Whiting','VT'),('Whitingham','VT'),('Whitinsville','MA'),('Whitlash','MT'),('Whitley City','KY'),('Whitleyville','TN'),('Whitman','MA'),('Whitman','NE'),('Whitman','WV'),('Whitmer','WV'),('Whitmire','SC'),('Whitmore','CA'),('Whitmore Lake','MI'),('Whitney','NE'),('Whitney','PA'),('Whitney','TX'),('Whitney Point','NY'),('Whitsett','NC'),('Whitsett','TX'),('Whitt','TX'),('Whittaker','MI'),('Whittemore','IA'),('Whittemore','MI'),('Whitten','IA'),('Whittier','AK'),('Whittier','CA'),('Whittier','NC'),('Whittington','IL'),('Whitwell','TN'),('Wibaux','MT'),('Wichita','KS'),('Wichita Falls','TX'),('Wickatunk','NJ'),('Wickenburg','AZ'),('Wickes','AR'),('Wickett','TX'),('Wickhaven','PA'),('Wickliffe','KY'),('Wickliffe','OH'),('Wicomico','VA'),('Wicomico Church','VA'),('Wiconisco','PA'),('Wideman','AR'),('Widen','WV'),('Widener','AR'),('Widnoon','PA'),('Wiergate','TX'),('Wiggins','CO'),('Wiggins','MS'),('Wikieup','AZ'),('Wilber','NE'),('Wilberforce','OH'),('Wilbraham','MA'),('Wilbur','OR'),('Wilbur','WA'),('Wilburn','AR'),('Wilburton','OK'),('Wilburton','PA'),('Wilcoe','WV'),('Wilcox','NE'),('Wilcox','PA'),('Wild Horse','CO'),('Wild Rose','WI'),('Wilder','ID'),('Wilder','TN'),('Wilder','VT'),('Wildersville','TN'),('Wilderville','OR'),('Wildie','KY'),('Wildomar','CA'),('Wildorado','TX'),('Wildrose','ND'),('Wildsville','LA'),('Wildwood','FL'),('Wildwood','GA'),('Wildwood','NJ'),('Wildwood','PA'),('Wiley','CO'),('Wiley','GA'),('Wiley Ford','WV'),('Wileyville','WV'),('Wilkes Barre','PA'),('Wilkesboro','NC'),('Wilkeson','WA'),('Wilkesville','OH'),('Wilkinson','IN'),('Wilkinson','WV'),('Willacoochee','GA'),('Willamina','OR'),('Willard','KY'),('Willard','MO'),('Willard','MT'),('Willard','NC'),('Willard','NM'),('Willard','NY'),('Willard','OH'),('Willard','UT'),('Willard','WI'),('Willards','MD'),('Willcox','AZ'),('Willernie','MN'),('Willet','NY'),('Williams','AZ'),('Williams','CA'),('Williams','IA'),('Williams','IN'),('Williams','MN'),('Williams','OR'),('Williams','SC'),('Williams Bay','WI'),('Williamsburg','IA'),('Williamsburg','IN'),('Williamsburg','KS'),('Williamsburg','KY'),('Williamsburg','MA'),('Williamsburg','MI'),('Williamsburg','MO'),('Williamsburg','NM'),('Williamsburg','OH'),('Williamsburg','PA'),('Williamsburg','VA'),('Williamsburg','WV'),('Williamsfield','IL'),('Williamsfield','OH'),('Williamson','GA'),('Williamson','IA'),('Williamson','NY'),('Williamson','PA'),('Williamson','WV'),('Williamsport','IN'),('Williamsport','KY'),('Williamsport','MD'),('Williamsport','OH'),('Williamsport','PA'),('Williamsport','TN'),('Williamston','MI'),('Williamston','NC'),('Williamston','SC'),('Williamstown','KY'),('Williamstown','MA'),('Williamstown','MO'),('Williamstown','NJ'),('Williamstown','NY'),('Williamstown','OH'),('Williamstown','PA'),('Williamstown','VT'),('Williamstown','WV'),('Williamsville','IL'),('Williamsville','MO'),('Williamsville','VA'),('Williamsville','VT'),('Williford','AR'),('Willimantic','CT'),('Willingboro','NJ'),('Willington','CT'),('Willis','MI'),('Willis','TX'),('Willis','VA'),('Willis Wharf','VA'),('Willisburg','KY'),('Williston','FL'),('Williston','NC'),('Williston','ND'),('Williston','OH'),('Williston','SC'),('Williston','TN'),('Williston','VT'),('Williston Park','NY'),('Willisville','AR'),('Willisville','IL'),('Willits','CA'),('Willmar','MN'),('Willoughby','OH'),('Willow','AK'),('Willow','NY'),('Willow','OK'),('Willow Beach','AZ'),('Willow City','ND'),('Willow City','TX'),('Willow Creek','CA'),('Willow Creek','MT'),('Willow Grove','PA'),('Willow Hill','IL'),('Willow Hill','PA'),('Willow Island','NE'),('Willow Lake','SD'),('Willow River','MN'),('Willow Spring','NC'),('Willow Springs','IL'),('Willow Springs','MO'),('Willow Street','PA'),('Willow Wood','OH'),('Willowbrook','IL'),('Willows','CA'),('Wills Point','TX'),('Willsboro','NY'),('Willseyville','NY'),('Willshire','OH'),('Wilmar','AR'),('Wilmer','AL'),('Wilmer','TX'),('Wilmerding','PA'),('Wilmette','IL'),('Wilmington','CA'),('Wilmington','DE'),('Wilmington','IL'),('Wilmington','MA'),('Wilmington','NC'),('Wilmington','NY'),('Wilmington','OH'),('Wilmington','VT'),('Wilmont','MN'),('Wilmore','KS'),('Wilmore','KY'),('Wilmore','PA'),('Wilmot','AR'),('Wilmot','NH'),('Wilmot','OH'),('Wilmot','SD'),('Wilmot','WI'),('Wilsall','MT'),('Wilsey','KS'),('Wilseyville','CA'),('Wilson','AR'),('Wilson','KS'),('Wilson','LA'),('Wilson','MI'),('Wilson','NC'),('Wilson','NY'),('Wilson','OK'),('Wilson','TX'),('Wilson','WI'),('Wilson','WY'),('Wilson Creek','WA'),('Wilsonburg','WV'),('Wilsondale','WV'),('Wilsons','VA'),('Wilsons Mills','NC'),('Wilsonville','AL'),('Wilsonville','IL'),('Wilsonville','NE'),('Wilsonville','OR'),('Wilton','AL'),('Wilton','AR'),('Wilton','CA'),('Wilton','CT'),('Wilton','IA'),('Wilton','ME'),('Wilton','MN'),('Wilton','ND'),('Wilton','NH'),('Wilton','WI'),('Wimauma','FL'),('Wimberley','TX'),('Wimbledon','ND'),('Winamac','IN'),('Winburne','PA'),('Winchendon','MA'),('Winchendon Springs','MA'),('Winchester','AR'),('Winchester','CA'),('Winchester','ID'),('Winchester','IL'),('Winchester','IN'),('Winchester','KS'),('Winchester','KY'),('Winchester','MA'),('Winchester','NH'),('Winchester','OH'),('Winchester','OR'),('Winchester','TN'),('Winchester','VA'),('Winchester Center','CT'),('Wind Gap','PA'),('Wind Ridge','PA'),('Windber','PA'),('Winder','GA'),('Windermere','FL'),('Windfall','IN'),('Windham','CT'),('Windham','ME'),('Windham','NH'),('Windham','NY'),('Windham','OH'),('Windom','KS'),('Windom','MN'),('Windom','TX'),('Window Rock','AZ'),('Windsor','CA'),('Windsor','CO'),('Windsor','CT'),('Windsor','IL'),('Windsor','KY'),('Windsor','MA'),('Windsor','ME'),('Windsor','MO'),('Windsor','NC'),('Windsor','NJ'),('Windsor','NY'),('Windsor','OH'),('Windsor','PA'),('Windsor','SC'),('Windsor','VA'),('Windsor','VT'),('Windsor','WI'),('Windsor Heights','WV'),('Windsor Locks','CT'),('Windsor Mill','MD'),('Windthorst','TX'),('Windyville','MO'),('Winesburg','OH'),('Winfall','NC'),('Winfield','AL'),('Winfield','IA'),('Winfield','IL'),('Winfield','KS'),('Winfield','MO'),('Winfield','PA'),('Winfield','TN'),('Winfield','TX'),('Winfield','WV'),('Winfred','SD'),('Wing','AL'),('Wing','ND'),('Wingate','IN'),('Wingate','MD'),('Wingate','NC'),('Wingate','TX'),('Wingdale','NY'),('Winger','MN'),('Wingett Run','OH'),('Wingina','VA'),('Wingo','KY'),('Winifred','MT'),('Winifrede','WV'),('Winigan','MO'),('Wink','TX'),('Winkelman','AZ'),('Winlock','WA'),('Winn','ME'),('Winn','MI'),('Winnabow','NC'),('Winnebago','IL'),('Winnebago','MN'),('Winnebago','NE'),('Winnebago','WI'),('Winneconne','WI'),('Winnemucca','NV'),('Winner','SD'),('Winnetka','CA'),('Winnetka','IL'),('Winnetoon','NE'),('Winnett','MT'),('Winnfield','LA'),('Winnie','TX'),('Winnisquam','NH'),('Winnsboro','LA'),('Winnsboro','SC'),('Winnsboro','TX'),('Winona','KS'),('Winona','MN'),('Winona','MO'),('Winona','MS'),('Winona','OH'),('Winona','TX'),('Winona','WV'),('Winona Lake','IN'),('Winooski','VT'),('Winside','NE'),('Winslow','AR'),('Winslow','AZ'),('Winslow','IL'),('Winslow','IN'),('Winslow','NE'),('Winslow','NJ'),('Winsted','CT'),('Winsted','MN'),('Winston','GA'),('Winston','KY'),('Winston','MO'),('Winston','MT'),('Winston','NM'),('Winston','OR'),('Winston Salem','NC'),('Winstonville','MS'),('Winter','WI'),('Winter Beach','FL'),('Winter Garden','FL'),('Winter Harbor','ME'),('Winter Haven','FL'),('Winter Park','CO'),('Winter Park','FL'),('Winter Springs','FL'),('Winterhaven','CA'),('Winterport','ME'),('Winters','CA'),('Winters','TX'),('Winterset','IA'),('Winterthur','DE'),('Winterville','GA'),('Winterville','MS'),('Winterville','NC'),('Winthrop','AR'),('Winthrop','IA'),('Winthrop','MA'),('Winthrop','ME'),('Winthrop','MN'),('Winthrop','NY'),('Winthrop','WA'),('Winthrop Harbor','IL'),('Winton','CA'),('Winton','MN'),('Winton','NC'),('Wiota','IA'),('Wirt','MN'),('Wirtz','VA'),('Wiscasset','ME'),('Wisconsin Dells','WI'),('Wisconsin Rapids','WI'),('Wisdom','MT'),('Wise','NC'),('Wise','VA'),('Wise River','MT'),('Wiseman','AR'),('Wishek','ND'),('Wishon','CA'),('Wishram','WA'),('Wisner','LA'),('Wisner','NE'),('Wister','OK'),('Withams','VA'),('Withee','WI'),('Witherbee','NY'),('Witmer','PA'),('Witt','IL'),('Witten','SD'),('Wittenberg','WI'),('Wittensville','KY'),('Witter','AR'),('Witter Springs','CA'),('Wittman','MD'),('Wittmann','AZ'),('Witts Springs','AR'),('Wixom','MI'),('Woburn','MA'),('Woden','IA'),('Woden','TX'),('Wofford Heights','CA'),('Wolbach','NE'),('Wolcott','CO'),('Wolcott','CT'),('Wolcott','IN'),('Wolcott','NY'),('Wolcott','VT'),('Wolcottville','IN'),('Wolf','WY'),('Wolf Creek','MT'),('Wolf Creek','OR'),('Wolf Island','MO'),('Wolf Lake','IL'),('Wolf Lake','MN'),('Wolf Point','MT'),('Wolf Run','OH'),('Wolfcreek','WV'),('Wolfe','WV'),('Wolfe City','TX'),('Wolfeboro','NH'),('Wolfeboro Falls','NH'),('Wolfforth','TX'),('Wolflake','IN'),('Wolford','ND'),('Wolford','VA'),('Wolftown','VA'),('Wolsey','SD'),('Wolverine','MI'),('Wolverton','MN'),('Womelsdorf','PA'),('Wonalancet','NH'),('Wonder Lake','IL'),('Wonewoc','WI'),('Wood','PA'),('Wood','SD'),('Wood Dale','IL'),('Wood Lake','MN'),('Wood Lake','NE'),('Wood Ridge','NJ'),('Wood River','IL'),('Wood River','NE'),('Wood River Junction','RI'),('Woodacre','CA'),('Woodberry Forest','VA'),('Woodbine','GA'),('Woodbine','IA'),('Woodbine','KS'),('Woodbine','KY'),('Woodbine','MD'),('Woodbine','NJ'),('Woodbourne','NY'),('Woodbridge','CA'),('Woodbridge','CT'),('Woodbridge','NJ'),('Woodbridge','VA'),('Woodburn','IA'),('Woodburn','IN'),('Woodburn','KY'),('Woodburn','OR'),('Woodbury','CT'),('Woodbury','GA'),('Woodbury','KY'),('Woodbury','NJ'),('Woodbury','NY'),('Woodbury','PA'),('Woodbury','TN'),('Woodbury','VT'),('Woodbury Heights','NJ'),('Woodcliff Lake','NJ'),('Woodford','VA'),('Woodford','WI'),('Woodgate','NY'),('Woodhaven','NY'),('Woodhull','IL'),('Woodhull','NY'),('Woodinville','WA'),('Woodlake','CA'),('Woodlake','TX'),('Woodland','AL'),('Woodland','CA'),('Woodland','GA'),('Woodland','IL'),('Woodland','MI'),('Woodland','MS'),('Woodland','NC'),('Woodland','PA'),('Woodland','WA'),('Woodland','WI'),('Woodland Hills','CA'),('Woodland Mills','TN'),('Woodland Park','CO'),('Woodlawn','IL'),('Woodlawn','TN'),('Woodlawn','TX'),('Woodlawn','VA'),('Woodleaf','NC'),('Woodlyn','PA'),('Woodman','WI'),('Woodmere','NY'),('Woodridge','IL'),('Woodridge','NY'),('Woodrow','CO'),('Woodruff','AZ'),('Woodruff','SC'),('Woodruff','UT'),('Woodruff','WI'),('Woods Cross','UT'),('Woods Cross Roads','VA'),('Woods Hole','MA'),('Woodsboro','MD'),('Woodsboro','TX'),('Woodsfield','OH'),('Woodside','DE'),('Woodside','NY'),('Woodson','AR'),('Woodson','IL'),('Woodson','TX'),('Woodstock','AL'),('Woodstock','CT'),('Woodstock','GA'),('Woodstock','IL'),('Woodstock','MD'),('Woodstock','MN'),('Woodstock','NH'),('Woodstock','NY'),('Woodstock','OH'),('Woodstock','VA'),('Woodstock','VT'),('Woodstock Valley','CT'),('Woodston','KS'),('Woodstown','NJ'),('Woodsville','NH'),('Woodville','AL'),('Woodville','FL'),('Woodville','MA'),('Woodville','MS'),('Woodville','OH'),('Woodville','TX'),('Woodville','VA'),('Woodville','WI'),('Woodville','WV'),('Woodward','IA'),('Woodward','OK'),('Woodward','PA'),('Woodway','TX'),('Woodworth','LA'),('Woodworth','ND'),('Woodworth','WI'),('Woody','CA'),('Woody Creek','CO'),('Wooldridge','MO'),('Woolford','MD'),('Woollum','KY'),('Woolrich','PA'),('Woolstock','IA'),('Woolwich','ME'),('Woolwine','VA'),('Woonsocket','RI'),('Woonsocket','SD'),('Wooster','AR'),('Wooster','OH'),('Woosung','IL'),('Wooton','KY'),('Worcester','MA'),('Worcester','NY'),('Worcester','PA'),('Worcester','VT'),('Worden','IL'),('Worden','MT'),('Worland','WY'),('Worley','ID'),('Woronoco','MA'),('Worth','IL'),('Worth','MO'),('Wortham','TX'),('Worthing','SD'),('Worthington','IA'),('Worthington','IN'),('Worthington','KY'),('Worthington','MA'),('Worthington','MN'),('Worthington','MO'),('Worthington','PA'),('Worthington','WV'),('Worthington Springs','FL'),('Worthville','KY'),('Worthville','PA'),('Worton','MD'),('Wounded Knee','SD'),('Woxall','PA'),('Wrangell','AK'),('Wray','CO'),('Wray','GA'),('Wren','OH'),('Wrens','GA'),('Wrenshall','MN'),('Wrentham','MA'),('Wright','AR'),('Wright','KS'),('Wright','MN'),('Wright','WY'),('Wright City','MO'),('Wright City','OK'),('Wrights','IL'),('Wrightsboro','TX'),('Wrightstown','NJ'),('Wrightstown','WI'),('Wrightsville','AR'),('Wrightsville','GA'),('Wrightsville','PA'),('Wrightsville Beach','NC'),('Wrightwood','CA'),('Wrigley','KY'),('Wurtsboro','NY'),('Wyaconda','MO'),('Wyalusing','PA'),('Wyandanch','NY'),('Wyandotte','MI'),('Wyandotte','OK'),('Wyanet','IL'),('Wyano','PA'),('Wyarno','WY'),('Wyatt','IN'),('Wyatt','MO'),('Wyatt','WV'),('Wyckoff','NJ'),('Wyco','WV'),('Wycombe','PA'),('Wye Mills','MD'),('Wykoff','MN'),('Wylie','TX'),('Wylliesburg','VA'),('Wymore','NE'),('Wynantskill','NY'),('Wyncote','PA'),('Wyndmere','ND'),('Wynnburg','TN'),('Wynne','AR'),('Wynnewood','OK'),('Wynnewood','PA'),('Wynona','OK'),('Wynot','NE'),('Wyocena','WI'),('Wyola','MT'),('Wyoming','IA'),('Wyoming','IL'),('Wyoming','MI'),('Wyoming','MN'),('Wyoming','NY'),('Wyoming','PA'),('Wyoming','RI'),('Wyoming','WV'),('Wysox','PA'),('Wytheville','VA'),('Wytopitlock','ME'),('Xenia','IL'),('Xenia','OH'),('Yabucoa','PR'),('Yachats','OR'),('Yacolt','WA'),('Yadkinville','NC'),('Yakima','WA'),('Yakutat','AK'),('Yalaha','FL'),('Yale','IA'),('Yale','IL'),('Yale','MI'),('Yale','OK'),('Yale','SD'),('Yale','VA'),('Yamhill','OR'),('Yampa','CO'),('Yancey','TX'),('Yanceyville','NC'),('Yankeetown','FL'),('Yankton','SD'),('Yantic','CT'),('Yantis','TX'),('Yaphank','NY'),('Yarmouth','IA'),('Yarmouth','ME'),('Yarmouth Port','MA'),('Yarnell','AZ'),('Yatahey','NM'),('Yates Center','KS'),('Yates City','IL'),('Yatesboro','PA'),('Yatesville','GA'),('Yauco','PR'),('Yawkey','WV'),('Yazoo City','MS'),('Yeaddiss','KY'),('Yeagertown','PA'),('Yellow Jacket','CO'),('Yellow Pine','ID'),('Yellow Spring','WV'),('Yellow Springs','OH'),('Yellowstone National Park','WY'),('Yellowtail','MT'),('Yellville','AR'),('Yelm','WA'),('Yemassee','SC'),('Yeoman','IN'),('Yerington','NV'),('Yerkes','KY'),('Yermo','CA'),('Yeso','NM'),('Yettem','CA'),('Yoakum','TX'),('Yoder','CO'),('Yoder','IN'),('Yoder','KS'),('Yoder','WY'),('Yolo','CA'),('Yolyn','WV'),('Yoncalla','OR'),('Yonkers','NY'),('Yorba Linda','CA'),('York','AL'),('York','ME'),('York','ND'),('York','NE'),('York','NY'),('York','PA'),('York','SC'),('York Beach','ME'),('York Harbor','ME'),('York Haven','PA'),('York New Salem','PA'),('York Springs','PA'),('Yorklyn','DE'),('Yorkshire','NY'),('Yorkshire','OH'),('Yorktown','AR'),('Yorktown','IA'),('Yorktown','IN'),('Yorktown','TX'),('Yorktown','VA'),('Yorktown Heights','NY'),('Yorkville','CA'),('Yorkville','IL'),('Yorkville','NY'),('Yorkville','OH'),('Yorkville','TN'),('Yosemite','KY'),('Yosemite National Park','CA'),('Young','AZ'),('Young America','IN'),('Young America','MN'),('Young Harris','GA'),('Youngstown','FL'),('Youngstown','NY'),('Youngstown','OH'),('Youngstown','PA'),('Youngsville','LA'),('Youngsville','NC'),('Youngsville','NM'),('Youngsville','NY'),('Youngsville','PA'),('Youngtown','AZ'),('Youngwood','PA'),('Yountville','CA'),('Ypsilanti','MI'),('Ypsilanti','ND'),('Yreka','CA'),('Yuba City','CA'),('Yucaipa','CA'),('Yucca','AZ'),('Yucca Valley','CA'),('Yukon','MO'),('Yukon','OK'),('Yukon','PA'),('Yulan','NY'),('Yulee','FL'),('Yuma','AZ'),('Yuma','CO'),('Yuma','TN'),('Yutan','NE'),('Zacata','VA'),('Zachary','LA'),('Zachow','WI'),('Zahl','ND'),('Zaleski','OH'),('Zalma','MO'),('Zamora','CA'),('Zanesfield','OH'),('Zanesville','IN'),('Zanesville','OH'),('Zanoni','MO'),('Zap','ND'),('Zapata','TX'),('Zarephath','NJ'),('Zavalla','TX'),('Zearing','IA'),('Zebulon','GA'),('Zebulon','NC'),('Zeeland','MI'),('Zeeland','ND'),('Zeigler','IL'),('Zelienople','PA'),('Zellwood','FL'),('Zenda','KS'),('Zenda','WI'),('Zenia','CA'),('Zephyr','TX'),('Zephyr Cove','NV'),('Zephyrhills','FL'),('Zieglerville','PA'),('Zillah','WA'),('Zimmerman','MN'),('Zion','IL'),('Zion Grove','PA'),('Zionhill','PA'),('Zionsville','IN'),('Zionsville','PA'),('Zionville','NC'),('Zirconia','NC'),('Zoar','OH'),('Zoe','KY'),('Zolfo Springs','FL'),('Zortman','MT'),('Zullinger','PA'),('Zumbro Falls','MN'),('Zumbrota','MN'),('Zuni','NM'),('Zuni','VA'),('Zurich','MT'),('Zwingle','IA'),('Zwolle','LA'); +/*!40000 ALTER TABLE `cities` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2015-02-10 13:53:41 From 43cdf7975973e697098759d52ed2a14c04a95077 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 10 Feb 2015 14:05:13 -0500 Subject: [PATCH 074/200] Updated paths in before_scripts scripts for travis. --- test/create_mock_dbs.sh | 2 +- test/mysql/create_mysql_mock.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 810723b..7ff1cde 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -./mysql/create_mysql_mock.sh \ No newline at end of file +./test/mysql/create_mysql_mock.sh \ No newline at end of file diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 74e5237..73f56c2 100755 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash mysql -e 'create database mock_db;' -mysql -utravis mock_db < mock_data.sql \ No newline at end of file +mysql -utravis mock_db < ./test/mysql/mock_data.sql \ No newline at end of file From f101e98710e08561599581feacfe6debf852eea4 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 10 Feb 2015 15:24:13 -0500 Subject: [PATCH 075/200] Added additional tests for the QueryBuilder adapter. --- drivers/mysql/adapters.js | 1 + test/mysql/tests-adapter.js | 83 ++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 64656b3..7a8fb4f 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -136,6 +136,7 @@ var Adapters = function(nqb) { }, release: function() { + if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); pool.releaseConnection(connection); } }, qb, qe); diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 530a543..0ec3c40 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -12,7 +12,14 @@ var check = function(done, f) { } }; -describe('QueryBuilder() - MySQL', function() { +var connection_released = function(qb) { + var connection = qb.connection(); + expect(connection._pool._freeConnections).to.have.length(0); + qb.release(); + expect(connection._pool._freeConnections).to.have.length(1); +}; + +describe('QueryBuilder() - MySQL Adapter', function() { var on_connect = function(err) { if (err) { console.error("Not connected!"); return; } console.log("connected!"); @@ -38,6 +45,21 @@ describe('QueryBuilder() - MySQL', function() { it('should be a function', function() { nqb.QueryBuilder.should.be.a('function'); }); + it('should have all the QueryBuilder methods', function() { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; + expect(qb).to.include.keys(children); + }); + it('should have all the QueryExec methods', function() { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; + expect(qb).to.include.keys(children); + }); + it('should have all the miscellaneous methods', function() { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var children = ['last_query','escape','empty_table','truncate']; + expect(qb).to.include.keys(children); + }); it('should establish a single connection given valid connection credentials', function(done) { var qb = nqb.QueryBuilder(_.extend({}, settings), driver); expect(qb, 'should have connect property').to.have.property('connect'); @@ -196,4 +218,63 @@ describe('QueryBuilder() - MySQL', function() { }); }); }); + it('should allow us to execute a query', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", function(err, res) { + check(done, function() { + expect(err).to.not.be.instanceof(Error); + expect(res).to.not.be.empty; + expect(res).to.have.length(3); + }); + }); + }); + }); + it('should not be able to release a non-pooled connection', function(done) { + var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + qb.connect(function(err) { + check(done, function() { + expect(function() { qb.release(); }).to.throw(Error); + }); + }); + }); + it('should create a connection pool object if asked', function() { + var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + expect(pool).to.be.instanceof.object; + expect(pool).to.include.keys(['pool','get_connection','disconnect']); + pool.pool.should.be.a('function'); + pool.get_connection.should.be.a('function'); + pool.disconnect.should.be.a('function'); + }); + it('should create a QueryBuilder adapter when getting a connection from the pool', function(done) { + var qb2 = nqb.QueryBuilder(_.extend({}, settings), driver); + var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + pool.get_connection(function(qb) { + check(done, function() { + expect(qb).to.include.keys(Object.keys(qb2)); + }); + }); + }); + it('should allow one to release a connection from the pool', function(done) { + var qb2 = nqb.QueryBuilder(_.extend({}, settings), driver); + var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + pool.get_connection(function(qb) { + check(done, function() { connection_released(qb); }); + }); + }); + it('should allow one use the same connection pool connection for multiple queries', function(done) { + var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + + pool.get_connection(function(qb) { + qb.query('select * from `cities` where `city` = "Gainesville"', function(err, res) { + if (res.length > 0) { + qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', function(err, res) { + check(done, function() { connection_released(qb); }); + }); + } else { + check(done, function() { connection_released(qb); }); + } + }); + }); + }); }); From 7808b64db8c16d79891a7f1083e33301a337da01 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 11 Feb 2015 14:09:39 -0500 Subject: [PATCH 076/200] Updated some formatting of the documentation. --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dcbe199..e57eb4e 100755 --- a/README.md +++ b/README.md @@ -119,16 +119,16 @@ Standard Connection Settings The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. -| Option | Default | Optional | Description | -| :-------- | :-------- | :-------- | :-------------------------------------------- | -| host | localhost | No | The server you're connecting to | -| user | NULL | No | The database user | -| password | NULL | Yes | The database `user`'s password | -| database | NULL | Yes | The database to connect to | -| port | NULL | Yes | The database port to use when connecting | -| pool_size | 10 | Yes | Max connections for `pool` connection type | -| debug | false | Yes | If true, debug info will be place in app log | -| version | default | Yes | Version of database driver to use | +| Option | Default | Optional | Description | +| :------------ | :-------- | :-------- | :-------------------------------------------- | +| **host** | localhost | No | The server you're connecting to | +| **user** | NULL | No | The database user | +| **password** | NULL | Yes | The database `user`'s password | +| **database** | NULL | Yes | The database to connect to | +| **port** | NULL | Yes | The database port to use when connecting | +| **pool_size** | 10 | Yes | Max connections for `pool` connection type | +| **debug** | false | Yes | If true, debug info will be place in app log | +| **version** | default | Yes | Version of database driver to use | The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. @@ -184,12 +184,12 @@ Choosing the Connection Type This library currently supports 3 connection methods: -* single (default) +* ***single*** (default) * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. * **All drivers must have this connection type**. -* pool +* ***pool*** * This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. -* cluster +* ***cluster*** * When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. This is ideal for high-traffic web sites and applications that utilize a farm of database servers as opposed to just one. **Note:** From e7490a581bdf45256995a6a1472ff767b55129e3 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 11 Feb 2015 17:12:54 -0500 Subject: [PATCH 077/200] Fixed some checks in the querybuilder library. --- drivers/mysql/query_builder.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 1d728d7..50336c9 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1239,7 +1239,7 @@ var QueryBuilder = function() { table = table.trim(); - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } @@ -1274,7 +1274,7 @@ var QueryBuilder = function() { table = table.trim(); - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } @@ -1439,7 +1439,7 @@ var QueryBuilder = function() { table = table.trim(); // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("update(): You have not set any tables to update!"); } @@ -1514,7 +1514,7 @@ var QueryBuilder = function() { table = table.trim(); // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+$/)) { + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("update(): You have not set any tables to update!"); } From 463cfb172f437745f0b9a2d44e41f459a716f93f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 17 Feb 2015 10:53:29 -0500 Subject: [PATCH 078/200] Fixed some checks for valid where and set clauses. --- drivers/mysql/query_builder.js | 3419 ++++++++++++++++---------------- drivers/mysql/query_exec.js | 407 ++-- 2 files changed, 1914 insertions(+), 1912 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 50336c9..0e960fe 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,1709 +1,1710 @@ -var QueryBuilder = function() { - - // ------------------------------ GENERIC FUNCTIONS ------------------------------// - var array_values = function(item) { - var keys = Object.keys(item); - var length = keys.length; - var values = Array(length); - for (var i = 0; i < length; i++) { - values[i] = item[keys[i]]; - } - return values; - }; - - var prepare_for_limit_and_offset = function(item,type) { - type = type || 'limit'; - type = type.toLowerCase(); - - if ((typeof item).match(/^(string|number)$/) === null) { - throw new Error("Only integers or integers in the form of a string are allowed"); - } - - if (typeof item === 'string') { - item = item.trim(); - if (item.match(/^\d+$/) === null) { - throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); - } - // Force to an integer - item = parseInt(item); - } - - // Make sure the number is a good one - if (typeof item === 'number') { - // Check for NaN and Infinity - if (item !== +item || item === Infinity) { - throw new Error("You have not provided a valid number to " + type + " by!"); - } - - // Make sure it's positive - if (item < 0) { - throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); - } - - // Only allow integers - if (item % 1 !== 0) { - throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); - } - } - else { - throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); - } - - return item; - } - - var extract_having_parts = function(key,key_array) { - var m; - key = key.trim().replace(/\s+/g,' '); - var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; - - if (m = str_condition.exec(key)) { - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = sstr_condition.exec(key)) { - //console.log("Key has sstring value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = num_condition.exec(key)) { - //console.log("Key has numeric value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = bool_condition.exec(key)) { - //console.log("Key has boolean value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else { - throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); - } - - return key_array; - } - - // Simply setting all properties to [] causes reference issues in the parent class. - var clear_array = function(a,debug) { - if (debug === true) { - console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (var key in a) { - if (a.hasOwnProperty(key)) { - delete a[key]; - } - } - } - else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { - while (a.length > 0) { - a.pop(); - } - } - if (debug === true) { - console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - }; - - // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - var track_aliases = function(qb,table) { - if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (var i in table) { - var t = table[i]; - track_aliases(qb,t); - } - return; - } - - // Does the string contain a comma? If so, we need to separate - // the string into discreet statements - if (table.indexOf(',') !== -1) { - return track_aliases(qb,table.split(',')); - } - - // if a table alias is used we can recognize it by a space - if (table.indexOf(' ') !== -1) { - // if the alias is written with the AS keyword, remove it - table = table.replace(/\s+AS\s+/gi, ' '); - - // Grab the alias - var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); - - // Store the alias, if it doesn't already exist - if(qb.aliased_tables.indexOf(alias) == -1) { - qb.aliased_tables.push(alias); - } - } - }; - - var create_aliases_from_table = function(item) { - if (item.indexOf('.') !== -1) { - return item.split('.').reverse()[0]; - } - - return item; - }; - - var escape_identifiers = function(item) { - if (!item || item === '*') { - return item; - } - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (var i in item) { - item[i] = escape_identifiers(item[i]); - } - return item; - } - else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { - return item; - } - - var str; - if (item.indexOf('.' + '*') !== -1) { - str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); - } - else { - str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); - } - - // remove duplicates if the user already included the escape - return str.replace(/[`]+/g,'`'); - }; - - var protect_identifiers = function(qb,item,protect_identifiers) { - if (item === '') return item; - - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - var escaped_array = {}; - - for (k in item) { - var v = item[k]; - escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); - } - - return escaped_array; - } - - // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/g, ' '); - - // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - var has_alias = item.lastIndexOf(')'); - var alias; - if (has_alias >= 0) { - alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = escape_identifiers(alias); - if (alias != '') - alias = ' AS ' + alias; - item = item.substr(0,has_alias + 1); - } else { - alias = ''; - } - - return item + alias; - } - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (item.match(/\sAS\s/ig)) { - var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else if (item.indexOf(' ') !== -1) { - var alias_index = item.indexOf(' '); - - var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else { - var alias = ''; - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - var parts = item.split('.'); - var first_seg = parts[0].trim().replace(/`/g,''); - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (qb.aliased_tables.indexOf(first_seg) !== -1) { - if (protect_identifiers === true) { - for (var key in parts) { - var val = parts[key]; - if (val !== '*') { - parts[key] = escape_identifiers(val); - } - } - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = escape_identifiers(item); - } - - return item + alias; - } - if (protect_identifiers === true) { - item = escape_identifiers(item); - } - - return item + alias; - }; - - var has_operator = function (str) { - if(typeof str === 'string' && str.length > 0) { - var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); - if(match === null) { - return false; - } - } - return true; - }; - - var qb_escape = function(qb,str) { - var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); - var do_escape = SqlString.escape; - - if (typeof str === 'boolean') { - str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { - str *= 1; - } else { - str = do_escape(str); - } - - return str; - }; - - - // ---------------------------- SQL BUILD TOOLS ----------------------------// - var build_where_clause = function(qb) { - var sql = ''; - if(qb.where_array.length > 0) { - sql += " WHERE "; - } - sql += qb.where_array.join(" "); - return sql; - }; - - var build_from_clause = function(qb) { - var sql = ''; - if(qb.from_array.length > 0) { - sql += " FROM "; - } else { - throw new Error("You have not provided any tables, views, or store procedures for this query!!"); - } - sql += '(' + qb.from_array.join(', ') + ')'; - return sql; - }; - - var build_join_string = function(qb) { - var sql = ''; - sql += qb.join_array.join(' '); - if(sql.length > 0) sql = ' ' + sql; - return sql; - }; - - var build_group_by_clause = function(qb) { - if (qb.group_by_array.length <= 0) return ''; - - var sql = ' GROUP BY '; - sql += qb.group_by_array.join(', '); - return sql; - }; - - var build_having_clause = function(qb) { - if (qb.having_array.length <= 0) return ''; - - var sql = ' HAVING '; - sql += qb.having_array.join(' '); - return sql; - }; - - var build_order_by_clause = function(qb) { - if (qb.order_by_array.length <= 0) return ''; - - var sql = ' ORDER BY '; - sql += qb.order_by_array.join(', '); - - return sql; - }; - - var build_limit_clause = function(sql, limit, offset) { - if (!limit) return sql; - - sql += ' '; - - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; - } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; - }; - - var compile_select = function(qb) { - var distinct_clause = qb.distinct_clause[0] || ''; - var sql = 'SELECT ' + distinct_clause; - if (qb.select_array.length === 0) { - sql += '*'; - } else { - sql += qb.select_array.join(', '); - } - - sql += build_from_clause(qb) - + build_join_string(qb) - + build_where_clause(qb) - + build_group_by_clause(qb) - + build_having_clause(qb) - + build_order_by_clause(qb); - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - sql = build_limit_clause(sql,limit_to,offset_val); - return sql; - }; - - var compile_delete = function(qb) { - if (qb.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - qb.from_array = qb.from_array.slice(0,1); - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); - return build_limit_clause(sql,limit_to,offset_val); - }; - - var compile_update = function(qb) { - var valstr = []; - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; - valstr.push(key + ' = ' + val); - } - - if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); - } else { - if (qb.from_array.length === 0) { - throw new Error("You haven't provided any tables to build UPDATE query with!"); - return ''; - } - throw new Error("You have provided too many tables to build UPDATE query with!"); - return ''; - } - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); - sql += build_where_clause(qb); - sql += build_order_by_clause(qb); - return build_limit_clause(sql, limit_to, offset_val); - }; - - var compile_insert = function(qb, ignore, suffix) { - var keys = []; - var values = []; - suffix = suffix || ''; - - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); - } else { - if (qb.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + 'INTO (' + qb.from_array[0] + ') (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; - }; - - // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// - return { - where_array: [], - where_in_array: [], - from_array: [], - join_array: [], - select_array: [], - set_array: [], // has to be array to work as reference - order_by_array: [], - group_by_array: [], - having_array: [], - limit_to: [], // has to be array to work as reference - offset_val: [], // has to be array to work as reference - join_clause: [], - last_query_string: [], // has to be array to work as reference - distinct_clause: [], // has to be array to work as reference - aliased_tables: [], - - reset_query: function(new_last_query,debug) { - clear_array(this.where_array,debug); - clear_array(this.where_in_array); - clear_array(this.from_array); - clear_array(this.join_array); - clear_array(this.select_array); - clear_array(this.set_array); - clear_array(this.order_by_array); - clear_array(this.group_by_array); - clear_array(this.having_array); - clear_array(this.limit_to); - clear_array(this.offset_val); - clear_array(this.join_clause); - clear_array(this.distinct_clause); - clear_array(this.aliased_tables); - - clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - }, - - where: function(key, value, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); - } - - value = (typeof value === 'undefined' ? null : value); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - }, - - or_where: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - value = value || null; - - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); - } - return this._where(key, value, 'OR ', escape); - }, - - _where: function(key, value, type, escape) { - value = (typeof value === 'undefined' ? null : value); - type = type || 'AND '; - escape = (typeof escape === 'boolean' ? escape : true); - - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } - } - - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { - var filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - var that = this; - var parse_statement = function(statement,joiner) { - var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || joiner.match(/AND/i)) { - that.where(key, value, true); - } else { - that.or_where(key, value, true); - } - } - }; - parse_statement(filters.shift(),null); - while (filters.length > 0) { - var joiner = filters.shift(); - var statement = filters.shift(); - parse_statement(statement, joiner); - } - return this; - } - } - - var key_array = {}; - key_array[key] = value; - key = key_array; - } - - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); - } - - for (var k in key) { - var v = key[k]; - - if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { - return this._where_in(k,v,false,type,escape); - } - - var prefix = (this.where_array.length == 0 ? '' : type); - - if (v === null && !has_operator(k)) { - k += ' IS NULL'; - } - - if (v !== null) { - if (escape === true) { - k = protect_identifiers(this,k,escape); - v = ' ' + qb_escape(this,v); - } - - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + qb_escape(this,v); - } - - if (!has_operator(k)) { - k += ' ='; - } - } - else { - k = protect_identifiers(this,k,escape); - } - - if (v) { - this.where_array.push(prefix+k+v); - } - else { - this.where_array.push(prefix+k); - } - } - - return this; - }, - - where_in: function(key, values, escape) { - return this._where_in(key,values,false,'AND ', escape); - }, - - or_where_in: function(key, values, escape) { - return this._where_in(key,values,false,'OR ', escape); - }, - - where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'AND ', escape); - }, - - or_where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'OR ', escape); - }, - - _where_in: function(key, values, not, type, escape) { - key = key || ''; - values = values || []; - type = type || 'AND '; - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } - - // Values must be an array... - if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } - - for (var i in values) { - this.where_in_array.push(qb_escape(this,values[i])); - } - - var prefix = (this.where_array.length == 0 ? '' : type); - var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); - - // reset the array for multiple calls - clear_array(this.where_in_array); - return this; - }, - - like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - }, - - not_like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - }, - - or_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - }, - - or_not_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - }, - - _like: function(field, match, type, side, not) { - match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); - type = type || 'AND '; - side = (typeof side === 'undefined' ? 'both' : side); - not = not || ''; - - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if ((typeof field).match(/^(string|object)$/) === null) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } - - if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } - - var field_array = {}; - field_array[field] = match; - field = field_array; - } - - for(k in field) { - var like_statement; - var v = field[k]; - var k = protect_identifiers(this,k.trim()); - - // Make sure value is only string, number, or boolean - if ((typeof v).match(/^(string|number|boolean)$/) === null) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } - - this._where(like_statement,null,type,false); - } - - return this; - }, - - from: function(from) { - if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { - from = [from]; - } - for (var i in from) { - var val = from[i]; - - if (val.trim() === '') continue; - - if (val.indexOf(',') !== -1) { - var objects = val.split(','); - for (var j in objects) { - var v = objects[j].trim(); - - track_aliases(this,v); - - this.from_array.push(protect_identifiers(this,v,true)); - } - } - else { - val = val.trim(); - - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - track_aliases(this,val); - - this.from_array.push(protect_identifiers(this,val,true)); - } - } - - return this; - }, - - join: function(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - track_aliases(this,table); - - // Split multiple conditions - var regex = /\sAND\s|\sOR\s/ig; - var m = relation.match(regex); - var matches = []; - var k, temp, temp_match, match; - if (escape === true && m) { - while(k = regex.exec(relation)) { - matches.push(k); - } - - var new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); - } - else if (!has_operator(relation)) { - relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = protect_identifiers(this,table,true); - } - - var join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - }, - - select: function(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } - } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); - } - - if (typeof escape !== 'boolean') escape = true; - - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - var m, open_paren_index, inner_parenthesis; - var reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } - } - } - } - else { - select = [select]; - } - } - } - - for (var i in select) { - var val = select[i].trim(); - - if(val !== '') { - this.select_array.push(protect_identifiers(this,val,escape)); - } - } - return this; - }, - - select_min: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MIN'); - }, - - select_max: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MAX'); - }, - - select_avg: function(select,alias) { - return this._min_max_avg_sum(select,alias,'AVG'); - }, - - select_sum: function(select,alias) { - return this._min_max_avg_sum(select,alias,'SUM'); - }, - - _min_max_avg_sum: function(select,alias,type) { - select = select || ''; - alias = alias || ''; - type = type || 'MAX'; - - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } - - type = type.toUpperCase(); - - if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } - - if (alias == '') { - alias = create_aliases_from_table(select.trim()); - } - - var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; - - this.select_array.push(sql); - - return this; - }, - - distinct: function(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - - if (do_distinct) { - clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - clear_array(this.distinct_clause); - } - return this; - }, - - group_by: function(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } - - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - - for (var key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } - - var val = by[key].trim(); - - if (val !== '') { - this.group_by_array.push(protect_identifiers(this,val)); - } - } - return this; - }, - - having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - }, - - or_having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - }, - - _having: function(key, value, type, escape) { - type = type || 'AND '; - - var m; - var key_array = {}; - var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); - - if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); - } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; - } - else { - key = extract_having_parts(key,key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (var i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = extract_having_parts(key[i],key_array); - } - } - key = key_array; - } - } - } - else { - throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); - } - - - for (var k in key) { - var v = key[k]; - var prefix = (this.having_array.length == 0 ? '' : type); - - if (escape === true) { - k = protect_identifiers(this,k); - } - - if (v === null) { - k += ' IS'; - } - else if (!has_operator(k)) { - k += ' ='; - } - - if (v != '') { - v = ' ' + qb_escape(this,v); - } - - this.having_array.push(prefix + k + v); - } - - return this; - }, - - order_by: function(orderby, direction) { - var m; - var rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { - this.order_by_array.push(rand_word); - return this; - } - - // normalize orderby to be an array of items - if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } - } - - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } - - for (var i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); - - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; - } else { - if (direction.match(/^(ASC|DESC)$/i) || direction === '') { - orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } - } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); - } - - return this; - }, - - limit: function(limit, offset) { - clear_array(this.limit_to); - this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); - - if (offset !== undefined) { - return this.offset(offset); - } - - return this; - }, - - offset: function(offset) { - clear_array(this.offset_val); - this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); - return this; - }, - - set: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) { - throw new Error("set(): Invalid field name provided!"); - } - - var key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } - - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } - } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); - } - - - // Add each key:value pair to the set_array - for (var i in key) { - var v = key[i]; - - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - - // Escape the key to be DRY - var escaped_key = protect_identifiers(this,i,escape); - - // Build a temporary object with escaped key and val - var temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = qb_escape(this,v); - } - - // Determine if this key has already been set - var found_index = null; - for (var j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } - } - - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } - } - - //console.dir(this.set_array); - - return this; - }, - - insert: function(table, set, ignore, suffix) { - table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } - - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - return this.insert_batch(table, set, ignore, suffix); - } - - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('insert(): No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - return compile_insert(this, ignore, suffix); - }, - - insert_ignore: function(table, set, suffix) { - return this.insert(table, set, true, suffix); - }, - - insert_batch: function(table,set,ignore,suffix) { - var self = this; - var orig_table = table = table || ''; - set = set || null; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); - } - - for (var key in set) { - var row = set[key]; - var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (var i in row) { - var v = row[i]; - - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - } - } - } - - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } - - var map = []; - var columns = []; - - // Obtain all the column names - for (var key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(protect_identifiers(this,key)); - } - } - } - - for (var i = 0; i < set.length; i++) { - (function(i,qb) { - var row = []; - for (var key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(qb_escape(qb,set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); - } - map.push('(' + row.join(', ') + ')'); - })(i,self); - } - - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO (' + this.from_array[0] + ') (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; - }, - - get: function(table) { - if (typeof table !== 'undefined') { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } - } - return compile_select(this); - }, - - get_where: function(table, where) { - table = table || null; - where = where || null; - - if (table === null || (typeof table === 'string' && table.trim().length === 0)) { - throw new Error('You must specify a table or tables in the first parameter of get_where()'); - } - else { - this.from(table); - } - - if (where === null || Object.keys(where).length === 0) { - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - } - else { - this.where(where); - } - - return compile_select(this); - }, - - count: function(table) { - if (typeof table === 'string') { - this.from(table); - } - - var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') - + build_from_clause(this) - + build_join_string(this) - + build_where_clause(this); - - return sql; - }, - - update: function(table, set, where) { - table = table || ''; - set = set || null; - where = where || null; - - // Send to batch_update if the data param is an array - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - var index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); - } - } - - // If set is a number, boolean, a non-empty string, or regex, fail - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); - } - - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); - } - - return compile_update(this); - }, - - update_batch: function(table, set, index, where) { - table = table || ''; - set = set || null; - index = index || null; - where = where || null; - - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } - - // Check to make sure we have a dataset - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } - - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } - - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (var i in set) { - var clean = {}; - var row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - var keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (var j in row) { - clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); - } - this.set_array.push(clean); - } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } - - // Verify there is a table in the from_array - if (this.from_array.length === 1) { - var table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); - } - - - // Limit to 100 rows per batch - var batches = []; - for (var i = 0, total = this.set_array.length; i < total; i += 100) { - var when_then = {}; - var ids = []; - var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); - var chunk = this.set_array.slice(i,100); - - // Escape the index - index = protect_identifiers(this, index); - - for (var j in chunk) { - ids.push(chunk[j][index]); - - var keys = Object.keys(chunk[j]); - for (var k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); - } - } - } - - // Build the actual SQL statement - var sql = 'UPDATE (' + table + ') SET '; - var cases = ''; - - for (var l in when_then) { - cases += l + ' = CASE '; - - for (var m in when_then[l]) { - cases += when_then[l][m]; - } - - cases += 'ELSE ' + l + ' END, '; - } - - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); - } - - return batches; - }, - - delete: function(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } - - return compile_delete(this); - }, - - get_compiled_select: function(table) { - if (typeof table !== 'undefined') { - track_aliases(this,table); - this.from(table); - } - else { - if (from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } - } - - return compile_select(this); - }, - - get_compiled_delete: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - - return compile_delete(this); - }, - - get_compiled_update: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_update(this); - }, - - get_compiled_insert: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_insert(this); - }, - - compile_select: function(table) { - return this.get_compiled_select(table); - }, - - compile_delete: function(table) { - return this.get_compiled_delete(table); - }, - - compile_update: function(table) { - return this.get_compiled_update(table); - }, - - compile_insert: function(table) { - return this.get_compiled_insert(table); - }, - - last_query: function() { - return this.last_query_string[0] || ''; - }, - - escape: function(val) { - return qb_escape(this, val); - }, - - empty_table: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } - - return "DELETE FROM " + this.from_array[0]; - }, - - truncate: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } - - return "TRUNCATE " + this.from_array[0]; - }, - } -}; - -exports.QueryBuilder = QueryBuilder; \ No newline at end of file +var QueryBuilder = function() { + + // ------------------------------ GENERIC FUNCTIONS ------------------------------// + var array_values = function(item) { + var keys = Object.keys(item); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = item[keys[i]]; + } + return values; + }; + + var prepare_for_limit_and_offset = function(item,type) { + type = type || 'limit'; + type = type.toLowerCase(); + + if ((typeof item).match(/^(string|number)$/) === null) { + throw new Error("Only integers or integers in the form of a string are allowed"); + } + + if (typeof item === 'string') { + item = item.trim(); + if (item.match(/^\d+$/) === null) { + throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); + } + // Force to an integer + item = parseInt(item); + } + + // Make sure the number is a good one + if (typeof item === 'number') { + // Check for NaN and Infinity + if (item !== +item || item === Infinity) { + throw new Error("You have not provided a valid number to " + type + " by!"); + } + + // Make sure it's positive + if (item < 0) { + throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); + } + + // Only allow integers + if (item % 1 !== 0) { + throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); + } + } + else { + throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); + } + + return item; + } + + var extract_having_parts = function(key,key_array) { + var m; + key = key.trim().replace(/\s+/g,' '); + var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; + + if (m = str_condition.exec(key)) { + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = sstr_condition.exec(key)) { + //console.log("Key has sstring value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = num_condition.exec(key)) { + //console.log("Key has numeric value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = bool_condition.exec(key)) { + //console.log("Key has boolean value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else { + throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); + } + + return key_array; + } + + // Simply setting all properties to [] causes reference issues in the parent class. + var clear_array = function(a,debug) { + if (debug === true) { + console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { + for (var key in a) { + if (a.hasOwnProperty(key)) { + delete a[key]; + } + } + } + else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { + while (a.length > 0) { + a.pop(); + } + } + if (debug === true) { + console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + }; + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + var track_aliases = function(qb,table) { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (var i in table) { + var t = table[i]; + track_aliases(qb,t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return track_aliases(qb,table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); + + // Store the alias, if it doesn't already exist + if(qb.aliased_tables.indexOf(alias) == -1) { + qb.aliased_tables.push(alias); + } + } + }; + + var create_aliases_from_table = function(item) { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + }; + + var escape_identifiers = function(item) { + if (!item || item === '*') { + return item; + } + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + for (var i in item) { + item[i] = escape_identifiers(item[i]); + } + return item; + } + else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + return item; + } + + var str; + if (item.indexOf('.' + '*') !== -1) { + str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); + } + else { + str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); + } + + // remove duplicates if the user already included the escape + return str.replace(/[`]+/g,'`'); + }; + + var protect_identifiers = function(qb,item,protect_identifiers) { + if (item === '') return item; + + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + var escaped_array = {}; + + for (k in item) { + var v = item[k]; + escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); + } + + return escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + item = item.replace(/\s+/g, ' '); + + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + var has_alias = item.lastIndexOf(')'); + var alias; + if (has_alias >= 0) { + alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); + alias = escape_identifiers(alias); + if (alias != '') + alias = ' AS ' + alias; + item = item.substr(0,has_alias + 1); + } else { + alias = ''; + } + + return item + alias; + } + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (item.match(/\sAS\s/ig)) { + var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else if (item.indexOf(' ') !== -1) { + var alias_index = item.indexOf(' '); + + var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else { + var alias = ''; + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + var parts = item.split('.'); + var first_seg = parts[0].trim().replace(/`/g,''); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (qb.aliased_tables.indexOf(first_seg) !== -1) { + if (protect_identifiers === true) { + for (var key in parts) { + var val = parts[key]; + if (val !== '*') { + parts[key] = escape_identifiers(val); + } + } + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + } + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + }; + + var has_operator = function (str) { + if(typeof str === 'string' && str.length > 0) { + var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); + if(match === null) { + return false; + } + } + return true; + }; + + var qb_escape = function(qb,str) { + var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); + var do_escape = SqlString.escape; + + if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { + str *= 1; + } else { + str = do_escape(str); + } + + return str; + }; + + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + var build_where_clause = function(qb) { + var sql = ''; + if(qb.where_array.length > 0) { + sql += " WHERE "; + } + sql += qb.where_array.join(" "); + return sql; + }; + + var build_from_clause = function(qb) { + var sql = ''; + if(qb.from_array.length > 0) { + sql += " FROM "; + } else { + throw new Error("You have not provided any tables, views, or store procedures for this query!!"); + } + sql += '(' + qb.from_array.join(', ') + ')'; + return sql; + }; + + var build_join_string = function(qb) { + var sql = ''; + sql += qb.join_array.join(' '); + if(sql.length > 0) sql = ' ' + sql; + return sql; + }; + + var build_group_by_clause = function(qb) { + if (qb.group_by_array.length <= 0) return ''; + + var sql = ' GROUP BY '; + sql += qb.group_by_array.join(', '); + return sql; + }; + + var build_having_clause = function(qb) { + if (qb.having_array.length <= 0) return ''; + + var sql = ' HAVING '; + sql += qb.having_array.join(' '); + return sql; + }; + + var build_order_by_clause = function(qb) { + if (qb.order_by_array.length <= 0) return ''; + + var sql = ' ORDER BY '; + sql += qb.order_by_array.join(', '); + + return sql; + }; + + var build_limit_clause = function(sql, limit, offset) { + if (!limit) return sql; + + sql += ' '; + + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + }; + + var compile_select = function(qb) { + var distinct_clause = qb.distinct_clause[0] || ''; + var sql = 'SELECT ' + distinct_clause; + if (qb.select_array.length === 0) { + sql += '*'; + } else { + sql += qb.select_array.join(', '); + } + + sql += build_from_clause(qb) + + build_join_string(qb) + + build_where_clause(qb) + + build_group_by_clause(qb) + + build_having_clause(qb) + + build_order_by_clause(qb); + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + sql = build_limit_clause(sql,limit_to,offset_val); + return sql; + }; + + var compile_delete = function(qb) { + if (qb.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } + + qb.from_array = qb.from_array.slice(0,1); + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); + return build_limit_clause(sql,limit_to,offset_val); + }; + + var compile_update = function(qb) { + var valstr = []; + for (var i in qb.set_array) { + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + valstr.push(key + ' = ' + val); + } + + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); + sql += build_where_clause(qb); + sql += build_order_by_clause(qb); + return build_limit_clause(sql, limit_to, offset_val); + }; + + var compile_insert = function(qb, ignore, suffix) { + var keys = []; + var values = []; + suffix = suffix || ''; + + for (var i in qb.set_array) { + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; + } + + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')';// + suffix; + }; + + // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// + return { + where_array: [], + where_in_array: [], + from_array: [], + join_array: [], + select_array: [], + set_array: [], // has to be array to work as reference + order_by_array: [], + group_by_array: [], + having_array: [], + limit_to: [], // has to be array to work as reference + offset_val: [], // has to be array to work as reference + join_clause: [], + last_query_string: [], // has to be array to work as reference + distinct_clause: [], // has to be array to work as reference + aliased_tables: [], + + reset_query: function(new_last_query,debug) { + clear_array(this.where_array,debug); + clear_array(this.where_in_array); + clear_array(this.from_array); + clear_array(this.join_array); + clear_array(this.select_array); + clear_array(this.set_array); + clear_array(this.order_by_array); + clear_array(this.group_by_array); + clear_array(this.having_array); + clear_array(this.limit_to); + clear_array(this.offset_val); + clear_array(this.join_clause); + clear_array(this.distinct_clause); + clear_array(this.aliased_tables); + + clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + }, + + where: function(key, value, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + value = (typeof value === 'undefined' ? null : value); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + }, + + or_where: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + value = value || null; + + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + }, + + _where: function(key, value, type, escape) { + value = (typeof value === 'undefined' ? null : value); + type = type || 'AND '; + escape = (typeof escape === 'boolean' ? escape : true); + + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { + var filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + var that = this; + var parse_statement = function(statement,joiner) { + var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || joiner.match(/AND/i)) { + that.where(key, value, true); + } else { + that.or_where(key, value, true); + } + } + }; + parse_statement(filters.shift(),null); + while (filters.length > 0) { + var joiner = filters.shift(); + var statement = filters.shift(); + parse_statement(statement, joiner); + } + return this; + } + } + + var key_array = {}; + key_array[key] = value; + key = key_array; + } + + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } + + for (var k in key) { + var v = key[k]; + + if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { + return this._where_in(k,v,false,type,escape); + } + + var prefix = (this.where_array.length == 0 ? '' : type); + + if (v === null && !has_operator(k)) { + k += ' IS NULL'; + } + + if (v !== null) { + if (escape === true) { + k = protect_identifiers(this,k,escape); + v = ' ' + qb_escape(this,v); + } + + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + qb_escape(this,v); + } + + if (!has_operator(k)) { + k += ' ='; + } + } + else { + k = protect_identifiers(this,k,escape); + } + + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } + + return this; + }, + + where_in: function(key, values, escape) { + return this._where_in(key,values,false,'AND ', escape); + }, + + or_where_in: function(key, values, escape) { + return this._where_in(key,values,false,'OR ', escape); + }, + + where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'AND ', escape); + }, + + or_where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'OR ', escape); + }, + + _where_in: function(key, values, not, type, escape) { + key = key || ''; + values = values || []; + type = type || 'AND '; + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } + + // Values must be an array... + if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); + } + } + + for (var i in values) { + this.where_in_array.push(qb_escape(this,values[i])); + } + + var prefix = (this.where_array.length == 0 ? '' : type); + var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); + + // reset the array for multiple calls + clear_array(this.where_in_array); + return this; + }, + + like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + }, + + not_like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + }, + + or_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + }, + + or_not_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + }, + + _like: function(field, match, type, side, not) { + match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); + type = type || 'AND '; + side = (typeof side === 'undefined' ? 'both' : side); + not = not || ''; + + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if ((typeof field).match(/^(string|object)$/) === null) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } + + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); + } + + var field_array = {}; + field_array[field] = match; + field = field_array; + } + + for(k in field) { + var like_statement; + var v = field[k]; + var k = protect_identifiers(this,k.trim()); + + // Make sure value is only string, number, or boolean + if ((typeof v).match(/^(string|number|boolean)$/) === null) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); + } + + this._where(like_statement,null,type,false); + } + + return this; + }, + + from: function(from) { + if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { + from = [from]; + } + for (var i in from) { + var val = from[i]; + + if (val.trim() === '') continue; + + if (val.indexOf(',') !== -1) { + var objects = val.split(','); + for (var j in objects) { + var v = objects[j].trim(); + + track_aliases(this,v); + + this.from_array.push(protect_identifiers(this,v,true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + track_aliases(this,val); + + this.from_array.push(protect_identifiers(this,val,true)); + } + } + + return this; + }, + + join: function(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } + + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } + + direction += ' '; + } + + track_aliases(this,table); + + // Split multiple conditions + var regex = /\sAND\s|\sOR\s/ig; + var m = relation.match(regex); + var matches = []; + var k, temp, temp_match, match; + if (escape === true && m) { + while(k = regex.exec(relation)) { + matches.push(k); + } + + var new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); + new_relation += matches[j][0]; + } + + relation = ' ON ' + new_relation; + } + + // Split apart the condition and protect the identifiers + else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); + } + else if (!has_operator(relation)) { + relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; + } + else { + relation = ' '; + } + + // Do we want to escape the table name? + if (escape === true) { + table = protect_identifiers(this,table,true); + } + + var join = direction + 'JOIN ' + table + relation; + + this.join_array.push(join); + return this; + }, + + select: function(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); + } + } + else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); + } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + + if (typeof escape !== 'boolean') escape = true; + + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + var m, open_paren_index, inner_parenthesis; + var reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; + } + } + } + } + else { + select = [select]; + } + } + } + + for (var i in select) { + var val = select[i].trim(); + + if(val !== '') { + this.select_array.push(protect_identifiers(this,val,escape)); + } + } + return this; + }, + + select_min: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MIN'); + }, + + select_max: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MAX'); + }, + + select_avg: function(select,alias) { + return this._min_max_avg_sum(select,alias,'AVG'); + }, + + select_sum: function(select,alias) { + return this._min_max_avg_sum(select,alias,'SUM'); + }, + + _min_max_avg_sum: function(select,alias,type) { + select = select || ''; + alias = alias || ''; + type = type || 'MAX'; + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } + + type = type.toUpperCase(); + + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } + + if (alias == '') { + alias = create_aliases_from_table(select.trim()); + } + + var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + + this.select_array.push(sql); + + return this; + }, + + distinct: function(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + + if (do_distinct) { + clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + clear_array(this.distinct_clause); + } + return this; + }, + + group_by: function(by) { + if (typeof by === 'string') { + by = by.trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + by = by.split(','); + } + + if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } + + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + + for (var key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); + } + + var val = by[key].trim(); + + if (val !== '') { + this.group_by_array.push(protect_identifiers(this,val)); + } + } + return this; + }, + + having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + }, + + or_having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + }, + + _having: function(key, value, type, escape) { + type = type || 'AND '; + + var m; + var key_array = {}; + var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); + + if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = extract_having_parts(key,key_array); + } + } + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (var i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); + } + else { + key_array = extract_having_parts(key[i],key_array); + } + } + key = key_array; + } + } + } + else { + throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); + } + + + for (var k in key) { + var v = key[k]; + var prefix = (this.having_array.length == 0 ? '' : type); + + if (escape === true) { + k = protect_identifiers(this,k); + } + + if (v === null) { + k += ' IS'; + } + else if (!has_operator(k)) { + k += ' ='; + } + + if (v != '') { + v = ' ' + qb_escape(this,v); + } + + this.having_array.push(prefix + k + v); + } + + return this; + }, + + order_by: function(orderby, direction) { + var m; + var rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(rand_word); + return this; + } + + // normalize orderby to be an array of items + if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { + this.order_by_array.push(rand_word); + return this; + } + else { + throw new Error("No field provided to order by!"); + } + } + + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } + + for (var i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); + + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.order_by_array.push(rand_word); + return this; + } + orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (direction.match(/^(ASC|DESC)$/i) || direction === '') { + orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + } else { + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); + } + } + + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } + + return this; + }, + + limit: function(limit, offset) { + clear_array(this.limit_to); + this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); + + if (offset !== undefined) { + return this.offset(offset); + } + + return this; + }, + + offset: function(offset) { + clear_array(this.offset_val); + this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); + return this; + }, + + set: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) { + throw new Error("set(): Invalid field name provided!"); + } + + var key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); + } + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); + } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } + + + // Add each key:value pair to the set_array + for (var i in key) { + var v = key[i]; + if (typeof v === 'undefined') continue; + + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + + // Escape the key to be DRY + var escaped_key = protect_identifiers(this,i,escape); + + // Build a temporary object with escaped key and val + var temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = qb_escape(this,v); + } + + // Determine if this key has already been set + var found_index = null; + for (var j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; + } + } + + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); + } + } + + //console.dir(this.set_array); + + return this; + }, + + insert: function(table, set, ignore, suffix) { + table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } + + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + return this.insert_batch(table, set, ignore, suffix); + } + + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } + } + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('insert(): No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + return compile_insert(this, ignore, suffix); + }, + + insert_ignore: function(table, set, suffix) { + return this.insert(table, set, true, suffix); + }, + + insert_batch: function(table,set,ignore,suffix) { + var self = this; + var orig_table = table = table || ''; + set = set || null; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (var key in set) { + var row = set[key]; + var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (var i in row) { + var v = row[i]; + + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } + } + } + + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } + + var map = []; + var columns = []; + + // Obtain all the column names + for (var key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(protect_identifiers(this,key)); + } + } + } + + for (var i = 0; i < set.length; i++) { + (function(i,qb) { + var row = []; + for (var key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(qb_escape(qb,set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(', ') + ')'); + })(i,self); + } + + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + 'INTO (' + this.from_array[0] + ') (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; + }, + + get: function(table) { + if (typeof table !== 'undefined') { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); + } + } + return compile_select(this); + }, + + get_where: function(table, where) { + table = table || null; + where = where || null; + + if (table === null || (typeof table === 'string' && table.trim().length === 0)) { + throw new Error('You must specify a table or tables in the first parameter of get_where()'); + } + else { + this.from(table); + } + + if (where === null || Object.keys(where).length === 0) { + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + } + else { + this.where(where); + } + + return compile_select(this); + }, + + count: function(table) { + if (typeof table === 'string') { + this.from(table); + } + + var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + + build_from_clause(this) + + build_join_string(this) + + build_where_clause(this); + + return sql; + }, + + update: function(table, set, where) { + table = table || ''; + set = set || null; + where = where || null; + + // Send to batch_update if the data param is an array + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + var index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; + } + } + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + } + } + + // If set is a number, boolean, a non-empty string, or regex, fail + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } + + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } + + return compile_update(this); + }, + + update_batch: function(table, set, index, where) { + table = table || ''; + set = set || null; + index = index || null; + where = where || null; + + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } + + // Check to make sure we have a dataset + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } + + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } + + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (var i in set) { + var clean = {}; + var row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + var keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (var j in row) { + clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); + } + this.set_array.push(clean); + } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } + + // Verify there is a table in the from_array + if (this.from_array.length === 1) { + var table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); + } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } + + + // Limit to 100 rows per batch + var batches = []; + for (var i = 0, total = this.set_array.length; i < total; i += 100) { + var when_then = {}; + var ids = []; + var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); + var chunk = this.set_array.slice(i,100); + + // Escape the index + index = protect_identifiers(this, index); + + for (var j in chunk) { + ids.push(chunk[j][index]); + + var keys = Object.keys(chunk[j]); + for (var k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; + } + when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + } + } + } + + // Build the actual SQL statement + var sql = 'UPDATE (' + table + ') SET '; + var cases = ''; + + for (var l in when_then) { + cases += l + ' = CASE '; + + for (var m in when_then[l]) { + cases += when_then[l][m]; + } + + cases += 'ELSE ' + l + ' END, '; + } + + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; + + // Add query to batch + batches.push(sql); + } + + return batches; + }, + + delete: function(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + } + else { + this.where(where); + } + } + + return compile_delete(this); + }, + + get_compiled_select: function(table) { + if (typeof table !== 'undefined') { + track_aliases(this,table); + this.from(table); + } + else { + if (from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; + } + } + + return compile_select(this); + }, + + get_compiled_delete: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + + return compile_delete(this); + }, + + get_compiled_update: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_update(this); + }, + + get_compiled_insert: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_insert(this); + }, + + compile_select: function(table) { + return this.get_compiled_select(table); + }, + + compile_delete: function(table) { + return this.get_compiled_delete(table); + }, + + compile_update: function(table) { + return this.get_compiled_update(table); + }, + + compile_insert: function(table) { + return this.get_compiled_insert(table); + }, + + last_query: function() { + return this.last_query_string[0] || ''; + }, + + escape: function(val) { + return qb_escape(this, val); + }, + + empty_table: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } + + return "DELETE FROM " + this.from_array[0]; + }, + + truncate: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } + + return "TRUNCATE " + this.from_array[0]; + }, + } +}; + +exports.QueryBuilder = QueryBuilder; diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 4e26633..04672c8 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -1,203 +1,204 @@ -// **************************************************************************** -// QueryBuilder "Query Execution" methods. -// ----- -// @param Object qb The QueryBuilder object -// @param Object adapter The connection adapter object -// **************************************************************************** -var QueryExec = function(qb, conn) { - - var exec = function(sql, callback) { - if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - conn.query(sql, function(err, results) { - // Standardize some important properties - if (!err && results.length > 0) { - - // Insert ID - if (results.hasOwnProperty('insertId')) { - results.insert_id = results.insertId; - } - - // Affected Rows - if (results.hasOwnProperty('affectedRows')) { - results.affected_rows = results.affectedRows; - } - - // Changed Rows - if (results.hasOwnProperty('changedRows')) { - results.changed_rows = results.changedRows; - } - } - - callback(err, results); - }); - } else { - throw new Error("No connection object supplied to the Query Exec Library!"); - } - }; - - return { - query: function(sql, callback) { - exec(sql, callback); - }, - - count: function(table, callback) { - if (typeof table === 'function' && typeof callback !== 'function') { - table = null; - callback = table; - } - - var sql = qb.count(table); - qb.reset_query(sql); - exec(sql, function(err, row) { - if (!err) { - callback(err, row.numrows); - } - else { - callback(err, row); - } - }); - }, - - get: function(table,callback,conn) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); - } - - var sql = qb.get(table); - qb.reset_query(sql); - exec(sql,callback); - }, - - get_where: function(table,where,callback) { - if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); - } - if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); - } - var sql = qb.get_where(table,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert: function(table,set,callback,ignore,suffix) { - var sql = qb.insert(table,set,ignore,suffix); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_ignore: function(table,set,callback) { - var sql = qb.insert_ignore(table,set); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_batch: function(table,set,callback) { - var sql = qb.insert_batch(table,set); - qb.reset_query(sql); - exec(sql,callback); - }, - - update: function(table,set,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } - - var sql = qb.update(table,set,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - // TODO: Write this complicated-ass function - update_batch: function(table,set,index,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } - - var sqls = qb.update_batch(table,set,index,where); - var results = null; - var errors = []; - - // Execute each batch of (at least) 100 - (function next_batch() { - var sql = sqls.shift(); - qb.reset_query(sql); - - exec(sql, function(err, res) { - if (!err) { - if (null === results) { - results = res; - } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; - } - } else { - errors.push(err); - } - - if (sql.length > 0) { - setTimeout(next_batch,0); - } else { - return callback(errors, results); - } - }); - })(); - }, - - delete: function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } - - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } - - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - - var sql = qb.delete(table, where); - - qb.reset_query(sql); - exec(sql,callback); - }, - - empty_table: function(table, callback) { - var sql = qb.empty_table(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, - - truncate: function(table, callback) { - var sql = qb.truncate(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, - } -} - -exports.QueryExec = QueryExec; \ No newline at end of file +// **************************************************************************** +// QueryBuilder "Query Execution" methods. +// ----- +// @param Object qb The QueryBuilder object +// @param Object adapter The connection adapter object +// **************************************************************************** +var QueryExec = function(qb, conn) { + + var exec = function(sql, callback) { + if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { + conn.query(sql, function(err, results) { + // Standardize some important properties + if (!err && results.length > 0) { + + // Insert ID + if (results.hasOwnProperty('insertId')) { + results.insert_id = results.insertId; + } + + // Affected Rows + if (results.hasOwnProperty('affectedRows')) { + results.affected_rows = results.affectedRows; + } + + // Changed Rows + if (results.hasOwnProperty('changedRows')) { + results.changed_rows = results.changedRows; + } + } + + callback(err, results); + }); + } else { + throw new Error("No connection object supplied to the Query Exec Library!"); + } + }; + + return { + query: function(sql, callback) { + exec(sql, callback); + }, + + count: function(table, callback) { + if (typeof table === 'function' && typeof callback !== 'function') { + table = null; + callback = table; + } + + var sql = qb.count(table); + qb.reset_query(sql); + exec(sql, function(err, row) { + if (!err) { + //console.dir(row[0].numrows); + callback(err, row[0].numrows); + } + else { + callback(err, row); + } + }); + }, + + get: function(table,callback,conn) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); + } + + var sql = qb.get(table); + qb.reset_query(sql); + exec(sql,callback); + }, + + get_where: function(table,where,callback) { + if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } + var sql = qb.get_where(table,where); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert: function(table,set,callback,ignore,suffix) { + var sql = qb.insert(table,set,ignore,suffix); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert_ignore: function(table,set,callback) { + var sql = qb.insert_ignore(table,set); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert_batch: function(table,set,callback) { + var sql = qb.insert_batch(table,set); + qb.reset_query(sql); + exec(sql,callback); + }, + + update: function(table,set,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sql = qb.update(table,set,where); + qb.reset_query(sql); + exec(sql,callback); + }, + + // TODO: Write this complicated-ass function + update_batch: function(table,set,index,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sqls = qb.update_batch(table,set,index,where); + var results = null; + var errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + var sql = sqls.shift(); + qb.reset_query(sql); + + exec(sql, function(err, res) { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } + } else { + errors.push(err); + } + + if (sql.length > 0) { + setTimeout(next_batch,0); + } else { + return callback(errors, results); + } + }); + })(); + }, + + delete: function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } + + var sql = qb.delete(table, where); + + qb.reset_query(sql); + exec(sql,callback); + }, + + empty_table: function(table, callback) { + var sql = qb.empty_table(table,callback); + qb.reset_query(sql); + exec(sql,callback); + }, + + truncate: function(table, callback) { + var sql = qb.truncate(table,callback); + qb.reset_query(sql); + exec(sql,callback); + }, + } +} + +exports.QueryExec = QueryExec; From 8d210867734a17c691abd75c8b7a9a19fa166a37 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 17 Feb 2015 11:15:36 -0500 Subject: [PATCH 079/200] Fixed a bug causing inserts to fail due to paranthesis. Fixed undefined value in set object bug. --- drivers/mysql/query_builder.js | 8 +++++--- test/mysql/tests-compilation_methods.js | 2 +- test/mysql/tests-insert.js | 16 ++++++++-------- test/mysql/tests-insert_batch.js | 8 ++++---- test/mysql/tests-update.js | 14 +++++++------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 0e960fe..f5ec0a4 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -453,7 +453,7 @@ var QueryBuilder = function() { return ''; } - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')';// + suffix; + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// @@ -1173,7 +1173,9 @@ var QueryBuilder = function() { // Add each key:value pair to the set_array for (var i in key) { var v = key[i]; - if (typeof v === 'undefined') continue; + if (typeof v === 'undefined') { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); @@ -1344,7 +1346,7 @@ var QueryBuilder = function() { } var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO (' + this.from_array[0] + ') (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; + return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, get: function(table) { diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index e632925..c85aff7 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -36,7 +36,7 @@ describe('get_compiled_insert()', function() { it('should return a SQL string', function() { qb.reset_query(); var sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); - sql.should.eql("INSERT INTO (`galaxies`) (`foo`) VALUES ('bar')"); + sql.should.eql("INSERT INTO `galaxies` (`foo`) VALUES ('bar')"); }); }); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index dffbd73..6c13d28 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -68,30 +68,30 @@ describe('insert()', function() { it('should allow for an empty data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.insert(); - sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing values set in in set_array', function() { qb.reset_query(); qb.set(test_data); var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); var sql = qb.insert(); - sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); var sql = qb.insert('galaxies', test_data); - sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); @@ -127,7 +127,7 @@ describe('insert_ignore()', function() { it('should create an INSERT IGNORE statement', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data); - sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { qb.reset_query(); @@ -138,11 +138,11 @@ describe('insert_ignore()', function() { it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data_set); - sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { qb.reset_query(); var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); \ No newline at end of file diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index 239b4e7..eba7c60 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -42,7 +42,7 @@ describe('insert_batch()', function() { it('should build a proper batch INSERT string', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies', test_data); - sql.should.eql("INSERT INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); it('should only accept an array as the second parameter', function() { qb.reset_query(); @@ -72,13 +72,13 @@ describe('insert_batch()', function() { it('should allow for an empty data parameter', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies',[]); - sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.insert_batch(null,[]); - sql.should.eql("INSERT INTO (`galaxies`) () VALUES ()"); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { qb.reset_query(); @@ -98,6 +98,6 @@ describe('insert_batch()', function() { it('should support insert ignore statements', function() { qb.reset_query(); var sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO (`galaxies`) (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); \ No newline at end of file diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 62b23d8..eb52058 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -108,13 +108,13 @@ describe('update()', function() { var arr = [1,2,3]; var obj = {foo: 'bar'}; - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: func}); }, 'function in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: arr}); }, 'array in data').to.throw(Error); + qb.reset_query(); expect(function() { qb.update('galaxies',{id: obj}); }, 'object in data').to.throw(Error); }); /* From 34ca478ac90f1214099ced7358c20f0fc413786f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 27 Jul 2015 11:04:17 -0400 Subject: [PATCH 080/200] Fixed bug in insert_batch. --- drivers/mysql/query_builder.js | 7 ++----- package.json | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index f5ec0a4..d514104 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -453,7 +453,7 @@ var QueryBuilder = function() { return ''; } - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')';// + suffix; }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// @@ -1173,9 +1173,7 @@ var QueryBuilder = function() { // Add each key:value pair to the set_array for (var i in key) { var v = key[i]; - if (typeof v === 'undefined') { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } + if (typeof v === 'undefined') continue; if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); @@ -1255,7 +1253,6 @@ var QueryBuilder = function() { clear_array(this.from_array); this.from(table); } - return compile_insert(this, ignore, suffix); }, diff --git a/package.json b/package.json index abc7bb2..8ca4e21 100755 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ "scripts": { "test": "make test" }, - "homepage": "https://github.com/martintajur/node-mysql-activerecord", + "homepage": "http://kylefarris.github.io/node-querybuilder/", "repository": { "type": "git", - "url": "git://github.com/martintajur/node-mysql-activerecord.git" + "url": "git@github.com:kylefarris/node-querybuilder.git" }, "contributors": [ "Martin Tajur ", @@ -33,4 +33,4 @@ "engines": { "node": "*" } -} \ No newline at end of file +} From d4e983d8f88d93ee001fa4285ffca0c9fc008eb2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 4 Sep 2015 10:46:39 -0400 Subject: [PATCH 081/200] Fixed some small bugs. Tests should all be passing now. --- drivers/mysql/query_builder.js | 539 +++++++++++++++++---------------- test/mysql/tests-insert.js | 24 +- test/mysql/tests-set.js | 10 +- 3 files changed, 290 insertions(+), 283 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d514104..d5dd68c 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,5 +1,5 @@ var QueryBuilder = function() { - + // ------------------------------ GENERIC FUNCTIONS ------------------------------// var array_values = function(item) { var keys = Object.keys(item); @@ -10,15 +10,15 @@ var QueryBuilder = function() { } return values; }; - + var prepare_for_limit_and_offset = function(item,type) { type = type || 'limit'; type = type.toLowerCase(); - + if ((typeof item).match(/^(string|number)$/) === null) { throw new Error("Only integers or integers in the form of a string are allowed"); } - + if (typeof item === 'string') { item = item.trim(); if (item.match(/^\d+$/) === null) { @@ -27,31 +27,31 @@ var QueryBuilder = function() { // Force to an integer item = parseInt(item); } - + // Make sure the number is a good one if (typeof item === 'number') { // Check for NaN and Infinity if (item !== +item || item === Infinity) { throw new Error("You have not provided a valid number to " + type + " by!"); } - + // Make sure it's positive if (item < 0) { throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); } - + // Only allow integers if (item % 1 !== 0) { throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); } - } + } else { throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); } - + return item; } - + var extract_having_parts = function(key,key_array) { var m; key = key.trim().replace(/\s+/g,' '); @@ -59,7 +59,7 @@ var QueryBuilder = function() { var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; - + if (m = str_condition.exec(key)) { key_array[m[1]] = m[3]; key = key_array; @@ -82,10 +82,10 @@ var QueryBuilder = function() { else { throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); } - + return key_array; } - + // Simply setting all properties to [] causes reference issues in the parent class. var clear_array = function(a,debug) { if (debug === true) { @@ -109,7 +109,7 @@ var QueryBuilder = function() { console.dir(a); } }; - + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // var track_aliases = function(qb,table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { @@ -130,7 +130,7 @@ var QueryBuilder = function() { if (table.indexOf(' ') !== -1) { // if the alias is written with the AS keyword, remove it table = table.replace(/\s+AS\s+/gi, ' '); - + // Grab the alias var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); @@ -153,7 +153,7 @@ var QueryBuilder = function() { if (!item || item === '*') { return item; } - + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (var i in item) { item[i] = escape_identifiers(item[i]); @@ -163,7 +163,7 @@ var QueryBuilder = function() { else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { return item; } - + var str; if (item.indexOf('.' + '*') !== -1) { str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); @@ -171,16 +171,16 @@ var QueryBuilder = function() { else { str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); } - + // remove duplicates if the user already included the escape return str.replace(/[`]+/g,'`'); }; var protect_identifiers = function(qb,item,protect_identifiers) { if (item === '') return item; - + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - + if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { var escaped_array = {}; @@ -191,10 +191,10 @@ var QueryBuilder = function() { return escaped_array; } - + // Convert tabs or multiple spaces into single spaces item = item.replace(/\s+/g, ' '); - + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. // If a parenthesis is found we know that we do not need to // escape the data or add a prefix. @@ -210,10 +210,10 @@ var QueryBuilder = function() { } else { alias = ''; } - + return item + alias; } - + // If the item has an alias declaration we remove it and set it aside. // Basically we remove everything to the right of the first space if (item.match(/\sAS\s/ig)) { @@ -223,21 +223,21 @@ var QueryBuilder = function() { } else if (item.indexOf(' ') !== -1) { var alias_index = item.indexOf(' '); - + var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); item = item.substr(0,alias_index); } else { var alias = ''; } - + // Break the string apart if it contains periods, then insert the table prefix // in the correct location, assuming the period doesn't indicate that we're dealing // with an alias. While we're at it, we will escape the components if (item.indexOf('.') !== -1) { var parts = item.split('.'); var first_seg = parts[0].trim().replace(/`/g,''); - + // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item @@ -264,7 +264,7 @@ var QueryBuilder = function() { if (protect_identifiers === true) { item = escape_identifiers(item); } - + return item + alias; }; @@ -281,7 +281,7 @@ var QueryBuilder = function() { var qb_escape = function(qb,str) { var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); var do_escape = SqlString.escape; - + if (typeof str === 'boolean') { str = (str === false ? 0 : 1); } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { @@ -324,7 +324,7 @@ var QueryBuilder = function() { var build_group_by_clause = function(qb) { if (qb.group_by_array.length <= 0) return ''; - + var sql = ' GROUP BY '; sql += qb.group_by_array.join(', '); return sql; @@ -332,7 +332,7 @@ var QueryBuilder = function() { var build_having_clause = function(qb) { if (qb.having_array.length <= 0) return ''; - + var sql = ' HAVING '; sql += qb.having_array.join(' '); return sql; @@ -340,18 +340,18 @@ var QueryBuilder = function() { var build_order_by_clause = function(qb) { if (qb.order_by_array.length <= 0) return ''; - + var sql = ' ORDER BY '; sql += qb.order_by_array.join(', '); - + return sql; }; var build_limit_clause = function(sql, limit, offset) { if (!limit) return sql; - + sql += ' '; - + if (typeof offset !== 'number' || offset === 0) { offset = ''; } @@ -369,91 +369,91 @@ var QueryBuilder = function() { } else { sql += qb.select_array.join(', '); } - + sql += build_from_clause(qb) + build_join_string(qb) + build_where_clause(qb) + build_group_by_clause(qb) + build_having_clause(qb) + build_order_by_clause(qb); - + var limit_to = qb.limit_to[0] || false; var offset_val = qb.offset_val[0] || false; - + sql = build_limit_clause(sql,limit_to,offset_val); return sql; }; - + var compile_delete = function(qb) { if (qb.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); return ''; } - + qb.from_array = qb.from_array.slice(0,1); - + var limit_to = qb.limit_to[0] || false; var offset_val = qb.offset_val[0] || false; - + var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); return build_limit_clause(sql,limit_to,offset_val); }; - - var compile_update = function(qb) { + + var compile_update = function(qb) { var valstr = []; for (var i in qb.set_array) { var key = Object.keys(qb.set_array[i])[0]; var val = qb.set_array[i][key]; valstr.push(key + ' = ' + val); } - - if (qb.from_array.length === 1) { + + if (qb.from_array.length === 1) { var table = qb.from_array.toString(); } else { - if (qb.from_array.length === 0) { + if (qb.from_array.length === 0) { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; } throw new Error("You have provided too many tables to build UPDATE query with!"); return ''; } - + var limit_to = qb.limit_to[0] || false; var offset_val = qb.offset_val[0] || false; - + var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); return build_limit_clause(sql, limit_to, offset_val); }; - + var compile_insert = function(qb, ignore, suffix) { var keys = []; var values = []; suffix = suffix || ''; - - for (var i in qb.set_array) { + + for (var i in qb.set_array) { var key = Object.keys(qb.set_array[i])[0]; var val = qb.set_array[i][key]; - + keys.push(key); values.push(val); } - + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (qb.from_array.length === 1) { + + if (qb.from_array.length === 1) { var table = qb.from_array.toString(); } else { - if (qb.from_array.length === 0) { + if (qb.from_array.length === 0) { throw new Error("You haven't provided any tables to build INSERT querty with!"); return ''; } throw new Error("You have provided too many tables to build INSERT query with!"); return ''; } - - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')';// + suffix; + + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; }; // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// @@ -473,7 +473,7 @@ var QueryBuilder = function() { last_query_string: [], // has to be array to work as reference distinct_clause: [], // has to be array to work as reference aliased_tables: [], - + reset_query: function(new_last_query,debug) { clear_array(this.where_array,debug); clear_array(this.where_in_array); @@ -489,42 +489,42 @@ var QueryBuilder = function() { clear_array(this.join_clause); clear_array(this.distinct_clause); clear_array(this.aliased_tables); - + clear_array(this.last_query_string); if (typeof new_last_query === 'string') { this.last_query_string.push(new_last_query); } }, - + where: function(key, value, escape) { if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { escape = (typeof escape === 'boolean' ? escape : value); } - + value = (typeof value === 'undefined' ? null : value); escape = (typeof escape === 'boolean' ? escape : true); - + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { return this._where_in(key, value, false, 'AND '); } return this._where(key, value, 'AND ', escape); }, - + or_where: function(key, value, escape) { escape = (typeof escape === 'boolean' ? escape : true); value = value || null; - + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { return this._where_in(key, value, false, 'OR '); } return this._where(key, value, 'OR ', escape); }, - + _where: function(key, value, type, escape) { value = (typeof value === 'undefined' ? null : value); type = type || 'AND '; escape = (typeof escape === 'boolean' ? escape : true); - + // Must be an object or a string if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { // If it's not an object, it must be a string @@ -536,8 +536,8 @@ var QueryBuilder = function() { throw new Error("where(): No field name or query provided!"); } } - - // If it's a actual where clause string (with no paranthesis), + + // If it's a actual where clause string (with no paranthesis), // not just a field name, split it into individual parts to escape it properly if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { var filters = key.split(/\s+(AND|OR)\s+/i); @@ -549,7 +549,7 @@ var QueryBuilder = function() { var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); if (joiner === null || joiner.match(/AND/i)) { - that.where(key, value, true); + that.where(key, value, true); } else { that.or_where(key, value, true); } @@ -564,39 +564,39 @@ var QueryBuilder = function() { return this; } } - + var key_array = {}; key_array[key] = value; key = key_array; } - + if (Object.keys(key).length == 0) { throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); } - + for (var k in key) { var v = key[k]; - + if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { return this._where_in(k,v,false,type,escape); } - + var prefix = (this.where_array.length == 0 ? '' : type); - + if (v === null && !has_operator(k)) { k += ' IS NULL'; } - + if (v !== null) { if (escape === true) { k = protect_identifiers(this,k,escape); v = ' ' + qb_escape(this,v); } - + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { v = ' ' + qb_escape(this,v); } - + if (!has_operator(k)) { k += ' ='; } @@ -604,34 +604,34 @@ var QueryBuilder = function() { else { k = protect_identifiers(this,k,escape); } - + if (v) { this.where_array.push(prefix+k+v); - } + } else { this.where_array.push(prefix+k); } } - + return this; }, - + where_in: function(key, values, escape) { return this._where_in(key,values,false,'AND ', escape); }, - + or_where_in: function(key, values, escape) { return this._where_in(key,values,false,'OR ', escape); }, - + where_not_in: function(key, values, escape) { return this._where_in(key,values,true,'AND ', escape); }, - + or_where_not_in: function(key, values, escape) { return this._where_in(key,values,true,'OR ', escape); }, - + _where_in: function(key, values, not, type, escape) { key = key || ''; values = values || []; @@ -642,7 +642,7 @@ var QueryBuilder = function() { if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); } - + // Values must be an array... if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); @@ -652,7 +652,7 @@ var QueryBuilder = function() { throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); } } - + for (var i in values) { this.where_in_array.push(qb_escape(this,values[i])); } @@ -665,29 +665,29 @@ var QueryBuilder = function() { clear_array(this.where_in_array); return this; }, - + like: function(field, match, side) { return this._like(field, match, 'AND ', side, ''); }, - + not_like: function(field, match, side) { return this._like(field, match, 'AND ', side, ' NOT'); }, - + or_like: function(field, match, side) { return this._like(field, match, 'OR ', side, ''); }, - + or_not_like: function(field, match, side) { return this._like(field, match, 'OR ', side, ' NOT'); }, - + _like: function(field, match, type, side, not) { match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); type = type || 'AND '; side = (typeof side === 'undefined' ? 'both' : side); not = not || ''; - + if (typeof field === 'string' && field.length == 0) { throw new Error("like(): The field you provided is empty."); } @@ -697,12 +697,12 @@ var QueryBuilder = function() { else if ((typeof field).match(/^(string|object)$/) === null) { throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); } - + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { if (match === null) { throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } - + var field_array = {}; field_array[field] = match; field = field_array; @@ -712,7 +712,7 @@ var QueryBuilder = function() { var like_statement; var v = field[k]; var k = protect_identifiers(this,k.trim()); - + // Make sure value is only string, number, or boolean if ((typeof v).match(/^(string|number|boolean)$/) === null) { throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); @@ -721,43 +721,43 @@ var QueryBuilder = function() { else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } - + if (side === 'none') { like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } + } else if (side === 'before' || side === 'left') { like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } + } else if (side === 'after' || side === 'right') { like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } + } else if (side === 'both') { like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; } else { throw new Error("like(): Invalid direction provided!"); } - + this._where(like_statement,null,type,false); } return this; }, - + from: function(from) { if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { from = [from]; } for (var i in from) { var val = from[i]; - + if (val.trim() === '') continue; - + if (val.indexOf(',') !== -1) { var objects = val.split(','); for (var j in objects) { var v = objects[j].trim(); - + track_aliases(this,v); this.from_array.push(protect_identifiers(this,v,true)); @@ -781,13 +781,13 @@ var QueryBuilder = function() { if (typeof table !== 'string' || table.trim().length == 0) { throw new Error("You must provide a table, view, or stored procedure to join to!"); } - + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); escape = (typeof escape === 'boolean' ? escape : true); - + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; - + if (direction != '') { direction = direction.toUpperCase().trim(); if (valid_directions.indexOf(direction) === -1) { @@ -796,12 +796,12 @@ var QueryBuilder = function() { if (relation === '') { throw new Error("You must provide a valid condition to join on when providing a join direction."); } - + direction += ' '; } - + track_aliases(this,table); - + // Split multiple conditions var regex = /\sAND\s|\sOR\s/ig; var m = relation.match(regex); @@ -811,7 +811,7 @@ var QueryBuilder = function() { while(k = regex.exec(relation)) { matches.push(k); } - + var new_relation = ''; matches.push(['']); matches[(matches.length - 1)].index = relation.length; @@ -821,10 +821,10 @@ var QueryBuilder = function() { new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); new_relation += matches[j][0]; } - + relation = ' ON ' + new_relation; } - + // Split apart the condition and protect the identifiers else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) @@ -836,18 +836,18 @@ var QueryBuilder = function() { else { relation = ' '; } - + // Do we want to escape the table name? if (escape === true) { table = protect_identifiers(this,table,true); } - + var join = direction + 'JOIN ' + table + relation; - + this.join_array.push(join); return this; }, - + select: function(select,escape) { // First param must be a non-empty string or array if (typeof select === 'string') { @@ -864,9 +864,9 @@ var QueryBuilder = function() { else { throw new Error("Select method requires a string or array to be passed in the first parameter!"); } - + if (typeof escape !== 'boolean') escape = true; - + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) if (typeof select === 'string') { if (select.indexOf(')') === -1) { @@ -891,64 +891,64 @@ var QueryBuilder = function() { } } } - + for (var i in select) { var val = select[i].trim(); - + if(val !== '') { this.select_array.push(protect_identifiers(this,val,escape)); } } return this; }, - + select_min: function(select,alias) { return this._min_max_avg_sum(select,alias,'MIN'); }, - + select_max: function(select,alias) { return this._min_max_avg_sum(select,alias,'MAX'); }, - + select_avg: function(select,alias) { return this._min_max_avg_sum(select,alias,'AVG'); }, - + select_sum: function(select,alias) { return this._min_max_avg_sum(select,alias,'SUM'); }, - + _min_max_avg_sum: function(select,alias,type) { select = select || ''; alias = alias || ''; type = type || 'MAX'; - + if (typeof select !== 'string' || select === '') { throw Error("Invalid query!"); return this; } - + type = type.toUpperCase(); - + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { throw Error("Invalid function type!"); return this; } - + if (alias == '') { alias = create_aliases_from_table(select.trim()); } - + var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; - + this.select_array.push(sql); - + return this; }, - + distinct: function(do_distinct) { do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - + if (do_distinct) { clear_array(this.distinct_clause); this.distinct_clause.push('DISTINCT '); @@ -967,11 +967,11 @@ var QueryBuilder = function() { } by = by.split(','); } - + if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); } - + if (by.length <= 0) { throw new Error("You haven't provided any fields to group by!"); } @@ -980,7 +980,7 @@ var QueryBuilder = function() { if (typeof by[key] !== 'string') { throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); } - + var val = by[key].trim(); if (val !== '') { @@ -994,20 +994,20 @@ var QueryBuilder = function() { escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'AND ', escape); }, - + or_having: function(key, value, escape) { escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'OR ', escape); }, - + _having: function(key, value, type, escape) { type = type || 'AND '; - + var m; var key_array = {}; var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); - + if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); @@ -1039,34 +1039,34 @@ var QueryBuilder = function() { key = key_array; } } - } + } else { throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); } - - + + for (var k in key) { var v = key[k]; var prefix = (this.having_array.length == 0 ? '' : type); - + if (escape === true) { k = protect_identifiers(this,k); } - + if (v === null) { k += ' IS'; } else if (!has_operator(k)) { k += ' ='; } - + if (v != '') { v = ' ' + qb_escape(this,v); } - + this.having_array.push(prefix + k + v); } - + return this; }, @@ -1074,13 +1074,13 @@ var QueryBuilder = function() { var m; var rand_word = 'RAND()'; direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - + // Don't need to do anything below if the direction provided is random if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { this.order_by_array.push(rand_word); return this; } - + // normalize orderby to be an array of items if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { if (typeof orderby === 'string') { @@ -1097,14 +1097,14 @@ var QueryBuilder = function() { throw new Error("No field provided to order by!"); } } - + if (orderby.length === 0) { throw new Error("You haven't provided any fields to order by!"); } - + for (var i in orderby) { orderby[i] = orderby[i].replace(/\s+/g, ' '); - + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { if (m[2].trim() === 'RAND()') { this.order_by_array.push(rand_word); @@ -1118,40 +1118,43 @@ var QueryBuilder = function() { throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); } } - + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); } - + return this; }, - + limit: function(limit, offset) { clear_array(this.limit_to); this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); - + if (offset !== undefined) { return this.offset(offset); } - + return this; }, - + offset: function(offset) { clear_array(this.offset_val); this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); return this; }, - + set: function(key, value, escape) { escape = (typeof escape === 'boolean' ? escape : true); - + if (typeof key === 'string') { // Convert key and value params to {key: value} key = key.trim(); if (key.length == 0) { throw new Error("set(): Invalid field name provided!"); } - + + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); + var key_array = {}; key_array[key] = value; key = key_array; @@ -1160,7 +1163,7 @@ var QueryBuilder = function() { if (Object.keys(key).length === 0) { throw new Error("set(): The object you provided is empty."); } - + if (typeof value !== 'undefined' & value !== null) { throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); } @@ -1168,23 +1171,23 @@ var QueryBuilder = function() { else { throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); } - - + + // Add each key:value pair to the set_array for (var i in key) { var v = key[i]; if (typeof v === 'undefined') continue; - + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); } - + // Escape the key to be DRY var escaped_key = protect_identifiers(this,i,escape); - + // Build a temporary object with escaped key and val var temp = {}; if (escape === false) { @@ -1192,7 +1195,7 @@ var QueryBuilder = function() { } else { temp[escaped_key] = qb_escape(this,v); } - + // Determine if this key has already been set var found_index = null; for (var j in this.set_array) { @@ -1201,7 +1204,7 @@ var QueryBuilder = function() { break; } } - + // Update value if key already set or add if not found if (found_index !== null) { this.set_array[found_index] = temp; @@ -1209,42 +1212,42 @@ var QueryBuilder = function() { this.set_array.push(temp); } } - + //console.dir(this.set_array); - + return this; }, - + insert: function(table, set, ignore, suffix) { table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { throw new Error("insert(): Invalid data provided to insert into database!"); } - + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { return this.insert_batch(table, set, ignore, suffix); } - + if (set !== null) { if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { this.set(set); } } - + if (typeof table !== 'string') { throw new Error("insert(): Table parameter must be a string!"); } - + table = table.trim(); - + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } - - if (table == '') { + + if (table === '') { if (this.from_array.length == 0) { throw new Error('insert(): No tables set to insert into!'); } @@ -1255,11 +1258,11 @@ var QueryBuilder = function() { } return compile_insert(this, ignore, suffix); }, - + insert_ignore: function(table, set, suffix) { return this.insert(table, set, true, suffix); }, - + insert_batch: function(table,set,ignore,suffix) { var self = this; var orig_table = table = table || ''; @@ -1267,17 +1270,17 @@ var QueryBuilder = function() { ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); if (suffix == ' ') suffix = ''; - + if (typeof table !== 'string') { throw new Error("insert(): Table parameter must be a string!"); } - + table = table.trim(); - + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } - + if (table == '') { if (this.from_array.length === 0) { throw new Error("insert_batch(): You have not set any tables to insert into."); @@ -1287,11 +1290,11 @@ var QueryBuilder = function() { clear_array(this.from_array); this.from(table); } - + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); } - + for (var key in set) { var row = set[key]; var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); @@ -1300,7 +1303,7 @@ var QueryBuilder = function() { } else { for (var i in row) { var v = row[i]; - + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided!"); } @@ -1310,14 +1313,14 @@ var QueryBuilder = function() { } } } - + if (set.length == 0) { return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); } - + var map = []; var columns = []; - + // Obtain all the column names for (var key in set[0]) { if (set[0].hasOwnProperty(key)) { @@ -1326,7 +1329,7 @@ var QueryBuilder = function() { } } } - + for (var i = 0; i < set.length; i++) { (function(i,qb) { var row = []; @@ -1341,7 +1344,7 @@ var QueryBuilder = function() { map.push('(' + row.join(', ') + ')'); })(i,self); } - + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, @@ -1357,46 +1360,46 @@ var QueryBuilder = function() { } return compile_select(this); }, - + get_where: function(table, where) { table = table || null; where = where || null; - + if (table === null || (typeof table === 'string' && table.trim().length === 0)) { throw new Error('You must specify a table or tables in the first parameter of get_where()'); } else { this.from(table); } - + if (where === null || Object.keys(where).length === 0) { throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); } else { this.where(where); } - + return compile_select(this); }, - + count: function(table) { if (typeof table === 'string') { this.from(table); } - + var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + build_from_clause(this) + build_join_string(this) + build_where_clause(this); - + return sql; }, - + update: function(table, set, where) { table = table || ''; set = set || null; where = where || null; - + // Send to batch_update if the data param is an array if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { var index = null; @@ -1411,12 +1414,12 @@ var QueryBuilder = function() { throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); } } - + // If set is a number, boolean, a non-empty string, or regex, fail if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { throw new Error("update(): Invalid data provided to update database!"); } - + // If data object was provided, set it if (set !== null) { if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { @@ -1425,24 +1428,24 @@ var QueryBuilder = function() { throw new Error("update(): Empty data object provided. This is not allowed."); } } - + // Fail if, at this point, nothing has been set if (this.set_array.length == 0) { throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); } - + // NOTE: If falsy table provided, table will have been converted to an empty string... if (typeof table !== 'string') { throw new Error("update(): Table parameter must be a string!"); } - + table = table.trim(); - + // Table name must be in a legitimate format if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("update(): You have not set any tables to update!"); } - + // If table not supplied, it must have been supplied already if (table == '') { if (this.from_array.length == 0) { @@ -1453,36 +1456,36 @@ var QueryBuilder = function() { clear_array(this.from_array); this.from(table); } - + // Set where array if a where statement was provided if (where !== null) { this.where(where); } - + return compile_update(this); }, - + update_batch: function(table, set, index, where) { table = table || ''; set = set || null; index = index || null; where = where || null; - + // Make sure an index has been provided! if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { throw new Error("update_batch(): Invalid index provided to generate batch update query!"); } - + // Check to make sure we have a dataset if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { throw new Error("update_batch(): Array of object expected and non-array received."); - } - + } + // Make sure our dataset isn't emtpy if (set.length === 0) { throw new Error("update_batch(): You must supply some data to batch update the table with."); } - + // Make sure each item in the dataset has the specified index and then add data to set_array //console.dir(set); for (var i in set) { @@ -1500,24 +1503,24 @@ var QueryBuilder = function() { throw new Error("update_batch(): You have supplied an invalid object to batch update!"); } } - + // Fail if, at this point, nothing has been set if (this.set_array.length == 0) { throw new Error("update_batch(): You must provide some data to batch update!"); } - + // NOTE: If falsy table provided, table will have been converted to an empty string... if (typeof table !== 'string') { throw new Error("update(): Table parameter must be a string!"); } - + table = table.trim(); - + // Table name must be in a legitimate format if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { throw new Error("update(): You have not set any tables to update!"); } - + // If table not supplied, it must have been supplied already if (table == '') { if (this.from_array.length == 0) { @@ -1528,23 +1531,23 @@ var QueryBuilder = function() { clear_array(this.from_array); this.from(table); } - + // Set where array if a where statement was provided if (where != null) { this.where(where); } - + // Verify there is a table in the from_array - if (this.from_array.length === 1) { + if (this.from_array.length === 1) { var table = this.from_array.toString(); } else { - if (this.from_array.length === 0) { + if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build batch UPDATE query with!"); } throw new Error("You have provided too many tables to build batch UPDATE query with!"); } - - + + // Limit to 100 rows per batch var batches = []; for (var i = 0, total = this.set_array.length; i < total; i += 100) { @@ -1552,13 +1555,13 @@ var QueryBuilder = function() { var ids = []; var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); var chunk = this.set_array.slice(i,100); - + // Escape the index index = protect_identifiers(this, index); - + for (var j in chunk) { ids.push(chunk[j][index]); - + var keys = Object.keys(chunk[j]); for (var k in keys) { if (keys[k] != index) { @@ -1569,49 +1572,49 @@ var QueryBuilder = function() { } } } - + // Build the actual SQL statement var sql = 'UPDATE (' + table + ') SET '; var cases = ''; - + for (var l in when_then) { cases += l + ' = CASE '; - + for (var m in when_then[l]) { cases += when_then[l][m]; } - + cases += 'ELSE ' + l + ' END, '; } - + sql += cases.substr(0, cases.length - 2); sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - + // Add query to batch batches.push(sql); } - + return batches; }, - + delete: function(table, where) { if (typeof table == 'string' && table.trim().length > 0) { clear_array(this.from_array); this.from(table); } - + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { if (Object.keys(where).length == 0) { throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } + } else { this.where(where); } } - + return compile_delete(this); }, - + get_compiled_select: function(table) { if (typeof table !== 'undefined') { track_aliases(this,table); @@ -1623,19 +1626,19 @@ var QueryBuilder = function() { return this; } } - + return compile_select(this); }, - + get_compiled_delete: function(table) { if (typeof table !== 'function') { track_aliases(this,table); this.from(table); } - + return compile_delete(this); }, - + get_compiled_update: function(table) { if (typeof table !== 'function') { track_aliases(this,table); @@ -1643,7 +1646,7 @@ var QueryBuilder = function() { } return compile_update(this); }, - + get_compiled_insert: function(table) { if (typeof table !== 'function') { track_aliases(this,table); @@ -1651,56 +1654,56 @@ var QueryBuilder = function() { } return compile_insert(this); }, - + compile_select: function(table) { return this.get_compiled_select(table); }, - + compile_delete: function(table) { return this.get_compiled_delete(table); }, - + compile_update: function(table) { return this.get_compiled_update(table); }, - + compile_insert: function(table) { return this.get_compiled_insert(table); }, - + last_query: function() { return this.last_query_string[0] || ''; }, - + escape: function(val) { return qb_escape(this, val); }, - + empty_table: function(table) { if (typeof table === 'string' && table.trim().length > 0) { clear_array(this.from_array); this.from(table); } - + if (this.from_array.length === 0) { throw new Error('empty_table(): You have not specified a table to empty!'); return ''; } - + return "DELETE FROM " + this.from_array[0]; }, - + truncate: function(table) { if (typeof table === 'string' && table.trim().length > 0) { clear_array(this.from_array); this.from(table); } - + if (this.from_array.length === 0) { throw new Error('truncate(): You have not specified a table to truncate!'); return ''; } - + return "TRUNCATE " + this.from_array[0]; }, } diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 6c13d28..1b496d9 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -21,10 +21,10 @@ describe('insert()', function() { }); it('should only accept nothing or a string for the table (first) parameter', function() { qb.reset_query(); - + // Doing these to prevent other errors - qb.from('galaxies'); - + qb.from('galaxies'); + expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); @@ -33,7 +33,7 @@ describe('insert()', function() { expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); - + expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); @@ -43,7 +43,7 @@ describe('insert()', function() { }); it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { qb.reset_query(); - + expect(function() { qb.insert('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); expect(function() { qb.insert('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); @@ -52,7 +52,7 @@ describe('insert()', function() { expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); expect(function() { qb.insert('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); - + expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); @@ -105,15 +105,19 @@ describe('insert()', function() { var regex = /foobar/; var arr = [1,2,3]; var obj = {foo: 'bar'}; - + + expect(function() { qb.insert('galaxies',{id: 1}); }, 'number in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: 'foo'}); }, 'string in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: false}); }, 'boolean in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: null}); }, 'null in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.not.throw(Error); expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); - + }); }); @@ -145,4 +149,4 @@ describe('insert_ignore()', function() { var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index 63793b8..9a30e66 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -31,11 +31,11 @@ describe('set()', function() { expect(function() { qb.set(''); }, 'empty string provided').to.throw(Error); expect(function() { qb.set(' '); }, 'string full of spaces provided').to.throw(Error); expect(function() { qb.set(/foobar/); }, 'regex provided').to.throw(Error); - + expect(function() { qb.set('planet_position',3); }, 'valid string provided').to.not.throw(Error); expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); }); - it('should not accept anything but a string, number, null, or boolean as second param', function() { + it('should not accept anything but a string, number, null, or boolean as second param if first param is a string.', function() { qb.reset_query(); expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); @@ -44,7 +44,7 @@ describe('set()', function() { expect(function() { qb.set('planet_position',[]); }, 'empty array provided').to.throw(Error); expect(function() { qb.set('planet_position',[1,2]); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.set('planet_position',/foobar/); }, 'regex provided').to.throw(Error); - + expect(function() { qb.set('planet_position',null); }, 'null provided').to.not.throw(Error); expect(function() { qb.set('planet_position',3); }, 'Integer provided').to.not.throw(Error); expect(function() { qb.set('planet_position',3.5); }, 'float provided').to.not.throw(Error); @@ -63,7 +63,7 @@ describe('set()', function() { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}); qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); - + qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}, {"`galaxy_class`": "'C'"}]); @@ -99,4 +99,4 @@ describe('set()', function() { qb.set('galaxy_class','D', false); qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'C'"}, {galaxy_class: 'D'}]); }); -}); \ No newline at end of file +}); From 6a02e87dc0b820d1bf969e5fc193683c5bb1f0e9 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 4 Sep 2015 11:13:20 -0400 Subject: [PATCH 082/200] Fixed a few minor bugs --- package.json | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) mode change 100755 => 100644 package.json diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 8ca4e21..168274f --- a/package.json +++ b/package.json @@ -1,36 +1,36 @@ { - "name" : "node-querybuilder", - "version": "0.9.0", - "author": "Kyle Farris ", - "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", - "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", - "scripts": { - "test": "make test" - }, - "homepage": "http://kylefarris.github.io/node-querybuilder/", - "repository": { - "type": "git", - "url": "git@github.com:kylefarris/node-querybuilder.git" - }, - "contributors": [ - "Martin Tajur ", - "Daniel Bretoi ", - "Daehyub Kim " - ], - "dependencies": { - "mssql": "1.3.*", - "pg": "4.2.*", - "mysql": "2.5.*", - "sqlite3": "3.0.*", - "mongodb": "1.4.*", - "underscore": "*" - }, - "devDependencies": { - "chai": "~1.4.2", - "mocha": "*" - }, - "main" : "./", - "engines": { - "node": "*" - } + "name": "node-querybuilder", + "version": "0.9.1", + "author": "Kyle Farris ", + "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", + "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", + "scripts": { + "test": "make test" + }, + "homepage": "http://kylefarris.github.io/node-querybuilder/", + "repository": { + "type": "git", + "url": "git@github.com:kylefarris/node-querybuilder.git" + }, + "contributors": [ + "Martin Tajur ", + "Daniel Bretoi ", + "Daehyub Kim " + ], + "dependencies": { + "mssql": "1.3.*", + "pg": "4.2.*", + "mysql": "2.5.*", + "sqlite3": "3.0.*", + "mongodb": "1.4.*", + "underscore": "*" + }, + "devDependencies": { + "chai": "~1.4.2", + "mocha": "*" + }, + "main": "./", + "engines": { + "node": "*" + } } From b1e9c52628ade8c3f008fd7ba74425aafece87f0 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 4 Sep 2015 11:36:11 -0400 Subject: [PATCH 083/200] Switched from tabs to spaces in some main files. --- README.md | 128 +- drivers/drivers.json | 126 +- drivers/mysql/adapters.js | 470 ++--- drivers/mysql/query_builder.js | 3410 ++++++++++++++++---------------- drivers/mysql/query_exec.js | 390 ++-- test/mysql/tests-insert.js | 262 +-- 6 files changed, 2393 insertions(+), 2393 deletions(-) diff --git a/README.md b/README.md index e57eb4e..24e7148 100755 --- a/README.md +++ b/README.md @@ -40,17 +40,17 @@ Table of Contents * [Execution Methods](#execution-methods) * [Other Library-Specific Methods](#other-library-specific-methods) * [Contribute](#contribute) - + Database Drivers ================ Currently Written: ------------------ * MySQL / MariaDB - + Coming Soon: ------------ - + * Postgres * Microsoft SQL Server * Oracle @@ -88,10 +88,10 @@ qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', function(err,response) { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); - + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 console.log("Query Ran: " + qb.last_query()); - + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] console.dir(response); } @@ -185,7 +185,7 @@ Choosing the Connection Type This library currently supports 3 connection methods: * ***single*** (default) - * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. + * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. * **All drivers must have this connection type**. * ***pool*** * This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. @@ -250,7 +250,7 @@ Chainable methods can be called as many times as you'd like in any order you lik This method is used to specify the fields to pull into the resultset when running SELECT-like queries. | Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | +| :-------- | :-------- | :----- | :-------------------------------------------- | | fields | String/Array | Required | The fields in which to grab from the database | | escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | @@ -380,7 +380,7 @@ qb.select_max('age', 'max_age').get('users',callback); This SQL command is used to find the average value for a specific field within a resultset. | Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| +| :-------- | :-------- | :----- | :-------------------------------------| | field | String | Required | The field to get the average value of | | alias | String | NULL | Optional alias to rename field | @@ -453,7 +453,7 @@ qb.select('id,name,description').from('users').get(callback); ***Comma-Seperated*** ```javascript -// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` +// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) qb.select('u.id,u.name,u,description,g.name as group_name') .from('users u, groups g') @@ -463,7 +463,7 @@ qb.select('u.id,u.name,u,description,g.name as group_name') ***Array of Tables*** ```javascript -// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` +// SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) qb.select('u.id,u.name,u,description,g.name as group_name') .from(['users u', 'groups g']) @@ -509,7 +509,7 @@ The table/view and the relationship of it to the main table/view (see: `.from()` If no direction is specified, "left" will be used: ```javascript -// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') @@ -520,7 +520,7 @@ qb.select('u.id,u.name,t.name as type_name').from('users u') You may specify a direction: ```javascript -// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` // FROM `users` `u` // RIGHT OUTER JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') @@ -593,7 +593,7 @@ qb.select('planet').where("order <= 3 AND class = 'M'").get('planets',callback); You can pass a non-empty array as a value and that portion will be treated as a call to `.where_in()`: ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `planet_count` >= 4, `star` IN('Sun', 'Betelgeuse') qb.select('star_system') .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']) @@ -606,7 +606,7 @@ qb.select('star_system') This method functions identically to [.where()](#where) except that it joins clauses with 'OR' instead of 'AND'. ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `star` = 'Sun' OR `star` = 'Betelgeuse' qb.select('star_system').where('star', 'Sun') .or_where('star', 'Betelgeuse') @@ -619,7 +619,7 @@ qb.select('star_system').where('star', 'Sun') This will create a "WHERE IN" statement in traditional SQL which is useful when you're trying to find rows with fields matching many different values... It will be joined with existing "WHERE" statements with 'AND'. ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `star` IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_in('star',stars).get('star_systems',callback); @@ -631,7 +631,7 @@ qb.select('star_system').where_in('star',stars).get('star_systems',callback); Same as `.where_in()` except the clauses are joined by 'OR'. ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `planet_count` = 4 OR `star` IN('Sun', 'Betelgeuse') var stars = ['Sun', 'Betelgeuse']; qb.select('star_system').where('planet_count',4) @@ -645,7 +645,7 @@ qb.select('star_system').where('planet_count',4) Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clauses are joined with 'AND'. ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); @@ -657,7 +657,7 @@ qb.select('star_system').where_not_in('star',stars).get('star_systems',callback) Same as `.where_not_in()` except that clauses are joined with 'OR'. ```javascript -// SELECT `star_system` FROM `star_systems` +// SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun', 'Betelgeuse') // OR `planet_count` NOT IN [2,4,6,8] var stars = ['Sun', 'Betelgeuse']; @@ -719,7 +719,7 @@ qb.select('first_name').like('first_name', 'kim', 'none').get('users',callback); If you'd like to have multiple like clauses, you can do that by calling like multiple times: ```javascript -// SELECT `first_name` FROM `users` +// SELECT `first_name` FROM `users` // WHERE `first_name` LIKE 'Kim%' // AND `middle_name` LIKE '%lyt%' // AND `last_name` LIKE '%arris' @@ -733,7 +733,7 @@ qb.select('first_name') Or you can do it with an object of field/match pairs. If you want to pass a wildcard side, provide `null` as the second paramter and the side as the third. **Note**: All `match` values in an object will share the same wildcard side. ```javascript -// SELECT `first_name` FROM `users` +// SELECT `first_name` FROM `users` // WHERE `first_name` LIKE '%ly' // AND `middle_name` LIKE '%the' // AND `last_name` LIKE '%is' @@ -750,7 +750,7 @@ This is exactly the same as the `.like()` method except that the clauses are joi **Example** ```javascript -// SELECT `first_name` FROM `users` +// SELECT `first_name` FROM `users` // WHERE `first_name` LIKE 'Kim%' // OR `middle_name` LIKE '%lyt%' // OR `last_name` LIKE '%arris' @@ -769,7 +769,7 @@ This is exactly the same as the `.like()` method except that it creates "NOT LIK **Example** ```javascript -// SELECT `first_name` FROM `users` +// SELECT `first_name` FROM `users` // WHERE `first_name` NOT LIKE 'A%' // AND `middle_name` NOT LIKE 'B%' // AND `last_name` NOT LIKE 'C%' @@ -786,7 +786,7 @@ This is exactly the same as the `.not_like()` method except that the clauses are **Example** ```javascript -// SELECT `first_name` FROM `users` +// SELECT `first_name` FROM `users` // WHERE `first_name` NOT LIKE 'A%' // OR `middle_name` NOT LIKE 'B%' // OR `last_name` NOT LIKE 'C%' @@ -842,7 +842,7 @@ This method works exactly the same way as the `.where()` method works with the e If you just want to add a single having clause: ```javascript -// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` = 5 qb.group_by('id').having('num_planets',5).count('star_systems',callback); @@ -851,7 +851,7 @@ qb.group_by('id').having('num_planets',5).count('star_systems',callback); If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: ```javascript -// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > 5 qb.group_by('id').having('num_planets >',5).count('star_systems',callback); @@ -860,7 +860,7 @@ qb.group_by('id').having('num_planets >',5).count('star_systems',callback); You can conveniently pass an object of key:value pairs (which can also contain custom operators): ```javascript -// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > 5 qb.group_by('id').having({'num_planets >': 5}).count('star_systems',callback); @@ -869,7 +869,7 @@ qb.group_by('id').having({'num_planets >': 5}).count('star_systems',callback); You can construct complex WHERE clauses manually and they will be escaped properly. *Please, for custom clauses containing subqueries, make sure you escape everything properly!* ***ALSO NOTE:*** with this method, there may be conflicts between database drivers! ```javascript -// SELECT COUNT(*) AS `num_planets` FROM `star_systems` +// SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > (5+2) qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems',callback); @@ -881,7 +881,7 @@ qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems This method functions identically to [.having()](#having) except that it joins clauses with 'OR' instead of 'AND'. ```javascript -// SELECT SUM(planets) AS `num_planets`, SUM(moons) AS `num_moons` FROM `star_systems` +// SELECT SUM(planets) AS `num_planets`, SUM(moons) AS `num_moons` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` >= 5 OR `num_moons` <= 10 qb.group_by('id') @@ -1063,8 +1063,8 @@ The final parameter of every execution method will be a callback function. For ` | API Method(s) | Response Format | | :-------------------------------- | :-------------------------------------------------------------------------------------------- | -| get(), get_where() | `[{field:value,field2:value2},{field:value, field2:value2}]` | -| count() | Integer (ex. `578`) | +| get(), get_where() | `[{field:value,field2:value2},{field:value, field2:value2}]` | +| count() | Integer (ex. `578`) | | insert(), update(), delete() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | | insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | @@ -1178,17 +1178,17 @@ Just a more-complicated example for the sake of it (note: using connection pool) ```javascript /** - * SELECT - * `g`.`name`, + * SELECT + * `g`.`name`, * `g`.`diameter`, - * `g`.`type_id`, - * `gt`.`name` AS `type`, + * `g`.`type_id`, + * `gt`.`name` AS `type`, * COUNT(`s`.`id`) as `num_stars` * FROM `galaxies` `g` * LEFT JOIN `galaxy_types` `gt` ON `gt`.`id`=`g`.`type_id` * LEFT JOIN `stars` `s` ON `s`.`galaxy_id`=`g`.`id` * GROUP BY `g`.`id` - * ORDER BY `g`.`name` ASC + * ORDER BY `g`.`name` ASC * LIMIT 10 **/ qb.limit(10) @@ -1202,11 +1202,11 @@ qb.limit(10) .get(function(err, response, conn) { conn.release(); if (err) return console.error(err); - + for (var i in response) { var row = response[i]; - console.log("The " + row.name + " is a " + row.diameter - + " lightyear-wide " + row.type + " galaxy with " + console.log("The " + row.name + " is a " + row.diameter + + " lightyear-wide " + row.type + " galaxy with " + row.num_stars + " stars."); } }); @@ -1310,19 +1310,19 @@ app.post('/update_account', function(req, res) { var user_id = req.session.user_id; var sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; var sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; - + var data = { first_name: sanitize_name(req.body.first_name), last_name: sanitize_name(req.body.last_name), age: sanitize_age(req.body.last_name), bio: req.body.bio, }; - + pool.get_connection(function(qb) { qb.update('users', data, {id:user_id}, function(err, res) { qb.release(); if (err) return console.error(err); - + var page_data = { prefill: data, } @@ -1383,10 +1383,10 @@ var where = {'last_updated <' : '2015-01-01'} qb.update_batch('galaxies', dataset, key, where, function(err, res) { if (err) return console.error(err); - - /* - * UPDATE `galaxies` - * SET + + /* + * UPDATE `galaxies` + * SET * `name` = CASE * WHEN `id` = 4569 THEN 'Cartwheel' * WHEN `id` = 5631 THEN 'Black Eye' @@ -1439,19 +1439,19 @@ var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/add_article', function(req, res) { var user_id = req.session.user_id; - + var data = { title: req.body.first_name, body: req.body.last_name, author: user_id, publish_date: sanitize_age(req.body.last_name) }; - + pool.get_connection(function(qb) { qb.insert('articles', data, function(err, res) { qb.release(); if (err) return console.error(err); - + var page_data = { article_id: res.insert_id, } @@ -1493,8 +1493,8 @@ var data = [ qb.insert_batch('db_engines', data, function(err, res) { if (err) throw err; - - // INSERT INTO `db_engines` (`name`, `version`) + + // INSERT INTO `db_engines` (`name`, `version`) // VALUES ('MySQL', '5.5.40'), ('Mongo', '2.6.7'), ('Postgres', '8.4'); console.log(qb.last_query()); }); @@ -1523,7 +1523,7 @@ Object containing information about the result of the query. ```javascript /* * Current Table Structure: - * + * * [ * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, @@ -1535,10 +1535,10 @@ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); qb.insert_ignore('db_engines', data, function(err, res) { if (err) throw err; var data = {name: 'Postgres', version: '8.4'}; - + // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); console.log(qb.last_query()); - + // 0 (because this data already exists...) console.log(res.affected_rows); }); @@ -1550,16 +1550,16 @@ This time we'll do it with an `on_dupe` string qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { if (err) throw err; var data = {name: 'Postgres', version: '8.4'}; - + // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); console.log(qb.last_query()); - + // 1 (because we updated the last_modified field) console.log(res.affected_rows); - + /* * Resulting Table Structure: - * + * * [ * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, @@ -1600,16 +1600,16 @@ var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/delete_comment/:id', function(req, res) { var comment_id = req.params.id; - + pool.get_connection(function(qb) { qb.get('comments', {id: id}, function(err, res) { if (err) return console.error(err); var article_id = res.article_id; - + qb.delete('comments', {id: id}, function(err, res) { qb.release(); if (err) return console.error(err); - + var page_data = { num_removed: res.affected_rows, } @@ -1780,7 +1780,7 @@ pool.get_connection(function(qb) { (function update_user() { var user = users.shift(); user.username = user.username.replace(/\^|/,''); - + qb.update('users', user, {id: user.id}, function(err, res) { if (user.length > 0) { setTimeout(update_user,0); @@ -1858,7 +1858,7 @@ This can be used to excape a value using your driver's native escape method. If **Example** -```javascript +```javascript var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; qb.query(sql, function(err, res) { @@ -1903,7 +1903,7 @@ var sql = qb .like('username','k','after') .get_compiled_select(); -// SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' +// SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' console.log(sql); ``` @@ -1967,7 +1967,7 @@ var sql = qb .where('id',4321) .set(data) .get_compiled_update('users'); - + // UPDATE `users` SET `password` = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' WHERE `id` = 4321 console.log(sql); ``` diff --git a/drivers/drivers.json b/drivers/drivers.json index 8abbe3a..8af906c 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -1,64 +1,64 @@ { - "mysql": { - "connection_types": { "single": true, "pool": true, "cluster": true }, - "versions": { - "2.5.4": { - "path": "./drivers/mysql/", - "active": true - }, - "default": { - "version": "2.5.4" - } - } - }, - "mssql": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "1.3.0": { - "path": "./drivers/mssql/", - "active": false - }, - "default": "1.3.0" - } - }, - "oracle": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "0.3.7": { - "path": "./drivers/oracle/", - "active": false - }, - "default": "0.3.7" - } - }, - "postgres": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "4.2.0": { - "path": "./drivers/postgres/", - "active": false - }, - "default": "4.2.0" - } - }, - "sqlite": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "3.0.4": { - "path": "./drivers/sqlite/", - "active": false - }, - "default": "3.0.4" - } - }, - "mongo": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "1.4.30": { - "path": "./drivers/mongo/", - "active": false - }, - "default": "1.4.30" - } - } -} \ No newline at end of file + "mysql": { + "connection_types": { "single": true, "pool": true, "cluster": true }, + "versions": { + "2.5.4": { + "path": "./drivers/mysql/", + "active": true + }, + "default": { + "version": "2.5.4" + } + } + }, + "mssql": { + "connection_types": { "single": true, "pool": false, "cluster": false }, + "versions": { + "1.3.0": { + "path": "./drivers/mssql/", + "active": false + }, + "default": "1.3.0" + } + }, + "oracle": { + "connection_types": { "single": true, "pool": false, "cluster": false }, + "versions": { + "0.3.7": { + "path": "./drivers/oracle/", + "active": false + }, + "default": "0.3.7" + } + }, + "postgres": { + "connection_types": { "single": true, "pool": false, "cluster": false }, + "versions": { + "4.2.0": { + "path": "./drivers/postgres/", + "active": false + }, + "default": "4.2.0" + } + }, + "sqlite": { + "connection_types": { "single": true, "pool": false, "cluster": false }, + "versions": { + "3.0.4": { + "path": "./drivers/sqlite/", + "active": false + }, + "default": "3.0.4" + } + }, + "mongo": { + "connection_types": { "single": true, "pool": false, "cluster": false }, + "versions": { + "1.4.30": { + "path": "./drivers/mongo/", + "active": false + }, + "default": "1.4.30" + } + } +} diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 7a8fb4f..25c38e1 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,239 +1,239 @@ var Adapters = function(nqb) { - - var _ = require('underscore'); - - // Load MySQL Driver - var mysql = require('mysql'); - - // Verify setting property exists - if (!nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } - - // Enable debugging if necessary - this.debugging = false; - if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { - this.debugging = true; - delete nqb.settings.qb_debug; - } - - // Verify that required fields are provided... - if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; - if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); - - this.connection_settings = {}; - var that = this; - - // **************************************************************************** - // Map generic NQB connection settings to node-mysql's format - // ---- - // NOTE: MySQL connection settings names are the same as Node Querybuilder, - // it's just good practice to go ahead and do this in case things change. - // **************************************************************************** - var map_connection_settings = function() { - that.connection_settings = { - host: nqb.settings.host, - user: nqb.settings.user, - password: nqb.settings.password - } - if (nqb.settings.hasOwnProperty('database')) { - that.connection_settings.database = nqb.settings.database; - delete nqb.settings.database - } - if (nqb.settings.hasOwnProperty('port')) { - that.connection_settings.port = nqb.settings.port; - delete nqb.settings.port - } - - // Remove mapped settings: - delete nqb.settings.host - delete nqb.settings.user - delete nqb.settings.password - - // Merge any driver-specific settings into connection settings - that.connection_settings = _.extend(that.connection_settings, nqb.settings); - } - - map_connection_settings(); - - - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object - // **************************************************************************** - var get_query_builder = function() { - try { - return require('./query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object - // **************************************************************************** - var get_query_exec = function(qb, conn) { - try { - return require('./query_exec.js').QueryExec(qb, conn); - } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Generic Single Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - var Adapter = function(settings) { - var pool, connection; - - // If the Pool object is instatiating this Adapter, use it's connection - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } - // Otherwise, let's create a new connection - else { - connection = new mysql.createConnection(that.connection_settings); - } - - var qb = get_query_builder(); - var qe = get_query_exec(qb, connection); - - var adapter = _.extend({ - connection_settings: function() { - return that.connection_settings; - }, - - connect: function(callback) { - return connection.connect(callback); - }, - - connection: function() { - return connection; - }, - - escape: function(str) { - return connection.escape(str); - }, - - escape_id: function(str) { - return connection.escapeId(str); - }, - - disconnect: function(callback) { - return connection.end(callback); - }, - - release: function() { - if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - pool.releaseConnection(connection); - } - }, qb, qe); - - return adapter; - }; - - // **************************************************************************** - // Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - var Pool = function() { - // Return Pool Object - var return_pool = function() { - return { - pool: function() { - return nqb.pool; - }, - get_connection: function(callback) { - if (null === nqb.pool) { - var error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - - nqb.pool.getConnection(function (err, connection) { - if (err) throw err; - var adapter = new Adapter({ - pool: { - pool: nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - nqb.pool.end(callback); - } - } - }; - - // Create pool for node-querybuild object if it doesn't already have one. - if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { - // Create connection Pool - nqb.pool = mysql.createPool(that.connection_settings); - - // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) - if (that.debugging === true) { - nqb.pool.getConnection(function(err, connection) { - connection.query('SELECT 1 + 1 AS solution', function(err) { - connection.release(); - if (err) { - console.error(err); - } else { - console.log('mysql connection pool created'); - } - }); - }); - } - } - return return_pool(); - }; - - // **************************************************************************** - // Clustered Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - var Cluster = function() { - - }; - - // **************************************************************************** - // Basic Description - // ----- - // @param - // @return - // **************************************************************************** - var determine_adapter = function() { - switch(nqb.connection_type) { - case 'cluster': - return new Cluster(); - break; - case 'pool': - return new Pool(); - break; - case 'single': - default: - return new Adapter({}); - break; - } - } - - return determine_adapter(); + + var _ = require('underscore'); + + // Load MySQL Driver + var mysql = require('mysql'); + + // Verify setting property exists + if (!nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + // Enable debugging if necessary + this.debugging = false; + if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { + this.debugging = true; + delete nqb.settings.qb_debug; + } + + // Verify that required fields are provided... + if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); + if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; + if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + + this.connection_settings = {}; + var that = this; + + // **************************************************************************** + // Map generic NQB connection settings to node-mysql's format + // ---- + // NOTE: MySQL connection settings names are the same as Node Querybuilder, + // it's just good practice to go ahead and do this in case things change. + // **************************************************************************** + var map_connection_settings = function() { + that.connection_settings = { + host: nqb.settings.host, + user: nqb.settings.user, + password: nqb.settings.password + } + if (nqb.settings.hasOwnProperty('database')) { + that.connection_settings.database = nqb.settings.database; + delete nqb.settings.database + } + if (nqb.settings.hasOwnProperty('port')) { + that.connection_settings.port = nqb.settings.port; + delete nqb.settings.port + } + + // Remove mapped settings: + delete nqb.settings.host + delete nqb.settings.user + delete nqb.settings.password + + // Merge any driver-specific settings into connection settings + that.connection_settings = _.extend(that.connection_settings, nqb.settings); + } + + map_connection_settings(); + + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object + // **************************************************************************** + var get_query_builder = function() { + try { + return require('./query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object + // **************************************************************************** + var get_query_exec = function(qb, conn) { + try { + return require('./query_exec.js').QueryExec(qb, conn); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Generic Single Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Adapter = function(settings) { + var pool, connection; + + // If the Pool object is instatiating this Adapter, use it's connection + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } + // Otherwise, let's create a new connection + else { + connection = new mysql.createConnection(that.connection_settings); + } + + var qb = get_query_builder(); + var qe = get_query_exec(qb, connection); + + var adapter = _.extend({ + connection_settings: function() { + return that.connection_settings; + }, + + connect: function(callback) { + return connection.connect(callback); + }, + + connection: function() { + return connection; + }, + + escape: function(str) { + return connection.escape(str); + }, + + escape_id: function(str) { + return connection.escapeId(str); + }, + + disconnect: function(callback) { + return connection.end(callback); + }, + + release: function() { + if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + pool.releaseConnection(connection); + } + }, qb, qe); + + return adapter; + }; + + // **************************************************************************** + // Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Pool = function() { + // Return Pool Object + var return_pool = function() { + return { + pool: function() { + return nqb.pool; + }, + get_connection: function(callback) { + if (null === nqb.pool) { + var error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + + nqb.pool.getConnection(function (err, connection) { + if (err) throw err; + var adapter = new Adapter({ + pool: { + pool: nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + nqb.pool.end(callback); + } + } + }; + + // Create pool for node-querybuild object if it doesn't already have one. + if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { + // Create connection Pool + nqb.pool = mysql.createPool(that.connection_settings); + + // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) + if (that.debugging === true) { + nqb.pool.getConnection(function(err, connection) { + connection.query('SELECT 1 + 1 AS solution', function(err) { + connection.release(); + if (err) { + console.error(err); + } else { + console.log('mysql connection pool created'); + } + }); + }); + } + } + return return_pool(); + }; + + // **************************************************************************** + // Clustered Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + var Cluster = function() { + + }; + + // **************************************************************************** + // Basic Description + // ----- + // @param + // @return + // **************************************************************************** + var determine_adapter = function() { + switch(nqb.connection_type) { + case 'cluster': + return new Cluster(); + break; + case 'pool': + return new Pool(); + break; + case 'single': + default: + return new Adapter({}); + break; + } + } + + return determine_adapter(); }; exports.Adapters = Adapters; diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d5dd68c..359173f 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,1712 +1,1712 @@ var QueryBuilder = function() { - // ------------------------------ GENERIC FUNCTIONS ------------------------------// - var array_values = function(item) { - var keys = Object.keys(item); - var length = keys.length; - var values = Array(length); - for (var i = 0; i < length; i++) { - values[i] = item[keys[i]]; - } - return values; - }; - - var prepare_for_limit_and_offset = function(item,type) { - type = type || 'limit'; - type = type.toLowerCase(); - - if ((typeof item).match(/^(string|number)$/) === null) { - throw new Error("Only integers or integers in the form of a string are allowed"); - } - - if (typeof item === 'string') { - item = item.trim(); - if (item.match(/^\d+$/) === null) { - throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); - } - // Force to an integer - item = parseInt(item); - } - - // Make sure the number is a good one - if (typeof item === 'number') { - // Check for NaN and Infinity - if (item !== +item || item === Infinity) { - throw new Error("You have not provided a valid number to " + type + " by!"); - } - - // Make sure it's positive - if (item < 0) { - throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); - } - - // Only allow integers - if (item % 1 !== 0) { - throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); - } - } - else { - throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); - } - - return item; - } - - var extract_having_parts = function(key,key_array) { - var m; - key = key.trim().replace(/\s+/g,' '); - var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; - - if (m = str_condition.exec(key)) { - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = sstr_condition.exec(key)) { - //console.log("Key has sstring value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = num_condition.exec(key)) { - //console.log("Key has numeric value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = bool_condition.exec(key)) { - //console.log("Key has boolean value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else { - throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); - } - - return key_array; - } - - // Simply setting all properties to [] causes reference issues in the parent class. - var clear_array = function(a,debug) { - if (debug === true) { - console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (var key in a) { - if (a.hasOwnProperty(key)) { - delete a[key]; - } - } - } - else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { - while (a.length > 0) { - a.pop(); - } - } - if (debug === true) { - console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - }; - - // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - var track_aliases = function(qb,table) { - if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (var i in table) { - var t = table[i]; - track_aliases(qb,t); - } - return; - } - - // Does the string contain a comma? If so, we need to separate - // the string into discreet statements - if (table.indexOf(',') !== -1) { - return track_aliases(qb,table.split(',')); - } - - // if a table alias is used we can recognize it by a space - if (table.indexOf(' ') !== -1) { - // if the alias is written with the AS keyword, remove it - table = table.replace(/\s+AS\s+/gi, ' '); - - // Grab the alias - var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); - - // Store the alias, if it doesn't already exist - if(qb.aliased_tables.indexOf(alias) == -1) { - qb.aliased_tables.push(alias); - } - } - }; - - var create_aliases_from_table = function(item) { - if (item.indexOf('.') !== -1) { - return item.split('.').reverse()[0]; - } - - return item; - }; - - var escape_identifiers = function(item) { - if (!item || item === '*') { - return item; - } - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (var i in item) { - item[i] = escape_identifiers(item[i]); - } - return item; - } - else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { - return item; - } - - var str; - if (item.indexOf('.' + '*') !== -1) { - str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); - } - else { - str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); - } - - // remove duplicates if the user already included the escape - return str.replace(/[`]+/g,'`'); - }; - - var protect_identifiers = function(qb,item,protect_identifiers) { - if (item === '') return item; - - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - var escaped_array = {}; - - for (k in item) { - var v = item[k]; - escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); - } - - return escaped_array; - } - - // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/g, ' '); - - // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - var has_alias = item.lastIndexOf(')'); - var alias; - if (has_alias >= 0) { - alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = escape_identifiers(alias); - if (alias != '') - alias = ' AS ' + alias; - item = item.substr(0,has_alias + 1); - } else { - alias = ''; - } - - return item + alias; - } - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (item.match(/\sAS\s/ig)) { - var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else if (item.indexOf(' ') !== -1) { - var alias_index = item.indexOf(' '); - - var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - else { - var alias = ''; - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - var parts = item.split('.'); - var first_seg = parts[0].trim().replace(/`/g,''); - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (qb.aliased_tables.indexOf(first_seg) !== -1) { - if (protect_identifiers === true) { - for (var key in parts) { - var val = parts[key]; - if (val !== '*') { - parts[key] = escape_identifiers(val); - } - } - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = escape_identifiers(item); - } - - return item + alias; - } - if (protect_identifiers === true) { - item = escape_identifiers(item); - } - - return item + alias; - }; - - var has_operator = function (str) { - if(typeof str === 'string' && str.length > 0) { - var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); - if(match === null) { - return false; - } - } - return true; - }; - - var qb_escape = function(qb,str) { - var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); - var do_escape = SqlString.escape; - - if (typeof str === 'boolean') { - str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { - str *= 1; - } else { - str = do_escape(str); - } - - return str; - }; - - - // ---------------------------- SQL BUILD TOOLS ----------------------------// - var build_where_clause = function(qb) { - var sql = ''; - if(qb.where_array.length > 0) { - sql += " WHERE "; - } - sql += qb.where_array.join(" "); - return sql; - }; - - var build_from_clause = function(qb) { - var sql = ''; - if(qb.from_array.length > 0) { - sql += " FROM "; - } else { - throw new Error("You have not provided any tables, views, or store procedures for this query!!"); - } - sql += '(' + qb.from_array.join(', ') + ')'; - return sql; - }; - - var build_join_string = function(qb) { - var sql = ''; - sql += qb.join_array.join(' '); - if(sql.length > 0) sql = ' ' + sql; - return sql; - }; - - var build_group_by_clause = function(qb) { - if (qb.group_by_array.length <= 0) return ''; - - var sql = ' GROUP BY '; - sql += qb.group_by_array.join(', '); - return sql; - }; - - var build_having_clause = function(qb) { - if (qb.having_array.length <= 0) return ''; - - var sql = ' HAVING '; - sql += qb.having_array.join(' '); - return sql; - }; - - var build_order_by_clause = function(qb) { - if (qb.order_by_array.length <= 0) return ''; - - var sql = ' ORDER BY '; - sql += qb.order_by_array.join(', '); - - return sql; - }; - - var build_limit_clause = function(sql, limit, offset) { - if (!limit) return sql; - - sql += ' '; - - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; - } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; - }; - - var compile_select = function(qb) { - var distinct_clause = qb.distinct_clause[0] || ''; - var sql = 'SELECT ' + distinct_clause; - if (qb.select_array.length === 0) { - sql += '*'; - } else { - sql += qb.select_array.join(', '); - } - - sql += build_from_clause(qb) - + build_join_string(qb) - + build_where_clause(qb) - + build_group_by_clause(qb) - + build_having_clause(qb) - + build_order_by_clause(qb); - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - sql = build_limit_clause(sql,limit_to,offset_val); - return sql; - }; - - var compile_delete = function(qb) { - if (qb.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - qb.from_array = qb.from_array.slice(0,1); - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); - return build_limit_clause(sql,limit_to,offset_val); - }; - - var compile_update = function(qb) { - var valstr = []; - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; - valstr.push(key + ' = ' + val); - } - - if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); - } else { - if (qb.from_array.length === 0) { - throw new Error("You haven't provided any tables to build UPDATE query with!"); - return ''; - } - throw new Error("You have provided too many tables to build UPDATE query with!"); - return ''; - } - - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; - - var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); - sql += build_where_clause(qb); - sql += build_order_by_clause(qb); - return build_limit_clause(sql, limit_to, offset_val); - }; - - var compile_insert = function(qb, ignore, suffix) { - var keys = []; - var values = []; - suffix = suffix || ''; - - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); - } else { - if (qb.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; - }; - - // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// - return { - where_array: [], - where_in_array: [], - from_array: [], - join_array: [], - select_array: [], - set_array: [], // has to be array to work as reference - order_by_array: [], - group_by_array: [], - having_array: [], - limit_to: [], // has to be array to work as reference - offset_val: [], // has to be array to work as reference - join_clause: [], - last_query_string: [], // has to be array to work as reference - distinct_clause: [], // has to be array to work as reference - aliased_tables: [], - - reset_query: function(new_last_query,debug) { - clear_array(this.where_array,debug); - clear_array(this.where_in_array); - clear_array(this.from_array); - clear_array(this.join_array); - clear_array(this.select_array); - clear_array(this.set_array); - clear_array(this.order_by_array); - clear_array(this.group_by_array); - clear_array(this.having_array); - clear_array(this.limit_to); - clear_array(this.offset_val); - clear_array(this.join_clause); - clear_array(this.distinct_clause); - clear_array(this.aliased_tables); - - clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - }, - - where: function(key, value, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); - } - - value = (typeof value === 'undefined' ? null : value); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - }, - - or_where: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - value = value || null; - - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); - } - return this._where(key, value, 'OR ', escape); - }, - - _where: function(key, value, type, escape) { - value = (typeof value === 'undefined' ? null : value); - type = type || 'AND '; - escape = (typeof escape === 'boolean' ? escape : true); - - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } - } - - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { - var filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - var that = this; - var parse_statement = function(statement,joiner) { - var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || joiner.match(/AND/i)) { - that.where(key, value, true); - } else { - that.or_where(key, value, true); - } - } - }; - parse_statement(filters.shift(),null); - while (filters.length > 0) { - var joiner = filters.shift(); - var statement = filters.shift(); - parse_statement(statement, joiner); - } - return this; - } - } - - var key_array = {}; - key_array[key] = value; - key = key_array; - } - - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); - } - - for (var k in key) { - var v = key[k]; - - if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { - return this._where_in(k,v,false,type,escape); - } - - var prefix = (this.where_array.length == 0 ? '' : type); - - if (v === null && !has_operator(k)) { - k += ' IS NULL'; - } - - if (v !== null) { - if (escape === true) { - k = protect_identifiers(this,k,escape); - v = ' ' + qb_escape(this,v); - } - - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + qb_escape(this,v); - } - - if (!has_operator(k)) { - k += ' ='; - } - } - else { - k = protect_identifiers(this,k,escape); - } - - if (v) { - this.where_array.push(prefix+k+v); - } - else { - this.where_array.push(prefix+k); - } - } - - return this; - }, - - where_in: function(key, values, escape) { - return this._where_in(key,values,false,'AND ', escape); - }, - - or_where_in: function(key, values, escape) { - return this._where_in(key,values,false,'OR ', escape); - }, - - where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'AND ', escape); - }, - - or_where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'OR ', escape); - }, - - _where_in: function(key, values, not, type, escape) { - key = key || ''; - values = values || []; - type = type || 'AND '; - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } - - // Values must be an array... - if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } - - for (var i in values) { - this.where_in_array.push(qb_escape(this,values[i])); - } - - var prefix = (this.where_array.length == 0 ? '' : type); - var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); - - // reset the array for multiple calls - clear_array(this.where_in_array); - return this; - }, - - like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - }, - - not_like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - }, - - or_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - }, - - or_not_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - }, - - _like: function(field, match, type, side, not) { - match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); - type = type || 'AND '; - side = (typeof side === 'undefined' ? 'both' : side); - not = not || ''; - - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if ((typeof field).match(/^(string|object)$/) === null) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } - - if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } - - var field_array = {}; - field_array[field] = match; - field = field_array; - } - - for(k in field) { - var like_statement; - var v = field[k]; - var k = protect_identifiers(this,k.trim()); - - // Make sure value is only string, number, or boolean - if ((typeof v).match(/^(string|number|boolean)$/) === null) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } - - this._where(like_statement,null,type,false); - } - - return this; - }, - - from: function(from) { - if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { - from = [from]; - } - for (var i in from) { - var val = from[i]; - - if (val.trim() === '') continue; - - if (val.indexOf(',') !== -1) { - var objects = val.split(','); - for (var j in objects) { - var v = objects[j].trim(); - - track_aliases(this,v); - - this.from_array.push(protect_identifiers(this,v,true)); - } - } - else { - val = val.trim(); - - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - track_aliases(this,val); - - this.from_array.push(protect_identifiers(this,val,true)); - } - } - - return this; - }, - - join: function(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - track_aliases(this,table); - - // Split multiple conditions - var regex = /\sAND\s|\sOR\s/ig; - var m = relation.match(regex); - var matches = []; - var k, temp, temp_match, match; - if (escape === true && m) { - while(k = regex.exec(relation)) { - matches.push(k); - } - - var new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); - } - else if (!has_operator(relation)) { - relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = protect_identifiers(this,table,true); - } - - var join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - }, - - select: function(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } - } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); - } - - if (typeof escape !== 'boolean') escape = true; - - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - var m, open_paren_index, inner_parenthesis; - var reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } - } - } - } - else { - select = [select]; - } - } - } - - for (var i in select) { - var val = select[i].trim(); - - if(val !== '') { - this.select_array.push(protect_identifiers(this,val,escape)); - } - } - return this; - }, - - select_min: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MIN'); - }, - - select_max: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MAX'); - }, - - select_avg: function(select,alias) { - return this._min_max_avg_sum(select,alias,'AVG'); - }, - - select_sum: function(select,alias) { - return this._min_max_avg_sum(select,alias,'SUM'); - }, - - _min_max_avg_sum: function(select,alias,type) { - select = select || ''; - alias = alias || ''; - type = type || 'MAX'; - - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } - - type = type.toUpperCase(); - - if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } - - if (alias == '') { - alias = create_aliases_from_table(select.trim()); - } - - var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; - - this.select_array.push(sql); - - return this; - }, - - distinct: function(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - - if (do_distinct) { - clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - clear_array(this.distinct_clause); - } - return this; - }, - - group_by: function(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } - - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - - for (var key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } - - var val = by[key].trim(); - - if (val !== '') { - this.group_by_array.push(protect_identifiers(this,val)); - } - } - return this; - }, - - having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - }, - - or_having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - }, - - _having: function(key, value, type, escape) { - type = type || 'AND '; - - var m; - var key_array = {}; - var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); - - if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); - } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; - } - else { - key = extract_having_parts(key,key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (var i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = extract_having_parts(key[i],key_array); - } - } - key = key_array; - } - } - } - else { - throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); - } - - - for (var k in key) { - var v = key[k]; - var prefix = (this.having_array.length == 0 ? '' : type); - - if (escape === true) { - k = protect_identifiers(this,k); - } - - if (v === null) { - k += ' IS'; - } - else if (!has_operator(k)) { - k += ' ='; - } - - if (v != '') { - v = ' ' + qb_escape(this,v); - } - - this.having_array.push(prefix + k + v); - } - - return this; - }, - - order_by: function(orderby, direction) { - var m; - var rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { - this.order_by_array.push(rand_word); - return this; - } - - // normalize orderby to be an array of items - if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } - } - - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } - - for (var i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); - - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; - } else { - if (direction.match(/^(ASC|DESC)$/i) || direction === '') { - orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } - } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); - } - - return this; - }, - - limit: function(limit, offset) { - clear_array(this.limit_to); - this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); - - if (offset !== undefined) { - return this.offset(offset); - } - - return this; - }, - - offset: function(offset) { - clear_array(this.offset_val); - this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); - return this; - }, - - set: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) { - throw new Error("set(): Invalid field name provided!"); - } - - if (typeof value === 'undefined') - throw new Error("set(): First param was string but no value (second param) provided to set!"); - - var key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } - - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } - } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); - } - - - // Add each key:value pair to the set_array - for (var i in key) { - var v = key[i]; + // ------------------------------ GENERIC FUNCTIONS ------------------------------// + var array_values = function(item) { + var keys = Object.keys(item); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = item[keys[i]]; + } + return values; + }; + + var prepare_for_limit_and_offset = function(item,type) { + type = type || 'limit'; + type = type.toLowerCase(); + + if ((typeof item).match(/^(string|number)$/) === null) { + throw new Error("Only integers or integers in the form of a string are allowed"); + } + + if (typeof item === 'string') { + item = item.trim(); + if (item.match(/^\d+$/) === null) { + throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); + } + // Force to an integer + item = parseInt(item); + } + + // Make sure the number is a good one + if (typeof item === 'number') { + // Check for NaN and Infinity + if (item !== +item || item === Infinity) { + throw new Error("You have not provided a valid number to " + type + " by!"); + } + + // Make sure it's positive + if (item < 0) { + throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); + } + + // Only allow integers + if (item % 1 !== 0) { + throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); + } + } + else { + throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); + } + + return item; + } + + var extract_having_parts = function(key,key_array) { + var m; + key = key.trim().replace(/\s+/g,' '); + var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; + + if (m = str_condition.exec(key)) { + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = sstr_condition.exec(key)) { + //console.log("Key has sstring value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = num_condition.exec(key)) { + //console.log("Key has numeric value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = bool_condition.exec(key)) { + //console.log("Key has boolean value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else { + throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); + } + + return key_array; + } + + // Simply setting all properties to [] causes reference issues in the parent class. + var clear_array = function(a,debug) { + if (debug === true) { + console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { + for (var key in a) { + if (a.hasOwnProperty(key)) { + delete a[key]; + } + } + } + else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { + while (a.length > 0) { + a.pop(); + } + } + if (debug === true) { + console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + }; + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + var track_aliases = function(qb,table) { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (var i in table) { + var t = table[i]; + track_aliases(qb,t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return track_aliases(qb,table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); + + // Store the alias, if it doesn't already exist + if(qb.aliased_tables.indexOf(alias) == -1) { + qb.aliased_tables.push(alias); + } + } + }; + + var create_aliases_from_table = function(item) { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + }; + + var escape_identifiers = function(item) { + if (!item || item === '*') { + return item; + } + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + for (var i in item) { + item[i] = escape_identifiers(item[i]); + } + return item; + } + else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + return item; + } + + var str; + if (item.indexOf('.' + '*') !== -1) { + str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); + } + else { + str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); + } + + // remove duplicates if the user already included the escape + return str.replace(/[`]+/g,'`'); + }; + + var protect_identifiers = function(qb,item,protect_identifiers) { + if (item === '') return item; + + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + var escaped_array = {}; + + for (k in item) { + var v = item[k]; + escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); + } + + return escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + item = item.replace(/\s+/g, ' '); + + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + var has_alias = item.lastIndexOf(')'); + var alias; + if (has_alias >= 0) { + alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); + alias = escape_identifiers(alias); + if (alias != '') + alias = ' AS ' + alias; + item = item.substr(0,has_alias + 1); + } else { + alias = ''; + } + + return item + alias; + } + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (item.match(/\sAS\s/ig)) { + var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else if (item.indexOf(' ') !== -1) { + var alias_index = item.indexOf(' '); + + var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else { + var alias = ''; + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + var parts = item.split('.'); + var first_seg = parts[0].trim().replace(/`/g,''); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (qb.aliased_tables.indexOf(first_seg) !== -1) { + if (protect_identifiers === true) { + for (var key in parts) { + var val = parts[key]; + if (val !== '*') { + parts[key] = escape_identifiers(val); + } + } + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + } + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + }; + + var has_operator = function (str) { + if(typeof str === 'string' && str.length > 0) { + var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); + if(match === null) { + return false; + } + } + return true; + }; + + var qb_escape = function(qb,str) { + var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); + var do_escape = SqlString.escape; + + if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { + str *= 1; + } else { + str = do_escape(str); + } + + return str; + }; + + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + var build_where_clause = function(qb) { + var sql = ''; + if(qb.where_array.length > 0) { + sql += " WHERE "; + } + sql += qb.where_array.join(" "); + return sql; + }; + + var build_from_clause = function(qb) { + var sql = ''; + if(qb.from_array.length > 0) { + sql += " FROM "; + } else { + throw new Error("You have not provided any tables, views, or store procedures for this query!!"); + } + sql += '(' + qb.from_array.join(', ') + ')'; + return sql; + }; + + var build_join_string = function(qb) { + var sql = ''; + sql += qb.join_array.join(' '); + if(sql.length > 0) sql = ' ' + sql; + return sql; + }; + + var build_group_by_clause = function(qb) { + if (qb.group_by_array.length <= 0) return ''; + + var sql = ' GROUP BY '; + sql += qb.group_by_array.join(', '); + return sql; + }; + + var build_having_clause = function(qb) { + if (qb.having_array.length <= 0) return ''; + + var sql = ' HAVING '; + sql += qb.having_array.join(' '); + return sql; + }; + + var build_order_by_clause = function(qb) { + if (qb.order_by_array.length <= 0) return ''; + + var sql = ' ORDER BY '; + sql += qb.order_by_array.join(', '); + + return sql; + }; + + var build_limit_clause = function(sql, limit, offset) { + if (!limit) return sql; + + sql += ' '; + + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + }; + + var compile_select = function(qb) { + var distinct_clause = qb.distinct_clause[0] || ''; + var sql = 'SELECT ' + distinct_clause; + if (qb.select_array.length === 0) { + sql += '*'; + } else { + sql += qb.select_array.join(', '); + } + + sql += build_from_clause(qb) + + build_join_string(qb) + + build_where_clause(qb) + + build_group_by_clause(qb) + + build_having_clause(qb) + + build_order_by_clause(qb); + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + sql = build_limit_clause(sql,limit_to,offset_val); + return sql; + }; + + var compile_delete = function(qb) { + if (qb.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } + + qb.from_array = qb.from_array.slice(0,1); + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); + return build_limit_clause(sql,limit_to,offset_val); + }; + + var compile_update = function(qb) { + var valstr = []; + for (var i in qb.set_array) { + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + valstr.push(key + ' = ' + val); + } + + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + var limit_to = qb.limit_to[0] || false; + var offset_val = qb.offset_val[0] || false; + + var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); + sql += build_where_clause(qb); + sql += build_order_by_clause(qb); + return build_limit_clause(sql, limit_to, offset_val); + }; + + var compile_insert = function(qb, ignore, suffix) { + var keys = []; + var values = []; + suffix = suffix || ''; + + for (var i in qb.set_array) { + var key = Object.keys(qb.set_array[i])[0]; + var val = qb.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + + if (qb.from_array.length === 1) { + var table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; + } + + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + }; + + // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// + return { + where_array: [], + where_in_array: [], + from_array: [], + join_array: [], + select_array: [], + set_array: [], // has to be array to work as reference + order_by_array: [], + group_by_array: [], + having_array: [], + limit_to: [], // has to be array to work as reference + offset_val: [], // has to be array to work as reference + join_clause: [], + last_query_string: [], // has to be array to work as reference + distinct_clause: [], // has to be array to work as reference + aliased_tables: [], + + reset_query: function(new_last_query,debug) { + clear_array(this.where_array,debug); + clear_array(this.where_in_array); + clear_array(this.from_array); + clear_array(this.join_array); + clear_array(this.select_array); + clear_array(this.set_array); + clear_array(this.order_by_array); + clear_array(this.group_by_array); + clear_array(this.having_array); + clear_array(this.limit_to); + clear_array(this.offset_val); + clear_array(this.join_clause); + clear_array(this.distinct_clause); + clear_array(this.aliased_tables); + + clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + }, + + where: function(key, value, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + value = (typeof value === 'undefined' ? null : value); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + }, + + or_where: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + value = value || null; + + if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + }, + + _where: function(key, value, type, escape) { + value = (typeof value === 'undefined' ? null : value); + type = type || 'AND '; + escape = (typeof escape === 'boolean' ? escape : true); + + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { + var filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + var that = this; + var parse_statement = function(statement,joiner) { + var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || joiner.match(/AND/i)) { + that.where(key, value, true); + } else { + that.or_where(key, value, true); + } + } + }; + parse_statement(filters.shift(),null); + while (filters.length > 0) { + var joiner = filters.shift(); + var statement = filters.shift(); + parse_statement(statement, joiner); + } + return this; + } + } + + var key_array = {}; + key_array[key] = value; + key = key_array; + } + + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } + + for (var k in key) { + var v = key[k]; + + if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { + return this._where_in(k,v,false,type,escape); + } + + var prefix = (this.where_array.length == 0 ? '' : type); + + if (v === null && !has_operator(k)) { + k += ' IS NULL'; + } + + if (v !== null) { + if (escape === true) { + k = protect_identifiers(this,k,escape); + v = ' ' + qb_escape(this,v); + } + + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + qb_escape(this,v); + } + + if (!has_operator(k)) { + k += ' ='; + } + } + else { + k = protect_identifiers(this,k,escape); + } + + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } + + return this; + }, + + where_in: function(key, values, escape) { + return this._where_in(key,values,false,'AND ', escape); + }, + + or_where_in: function(key, values, escape) { + return this._where_in(key,values,false,'OR ', escape); + }, + + where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'AND ', escape); + }, + + or_where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'OR ', escape); + }, + + _where_in: function(key, values, not, type, escape) { + key = key || ''; + values = values || []; + type = type || 'AND '; + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } + + // Values must be an array... + if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); + } + } + + for (var i in values) { + this.where_in_array.push(qb_escape(this,values[i])); + } + + var prefix = (this.where_array.length == 0 ? '' : type); + var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); + + // reset the array for multiple calls + clear_array(this.where_in_array); + return this; + }, + + like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + }, + + not_like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + }, + + or_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + }, + + or_not_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + }, + + _like: function(field, match, type, side, not) { + match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); + type = type || 'AND '; + side = (typeof side === 'undefined' ? 'both' : side); + not = not || ''; + + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if ((typeof field).match(/^(string|object)$/) === null) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } + + if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); + } + + var field_array = {}; + field_array[field] = match; + field = field_array; + } + + for(k in field) { + var like_statement; + var v = field[k]; + var k = protect_identifiers(this,k.trim()); + + // Make sure value is only string, number, or boolean + if ((typeof v).match(/^(string|number|boolean)$/) === null) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); + } + + this._where(like_statement,null,type,false); + } + + return this; + }, + + from: function(from) { + if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { + from = [from]; + } + for (var i in from) { + var val = from[i]; + + if (val.trim() === '') continue; + + if (val.indexOf(',') !== -1) { + var objects = val.split(','); + for (var j in objects) { + var v = objects[j].trim(); + + track_aliases(this,v); + + this.from_array.push(protect_identifiers(this,v,true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + track_aliases(this,val); + + this.from_array.push(protect_identifiers(this,val,true)); + } + } + + return this; + }, + + join: function(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } + + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } + + direction += ' '; + } + + track_aliases(this,table); + + // Split multiple conditions + var regex = /\sAND\s|\sOR\s/ig; + var m = relation.match(regex); + var matches = []; + var k, temp, temp_match, match; + if (escape === true && m) { + while(k = regex.exec(relation)) { + matches.push(k); + } + + var new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); + new_relation += matches[j][0]; + } + + relation = ' ON ' + new_relation; + } + + // Split apart the condition and protect the identifiers + else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); + } + else if (!has_operator(relation)) { + relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; + } + else { + relation = ' '; + } + + // Do we want to escape the table name? + if (escape === true) { + table = protect_identifiers(this,table,true); + } + + var join = direction + 'JOIN ' + table + relation; + + this.join_array.push(join); + return this; + }, + + select: function(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); + } + } + else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); + } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + + if (typeof escape !== 'boolean') escape = true; + + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + var m, open_paren_index, inner_parenthesis; + var reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; + } + } + } + } + else { + select = [select]; + } + } + } + + for (var i in select) { + var val = select[i].trim(); + + if(val !== '') { + this.select_array.push(protect_identifiers(this,val,escape)); + } + } + return this; + }, + + select_min: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MIN'); + }, + + select_max: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MAX'); + }, + + select_avg: function(select,alias) { + return this._min_max_avg_sum(select,alias,'AVG'); + }, + + select_sum: function(select,alias) { + return this._min_max_avg_sum(select,alias,'SUM'); + }, + + _min_max_avg_sum: function(select,alias,type) { + select = select || ''; + alias = alias || ''; + type = type || 'MAX'; + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } + + type = type.toUpperCase(); + + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } + + if (alias == '') { + alias = create_aliases_from_table(select.trim()); + } + + var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + + this.select_array.push(sql); + + return this; + }, + + distinct: function(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + + if (do_distinct) { + clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + clear_array(this.distinct_clause); + } + return this; + }, + + group_by: function(by) { + if (typeof by === 'string') { + by = by.trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + by = by.split(','); + } + + if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } + + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + + for (var key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); + } + + var val = by[key].trim(); + + if (val !== '') { + this.group_by_array.push(protect_identifiers(this,val)); + } + } + return this; + }, + + having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + }, + + or_having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + }, + + _having: function(key, value, type, escape) { + type = type || 'AND '; + + var m; + var key_array = {}; + var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); + + if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = extract_having_parts(key,key_array); + } + } + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (var i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); + } + else { + key_array = extract_having_parts(key[i],key_array); + } + } + key = key_array; + } + } + } + else { + throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); + } + + + for (var k in key) { + var v = key[k]; + var prefix = (this.having_array.length == 0 ? '' : type); + + if (escape === true) { + k = protect_identifiers(this,k); + } + + if (v === null) { + k += ' IS'; + } + else if (!has_operator(k)) { + k += ' ='; + } + + if (v != '') { + v = ' ' + qb_escape(this,v); + } + + this.having_array.push(prefix + k + v); + } + + return this; + }, + + order_by: function(orderby, direction) { + var m; + var rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(rand_word); + return this; + } + + // normalize orderby to be an array of items + if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { + this.order_by_array.push(rand_word); + return this; + } + else { + throw new Error("No field provided to order by!"); + } + } + + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } + + for (var i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); + + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.order_by_array.push(rand_word); + return this; + } + orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (direction.match(/^(ASC|DESC)$/i) || direction === '') { + orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + } else { + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); + } + } + + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } + + return this; + }, + + limit: function(limit, offset) { + clear_array(this.limit_to); + this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); + + if (offset !== undefined) { + return this.offset(offset); + } + + return this; + }, + + offset: function(offset) { + clear_array(this.offset_val); + this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); + return this; + }, + + set: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) { + throw new Error("set(): Invalid field name provided!"); + } + + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); + + var key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); + } + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); + } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } + + + // Add each key:value pair to the set_array + for (var i in key) { + var v = key[i]; if (typeof v === 'undefined') continue; - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - - // Escape the key to be DRY - var escaped_key = protect_identifiers(this,i,escape); - - // Build a temporary object with escaped key and val - var temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = qb_escape(this,v); - } - - // Determine if this key has already been set - var found_index = null; - for (var j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } - } - - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } - } - - //console.dir(this.set_array); - - return this; - }, - - insert: function(table, set, ignore, suffix) { - table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } - - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - return this.insert_batch(table, set, ignore, suffix); - } - - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table === '') { - if (this.from_array.length == 0) { - throw new Error('insert(): No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - return compile_insert(this, ignore, suffix); - }, - - insert_ignore: function(table, set, suffix) { - return this.insert(table, set, true, suffix); - }, - - insert_batch: function(table,set,ignore,suffix) { - var self = this; - var orig_table = table = table || ''; - set = set || null; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); - } - - for (var key in set) { - var row = set[key]; - var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (var i in row) { - var v = row[i]; - - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - } - } - } - - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } - - var map = []; - var columns = []; - - // Obtain all the column names - for (var key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(protect_identifiers(this,key)); - } - } - } - - for (var i = 0; i < set.length; i++) { - (function(i,qb) { - var row = []; - for (var key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(qb_escape(qb,set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); - } - map.push('(' + row.join(', ') + ')'); - })(i,self); - } - - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; - }, - - get: function(table) { - if (typeof table !== 'undefined') { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } - } - return compile_select(this); - }, - - get_where: function(table, where) { - table = table || null; - where = where || null; - - if (table === null || (typeof table === 'string' && table.trim().length === 0)) { - throw new Error('You must specify a table or tables in the first parameter of get_where()'); - } - else { - this.from(table); - } - - if (where === null || Object.keys(where).length === 0) { - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - } - else { - this.where(where); - } - - return compile_select(this); - }, - - count: function(table) { - if (typeof table === 'string') { - this.from(table); - } - - var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') - + build_from_clause(this) - + build_join_string(this) - + build_where_clause(this); - - return sql; - }, - - update: function(table, set, where) { - table = table || ''; - set = set || null; - where = where || null; - - // Send to batch_update if the data param is an array - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - var index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); - } - } - - // If set is a number, boolean, a non-empty string, or regex, fail - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); - } - - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); - } - - return compile_update(this); - }, - - update_batch: function(table, set, index, where) { - table = table || ''; - set = set || null; - index = index || null; - where = where || null; - - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } - - // Check to make sure we have a dataset - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } - - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } - - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (var i in set) { - var clean = {}; - var row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - var keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (var j in row) { - clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); - } - this.set_array.push(clean); - } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } - - // Verify there is a table in the from_array - if (this.from_array.length === 1) { - var table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); - } - - - // Limit to 100 rows per batch - var batches = []; - for (var i = 0, total = this.set_array.length; i < total; i += 100) { - var when_then = {}; - var ids = []; - var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); - var chunk = this.set_array.slice(i,100); - - // Escape the index - index = protect_identifiers(this, index); - - for (var j in chunk) { - ids.push(chunk[j][index]); - - var keys = Object.keys(chunk[j]); - for (var k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); - } - } - } - - // Build the actual SQL statement - var sql = 'UPDATE (' + table + ') SET '; - var cases = ''; - - for (var l in when_then) { - cases += l + ' = CASE '; - - for (var m in when_then[l]) { - cases += when_then[l][m]; - } - - cases += 'ELSE ' + l + ' END, '; - } - - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); - } - - return batches; - }, - - delete: function(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } - - return compile_delete(this); - }, - - get_compiled_select: function(table) { - if (typeof table !== 'undefined') { - track_aliases(this,table); - this.from(table); - } - else { - if (from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } - } - - return compile_select(this); - }, - - get_compiled_delete: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - - return compile_delete(this); - }, - - get_compiled_update: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_update(this); - }, - - get_compiled_insert: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_insert(this); - }, - - compile_select: function(table) { - return this.get_compiled_select(table); - }, - - compile_delete: function(table) { - return this.get_compiled_delete(table); - }, - - compile_update: function(table) { - return this.get_compiled_update(table); - }, - - compile_insert: function(table) { - return this.get_compiled_insert(table); - }, - - last_query: function() { - return this.last_query_string[0] || ''; - }, - - escape: function(val) { - return qb_escape(this, val); - }, - - empty_table: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } - - return "DELETE FROM " + this.from_array[0]; - }, - - truncate: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } - - return "TRUNCATE " + this.from_array[0]; - }, - } + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + + // Escape the key to be DRY + var escaped_key = protect_identifiers(this,i,escape); + + // Build a temporary object with escaped key and val + var temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = qb_escape(this,v); + } + + // Determine if this key has already been set + var found_index = null; + for (var j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; + } + } + + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); + } + } + + //console.dir(this.set_array); + + return this; + }, + + insert: function(table, set, ignore, suffix) { + table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } + + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + return this.insert_batch(table, set, ignore, suffix); + } + + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } + } + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table === '') { + if (this.from_array.length == 0) { + throw new Error('insert(): No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + return compile_insert(this, ignore, suffix); + }, + + insert_ignore: function(table, set, suffix) { + return this.insert(table, set, true, suffix); + }, + + insert_batch: function(table,set,ignore,suffix) { + var self = this; + var orig_table = table = table || ''; + set = set || null; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (var key in set) { + var row = set[key]; + var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (var i in row) { + var v = row[i]; + + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } + } + } + + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } + + var map = []; + var columns = []; + + // Obtain all the column names + for (var key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(protect_identifiers(this,key)); + } + } + } + + for (var i = 0; i < set.length; i++) { + (function(i,qb) { + var row = []; + for (var key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(qb_escape(qb,set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(', ') + ')'); + })(i,self); + } + + var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; + }, + + get: function(table) { + if (typeof table !== 'undefined') { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); + } + } + return compile_select(this); + }, + + get_where: function(table, where) { + table = table || null; + where = where || null; + + if (table === null || (typeof table === 'string' && table.trim().length === 0)) { + throw new Error('You must specify a table or tables in the first parameter of get_where()'); + } + else { + this.from(table); + } + + if (where === null || Object.keys(where).length === 0) { + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + } + else { + this.where(where); + } + + return compile_select(this); + }, + + count: function(table) { + if (typeof table === 'string') { + this.from(table); + } + + var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + + build_from_clause(this) + + build_join_string(this) + + build_where_clause(this); + + return sql; + }, + + update: function(table, set, where) { + table = table || ''; + set = set || null; + where = where || null; + + // Send to batch_update if the data param is an array + if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + var index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; + } + } + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + } + } + + // If set is a number, boolean, a non-empty string, or regex, fail + if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } + + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } + + return compile_update(this); + }, + + update_batch: function(table, set, index, where) { + table = table || ''; + set = set || null; + index = index || null; + where = where || null; + + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } + + // Check to make sure we have a dataset + if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } + + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } + + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (var i in set) { + var clean = {}; + var row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + var keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (var j in row) { + clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); + } + this.set_array.push(clean); + } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } + + // Verify there is a table in the from_array + if (this.from_array.length === 1) { + var table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); + } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } + + + // Limit to 100 rows per batch + var batches = []; + for (var i = 0, total = this.set_array.length; i < total; i += 100) { + var when_then = {}; + var ids = []; + var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); + var chunk = this.set_array.slice(i,100); + + // Escape the index + index = protect_identifiers(this, index); + + for (var j in chunk) { + ids.push(chunk[j][index]); + + var keys = Object.keys(chunk[j]); + for (var k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; + } + when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + } + } + } + + // Build the actual SQL statement + var sql = 'UPDATE (' + table + ') SET '; + var cases = ''; + + for (var l in when_then) { + cases += l + ' = CASE '; + + for (var m in when_then[l]) { + cases += when_then[l][m]; + } + + cases += 'ELSE ' + l + ' END, '; + } + + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; + + // Add query to batch + batches.push(sql); + } + + return batches; + }, + + delete: function(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + } + else { + this.where(where); + } + } + + return compile_delete(this); + }, + + get_compiled_select: function(table) { + if (typeof table !== 'undefined') { + track_aliases(this,table); + this.from(table); + } + else { + if (from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; + } + } + + return compile_select(this); + }, + + get_compiled_delete: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + + return compile_delete(this); + }, + + get_compiled_update: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_update(this); + }, + + get_compiled_insert: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_insert(this); + }, + + compile_select: function(table) { + return this.get_compiled_select(table); + }, + + compile_delete: function(table) { + return this.get_compiled_delete(table); + }, + + compile_update: function(table) { + return this.get_compiled_update(table); + }, + + compile_insert: function(table) { + return this.get_compiled_insert(table); + }, + + last_query: function() { + return this.last_query_string[0] || ''; + }, + + escape: function(val) { + return qb_escape(this, val); + }, + + empty_table: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } + + return "DELETE FROM " + this.from_array[0]; + }, + + truncate: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } + + return "TRUNCATE " + this.from_array[0]; + }, + } }; exports.QueryBuilder = QueryBuilder; diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 04672c8..79e3826 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -1,204 +1,204 @@ // **************************************************************************** // QueryBuilder "Query Execution" methods. // ----- -// @param Object qb The QueryBuilder object -// @param Object adapter The connection adapter object +// @param Object qb The QueryBuilder object +// @param Object adapter The connection adapter object // **************************************************************************** var QueryExec = function(qb, conn) { - - var exec = function(sql, callback) { - if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - conn.query(sql, function(err, results) { - // Standardize some important properties - if (!err && results.length > 0) { - - // Insert ID - if (results.hasOwnProperty('insertId')) { - results.insert_id = results.insertId; - } - - // Affected Rows - if (results.hasOwnProperty('affectedRows')) { - results.affected_rows = results.affectedRows; - } - - // Changed Rows - if (results.hasOwnProperty('changedRows')) { - results.changed_rows = results.changedRows; - } - } - - callback(err, results); - }); - } else { - throw new Error("No connection object supplied to the Query Exec Library!"); - } - }; - - return { - query: function(sql, callback) { - exec(sql, callback); - }, - - count: function(table, callback) { - if (typeof table === 'function' && typeof callback !== 'function') { - table = null; - callback = table; - } - - var sql = qb.count(table); - qb.reset_query(sql); - exec(sql, function(err, row) { - if (!err) { + + var exec = function(sql, callback) { + if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { + conn.query(sql, function(err, results) { + // Standardize some important properties + if (!err && results.length > 0) { + + // Insert ID + if (results.hasOwnProperty('insertId')) { + results.insert_id = results.insertId; + } + + // Affected Rows + if (results.hasOwnProperty('affectedRows')) { + results.affected_rows = results.affectedRows; + } + + // Changed Rows + if (results.hasOwnProperty('changedRows')) { + results.changed_rows = results.changedRows; + } + } + + callback(err, results); + }); + } else { + throw new Error("No connection object supplied to the Query Exec Library!"); + } + }; + + return { + query: function(sql, callback) { + exec(sql, callback); + }, + + count: function(table, callback) { + if (typeof table === 'function' && typeof callback !== 'function') { + table = null; + callback = table; + } + + var sql = qb.count(table); + qb.reset_query(sql); + exec(sql, function(err, row) { + if (!err) { //console.dir(row[0].numrows); - callback(err, row[0].numrows); - } - else { - callback(err, row); - } - }); - }, - - get: function(table,callback,conn) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); - } - - var sql = qb.get(table); - qb.reset_query(sql); - exec(sql,callback); - }, - - get_where: function(table,where,callback) { - if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); - } - if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); - } - var sql = qb.get_where(table,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert: function(table,set,callback,ignore,suffix) { - var sql = qb.insert(table,set,ignore,suffix); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_ignore: function(table,set,callback) { - var sql = qb.insert_ignore(table,set); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_batch: function(table,set,callback) { - var sql = qb.insert_batch(table,set); - qb.reset_query(sql); - exec(sql,callback); - }, - - update: function(table,set,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } - - var sql = qb.update(table,set,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - // TODO: Write this complicated-ass function - update_batch: function(table,set,index,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } - - var sqls = qb.update_batch(table,set,index,where); - var results = null; - var errors = []; - - // Execute each batch of (at least) 100 - (function next_batch() { - var sql = sqls.shift(); - qb.reset_query(sql); - - exec(sql, function(err, res) { - if (!err) { - if (null === results) { - results = res; - } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; - } - } else { - errors.push(err); - } - - if (sql.length > 0) { - setTimeout(next_batch,0); - } else { - return callback(errors, results); - } - }); - })(); - }, - - delete: function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } - - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } - - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - - var sql = qb.delete(table, where); - - qb.reset_query(sql); - exec(sql,callback); - }, - - empty_table: function(table, callback) { - var sql = qb.empty_table(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, - - truncate: function(table, callback) { - var sql = qb.truncate(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, - } + callback(err, row[0].numrows); + } + else { + callback(err, row); + } + }); + }, + + get: function(table,callback,conn) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); + } + + var sql = qb.get(table); + qb.reset_query(sql); + exec(sql,callback); + }, + + get_where: function(table,where,callback) { + if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } + var sql = qb.get_where(table,where); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert: function(table,set,callback,ignore,suffix) { + var sql = qb.insert(table,set,ignore,suffix); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert_ignore: function(table,set,callback) { + var sql = qb.insert_ignore(table,set); + qb.reset_query(sql); + exec(sql,callback); + }, + + insert_batch: function(table,set,callback) { + var sql = qb.insert_batch(table,set); + qb.reset_query(sql); + exec(sql,callback); + }, + + update: function(table,set,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sql = qb.update(table,set,where); + qb.reset_query(sql); + exec(sql,callback); + }, + + // TODO: Write this complicated-ass function + update_batch: function(table,set,index,where,callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + var sqls = qb.update_batch(table,set,index,where); + var results = null; + var errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + var sql = sqls.shift(); + qb.reset_query(sql); + + exec(sql, function(err, res) { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } + } else { + errors.push(err); + } + + if (sql.length > 0) { + setTimeout(next_batch,0); + } else { + return callback(errors, results); + } + }); + })(); + }, + + delete: function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } + + var sql = qb.delete(table, where); + + qb.reset_query(sql); + exec(sql,callback); + }, + + empty_table: function(table, callback) { + var sql = qb.empty_table(table,callback); + qb.reset_query(sql); + exec(sql,callback); + }, + + truncate: function(table, callback) { + var sql = qb.truncate(table,callback); + qb.reset_query(sql); + exec(sql,callback); + }, + } } exports.QueryExec = QueryExec; diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 1b496d9..69dc796 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -8,145 +8,145 @@ var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'And // table, data, callback, ignore, suffix describe('insert()', function() { - it('should exist', function() { - should.exist(qb.insert); - }); - it('should be a function', function() { - qb.insert.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', function() { - qb.reset_query(); - qb.insert('galaxies', test_data); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should only accept nothing or a string for the table (first) parameter', function() { - qb.reset_query(); + it('should exist', function() { + should.exist(qb.insert); + }); + it('should be a function', function() { + qb.insert.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', function() { + qb.reset_query(); + qb.insert('galaxies', test_data); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a string for the table (first) parameter', function() { + qb.reset_query(); - // Doing these to prevent other errors - qb.from('galaxies'); + // Doing these to prevent other errors + qb.from('galaxies'); - expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); - expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); - expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); - expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); - expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); + expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); + expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); + expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); + expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); + expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); - expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); - }); - it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { - qb.reset_query(); + expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); + expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); + expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { + qb.reset_query(); - expect(function() { qb.insert('galaxies',test_data);}, 'non-empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[test_data,test_data]); }, 'array of non-empty standard objects provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',undefined);}, 'undefined provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',test_data); }, 'non-empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',[test_data,test_data]);}, 'array of non-empty standard objects provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies',undefined); }, 'undefined provided').to.not.throw(Error); + expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); - expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); - expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); - expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); - }); - it('should allow for an empty data parameter', function() { - qb.reset_query(); - var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); - }); - it('should utilize pre-existing tables set in from_array', function() { - qb.reset_query(); - qb.from('galaxies'); - var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); - }); - it('should utilize pre-existing values set in in set_array', function() { - qb.reset_query(); - qb.set(test_data); - var sql = qb.insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); - }); - it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { - qb.reset_query(); - qb.from('galaxies').set(test_data); - var sql = qb.insert(); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); - }); - it('should accept a non-empty object for the data parameter', function() { - qb.reset_query(); - var sql = qb.insert('galaxies', test_data); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); - }); - it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { - qb.reset_query(); - var sql = qb.insert('galaxies', test_data_set); - var sql_b = qb.insert_batch('galaxies', test_data_set); - sql.should.eql(sql_b); - }); - it('should fail if any invalid values are passed in the data object.', function() { - qb.reset_query(); - var func = function() { console.log("foo"); }; - var regex = /foobar/; - var arr = [1,2,3]; - var obj = {foo: 'bar'}; + expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); + expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); + expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); + expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]);}, 'array of mixed values provided').to.throw(Error); + expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); + expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', function() { + qb.reset_query(); + qb.from('galaxies'); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing values set in in set_array', function() { + qb.reset_query(); + qb.set(test_data); + var sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { + qb.reset_query(); + qb.from('galaxies').set(test_data); + var sql = qb.insert(); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should accept a non-empty object for the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + qb.reset_query(); + var sql = qb.insert('galaxies', test_data_set); + var sql_b = qb.insert_batch('galaxies', test_data_set); + sql.should.eql(sql_b); + }); + it('should fail if any invalid values are passed in the data object.', function() { + qb.reset_query(); + var func = function() { console.log("foo"); }; + var regex = /foobar/; + var arr = [1,2,3]; + var obj = {foo: 'bar'}; - expect(function() { qb.insert('galaxies',{id: 1}); }, 'number in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: 'foo'}); }, 'string in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: false}); }, 'boolean in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: null}); }, 'null in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: 1}); }, 'number in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: 'foo'}); }, 'string in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: false}); }, 'boolean in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: null}); }, 'null in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.not.throw(Error); + expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); + expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); - }); + }); }); describe('insert_ignore()', function() { - it('should exist', function() { - should.exist(qb.insert_ignore); - }); - it('should be a function', function() { - qb.insert_ignore.should.be.a('function'); - }); - it('should create an INSERT IGNORE statement', function() { - qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); - }); - it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { - qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data); - var sql_b = qb.insert('galaxies', test_data, true); - sql.should.eql(sql_b); - }); - it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { - qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data_set); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); - }); - it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { - qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); - }); + it('should exist', function() { + should.exist(qb.insert_ignore); + }); + it('should be a function', function() { + qb.insert_ignore.should.be.a('function'); + }); + it('should create an INSERT IGNORE statement', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data); + var sql_b = qb.insert('galaxies', test_data, true); + sql.should.eql(sql_b); + }); + it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data_set); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { + qb.reset_query(); + var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); }); From 31f97b4d0893a658c32ac0cbc1beede2d4ccd2da Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 13 Oct 2015 16:42:56 -0400 Subject: [PATCH 084/200] Removed paranthesis for the FROM clause string builder method and updated pertinent tests. --- drivers/mysql/query_builder.js | 2 +- test/mysql/tests-compilation_methods.js | 6 ++-- test/mysql/tests-count.js | 12 +++---- test/mysql/tests-delete.js | 18 +++++----- test/mysql/tests-get.js | 44 ++++++++++++------------- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 359173f..917eed1 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -311,7 +311,7 @@ var QueryBuilder = function() { } else { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); } - sql += '(' + qb.from_array.join(', ') + ')'; + sql += qb.from_array.join(', '); return sql; }; diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index c85aff7..41c27ab 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -22,7 +22,7 @@ describe('get_compiled_select()', function() { it('should return a SQL string', function() { qb.reset_query(); var sql = qb.get_compiled_select('galaxies'); - sql.should.eql('SELECT * FROM (`galaxies`)'); + sql.should.eql('SELECT * FROM `galaxies`'); }); }); @@ -64,6 +64,6 @@ describe('get_compiled_delete()', function() { it('should return a SQL string', function() { qb.reset_query(); var sql = qb.where('id',45).get_compiled_delete('galaxies'); - sql.should.eql("DELETE FROM (`galaxies`) WHERE `id` = 45"); + sql.should.eql("DELETE FROM `galaxies` WHERE `id` = 45"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js index 25c8ea8..caf0ea9 100755 --- a/test/mysql/tests-count.js +++ b/test/mysql/tests-count.js @@ -30,26 +30,26 @@ describe('count()', function() { it('should create a properly-escaped SELECT query', function() { qb.reset_query(); var sql = qb.count('galaxies'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`)"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); it('should include WHERE statements', function() { qb.reset_query(); var sql = qb.where({type:'spiral'}).count('galaxies'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`) WHERE `type` = 'spiral'"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 'spiral'"); }); it('should work when table/view/procedure is provided earlier in chain but not in count() method', function() { qb.reset_query(); var sql = qb.from('galaxies').count(); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`galaxies`)"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); it('should work with multiple tables/views/stored procedures', function() { qb.reset_query(); var sql = qb.from(['planets','galaxies']).count(); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`planets`, `galaxies`)"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `planets`, `galaxies`"); }); it('should include any joins that were added in the chain', function() { qb.reset_query(); var sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM (`star_systems` `s`) LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index ab273cd..2687d0e 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -38,7 +38,7 @@ describe('delete()', function() { it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).delete(); - sql.should.eql("DELETE FROM (`galaxies`)"); + sql.should.eql("DELETE FROM `galaxies`"); }); it('should add where conditions to where_array when conditions are supplied', function() { qb.reset_query(); @@ -55,36 +55,36 @@ describe('delete()', function() { it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', function() { qb.reset_query(); var sql = qb.delete('galaxies'); - sql.should.eql('DELETE FROM (`galaxies`)'); + sql.should.eql('DELETE FROM `galaxies`'); }); it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', function() { qb.reset_query(); var sql = qb.delete('galaxies', {class: 'M'}); - sql.should.eql("DELETE FROM (`galaxies`) WHERE `class` = 'M'"); + sql.should.eql("DELETE FROM `galaxies` WHERE `class` = 'M'"); }); it('should use ONLY the FIRST table added previously via the from() method', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.delete(); - sql.should.eql('DELETE FROM (`galaxies`)'); + sql.should.eql('DELETE FROM `galaxies`'); qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).delete(); - sql.should.eql('DELETE FROM (`galaxies`)'); + sql.should.eql('DELETE FROM `galaxies`'); }); it('should accept where conditions added previously via the where() method', function() { qb.reset_query(); var sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); - sql.should.eql("DELETE FROM (`galaxies`) WHERE `created` >= 4600000000 AND `class` = 'M'"); + sql.should.eql("DELETE FROM `galaxies` WHERE `created` >= 4600000000 AND `class` = 'M'"); }); it('should accept a limit on the number of rows deleted', function() { qb.reset_query(); var sql = qb.limit(20).delete('galaxies'); - sql.should.eql("DELETE FROM (`galaxies`) LIMIT 20"); + sql.should.eql("DELETE FROM `galaxies` LIMIT 20"); }); it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', function() { qb.reset_query(); var sql = qb.limit(20,10).delete('galaxies'); - sql.should.eql("DELETE FROM (`galaxies`) LIMIT 10, 20"); + sql.should.eql("DELETE FROM `galaxies` LIMIT 10, 20"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index ba97ebd..13ffa41 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -29,106 +29,106 @@ describe('get()', function() { it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', function() { qb.reset_query(); var sql = qb.get('galaxies'); - sql.should.eql('SELECT * FROM (`galaxies`)'); + sql.should.eql('SELECT * FROM `galaxies`'); }); it('should properly handle alias if provided in table string', function() { qb.reset_query(); var sql = qb.get('galaxies g'); - sql.should.eql('SELECT * FROM (`galaxies` `g`)'); + sql.should.eql('SELECT * FROM `galaxies` `g`'); }); it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', function() { qb.reset_query(); var sql = qb.select(['id','name']).get('galaxies'); - sql.should.eql("SELECT `id`, `name` FROM (`galaxies`)"); + sql.should.eql("SELECT `id`, `name` FROM `galaxies`"); }); it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', function() { qb.reset_query(); var sql = qb.where('class','M').get('galaxies'); - sql.should.eql("SELECT * FROM (`galaxies`) WHERE `class` = 'M'"); + sql.should.eql("SELECT * FROM `galaxies` WHERE `class` = 'M'"); }); it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', function() { qb.reset_query(); var sql = qb.get(['galaxies','star_systems','planets']); - sql.should.eql('SELECT * FROM (`galaxies`, `star_systems`, `planets`)'); + sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', function() { qb.reset_query(); var sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); - sql.should.eql("SELECT * FROM (`galaxies`, `star_systems`, `planets`) WHERE `class` = 'M'"); + sql.should.eql("SELECT * FROM `galaxies`, `star_systems`, `planets` WHERE `class` = 'M'"); }); it('should use tables added previously via the from() method', function() { qb.reset_query(); qb.from('galaxies'); var sql = qb.get(); - sql.should.eql('SELECT * FROM (`galaxies`)'); + sql.should.eql('SELECT * FROM `galaxies`'); qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).get(); - sql.should.eql('SELECT * FROM (`galaxies`, `star_systems`, `planets`)'); + sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); it('should accept where conditions added previously via the where() method', function() { qb.reset_query(); var sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); - sql.should.eql("SELECT * FROM (`galaxies`) WHERE `created` >= 4600000000 AND `classification` = 'M'"); + sql.should.eql("SELECT * FROM `galaxies` WHERE `created` >= 4600000000 AND `classification` = 'M'"); }); it('should accept a limit on the number of rows selected', function() { qb.reset_query(); var sql = qb.limit(20).get('galaxies'); - sql.should.eql("SELECT * FROM (`galaxies`) LIMIT 20"); + sql.should.eql("SELECT * FROM `galaxies` LIMIT 20"); }); it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', function() { qb.reset_query(); var sql = qb.limit(20,10).get('galaxies'); - sql.should.eql("SELECT * FROM (`galaxies`) LIMIT 10, 20"); + sql.should.eql("SELECT * FROM `galaxies` LIMIT 10, 20"); }); it('should include the DISTINCT keyword if the distinct() method is called', function() { qb.reset_query(); var sql = qb.distinct().select(['id','name']).get('galaxies'); - sql.should.eql("SELECT DISTINCT `id`, `name` FROM (`galaxies`)"); + sql.should.eql("SELECT DISTINCT `id`, `name` FROM `galaxies`"); }); it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', function() { qb.reset_query(); // MIN var sql = qb.select_min('size','min_size').get('galaxies'); - sql.should.eql("SELECT MIN(`size`) AS min_size FROM (`galaxies`)"); + sql.should.eql("SELECT MIN(`size`) AS min_size FROM `galaxies`"); qb.reset_query(); // MAX var sql = qb.select_max('size','max_size').get('galaxies'); - sql.should.eql("SELECT MAX(`size`) AS max_size FROM (`galaxies`)"); + sql.should.eql("SELECT MAX(`size`) AS max_size FROM `galaxies`"); qb.reset_query(); // AVG var sql = qb.select_avg('size','avg_size').get('galaxies'); - sql.should.eql("SELECT AVG(`size`) AS avg_size FROM (`galaxies`)"); + sql.should.eql("SELECT AVG(`size`) AS avg_size FROM `galaxies`"); qb.reset_query(); // SUM var sql = qb.select_sum('size','total_size').get('galaxies'); - sql.should.eql("SELECT SUM(`size`) AS total_size FROM (`galaxies`)"); + sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); }); it('should include any joins that were added in the chain', function() { qb.reset_query(); var sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) .join('galaxies g','g.id=s.galaxy_id','left') .get('star_systems s'); - sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM (`star_systems` `s`) LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); }); it('should include any GROUP BY statements added using the group_by() method.', function() { qb.reset_query(); var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM (`galaxies`) GROUP BY `size`"); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); }); it('should add the ORDER BY clause of the order_by() method was called in the chain', function() { qb.reset_query(); var sql = qb.order_by('size').get('galaxies'); - sql.should.eql("SELECT * FROM (`galaxies`) ORDER BY `size` ASC"); + sql.should.eql("SELECT * FROM `galaxies` ORDER BY `size` ASC"); }); it('should include any HAVING clauses added using the having() method', function() { qb.reset_query(); var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM (`galaxies`) GROUP BY `size` HAVING `num_of_size` >= 456034960"); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); }); }); @@ -176,6 +176,6 @@ describe('get_where()', function() { var sql = qb.get_where('galaxies', {type: 'spiral'}); qb.from_array.should.eql(['`galaxies`']); qb.where_array.should.eql(["`type` = 'spiral'"]); - sql.should.eql("SELECT * FROM (`galaxies`) WHERE `type` = 'spiral'"); + sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); }); -}); \ No newline at end of file +}); From ff34d480720690d6846dd37ee49f41858581eaf6 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 13 Oct 2015 16:43:45 -0400 Subject: [PATCH 085/200] Upped minor version because of bug fix related to building the FROM clause. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 168274f..d3f5e0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.9.1", + "version": "0.10.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From d81227db85a4643425e4373cf91de78101391666 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 15:50:14 -0500 Subject: [PATCH 086/200] Update README.md Fixed order of variable in examples on `insert_ignore` method. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 24e7148..982f78b 100755 --- a/README.md +++ b/README.md @@ -1532,9 +1532,9 @@ Object containing information about the result of the query. */ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, function(err, res) { if (err) throw err; - var data = {name: 'Postgres', version: '8.4'}; // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); console.log(qb.last_query()); @@ -1547,9 +1547,9 @@ qb.insert_ignore('db_engines', data, function(err, res) { This time we'll do it with an `on_dupe` string ```javascript +var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { if (err) throw err; - var data = {name: 'Postgres', version: '8.4'}; // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); console.log(qb.last_query()); From 3789a45aaffd2a35bdac38f6f5c6c9f781765b17 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 16:02:40 -0500 Subject: [PATCH 087/200] Updated README.md Fixed some documentation regarding the `insert_ignore` method. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 982f78b..1037dd6 100755 --- a/README.md +++ b/README.md @@ -1512,7 +1512,7 @@ qb.insert_batch('db_engines', data, function(err, res) { | on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | | callback | Function | Required | What to do when the driver has responded. | -This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd paramter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). +This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd paramter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). With the third parameter, you can create an 'upsert' of sorts. Without the third parameter, it's essentially just "ignoring" errors, or, rather, converting them to simple warnings. **Type of Response Sent to Callback** @@ -1536,7 +1536,7 @@ var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, function(err, res) { if (err) throw err; - // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); + // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); console.log(qb.last_query()); // 0 (because this data already exists...) @@ -1551,7 +1551,7 @@ var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { if (err) throw err; - // INSERT INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); + // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4') ON DUPLICATE KEY UPDATE last_modified = NOW(); console.log(qb.last_query()); // 1 (because we updated the last_modified field) From 9436e1168494a372775955b81c2ed5bf1fccf07a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 16:04:01 -0500 Subject: [PATCH 088/200] Update README.md Fixed a bad string concat in `update` method documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1037dd6..3d69ae5 100755 --- a/README.md +++ b/README.md @@ -1341,7 +1341,7 @@ qb.where('id', 42) .set('email', 'email@domain.net') .update(null, null, null, function(err, res) { if (err) return console.error(err); - console.log("Updated: " + res.affected_rows + " rows); + console.log("Updated: " + res.affected_rows " + rows); }); ``` From 33e1bf45fdaea9438d769fdec9c6ba783d86062c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 16:05:17 -0500 Subject: [PATCH 089/200] Update README.md Properly fixed the previous commit. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d69ae5..24e0e2e 100755 --- a/README.md +++ b/README.md @@ -1341,7 +1341,7 @@ qb.where('id', 42) .set('email', 'email@domain.net') .update(null, null, null, function(err, res) { if (err) return console.error(err); - console.log("Updated: " + res.affected_rows " + rows); + console.log("Updated: " + res.affected_rows + " rows"); }); ``` From 6015143269f3a3fcb66563af7c1ce91fb7b14a60 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 16:42:53 -0500 Subject: [PATCH 090/200] Fixed bugs in insert_ignore and get_where methods. --- drivers/mysql/query_builder.js | 41 +++++++++++++++++++++------------- drivers/mysql/query_exec.js | 8 +++++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 917eed1..c4278ad 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -181,7 +181,7 @@ var QueryBuilder = function() { protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - if(Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { var escaped_array = {}; for (k in item) { @@ -1364,21 +1364,32 @@ var QueryBuilder = function() { get_where: function(table, where) { table = table || null; where = where || null; - - if (table === null || (typeof table === 'string' && table.trim().length === 0)) { - throw new Error('You must specify a table or tables in the first parameter of get_where()'); - } - else { - this.from(table); - } - - if (where === null || Object.keys(where).length === 0) { + + // Check if table is either a string or array + if (typeof table !== 'string' && !Array.isArray(table)) + throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); + + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); + + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (var v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; + } + } + } + + this.from(table); + + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - } - else { - this.where(where); - } - + + this.where(where); + return compile_select(this); }, diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 79e3826..1bbe6ba 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -91,8 +91,12 @@ var QueryExec = function(qb, conn) { exec(sql,callback); }, - insert_ignore: function(table,set,callback) { - var sql = qb.insert_ignore(table,set); + insert_ignore: function(table,set,on_dupe,callback) { + if (typeof on_dupe === 'function') { + callback = on_dupe; + on_dupe = null; + } + var sql = qb.insert_ignore(table,set,on_dupe); qb.reset_query(sql); exec(sql,callback); }, From 0ae2e4fe00162c47f744c3a625787f05b777601c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 14 Dec 2015 16:43:57 -0500 Subject: [PATCH 091/200] Upgrade to v0.11.0 because of fixed bugs in insert_ignore and get_where methods. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d3f5e0a..b41c8c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.10.0", + "version": "0.11.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 4e5f8d321d501b238f40537941b2ab180de5b3f6 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Apr 2016 14:59:53 -0400 Subject: [PATCH 092/200] Fixed a bug where the db connection settings passed into the constructor would be overwritten because of object referencing silliness in JS. --- index.js | 5 +++-- package.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 7c087dc..08ba8bf 100755 --- a/index.js +++ b/index.js @@ -38,9 +38,10 @@ * OTHER DEALINGS IN THE SOFTWARE. * **/ +var lo_assign = require('lodash.assign'); var QueryBuilder = function(settings,driver,type) { - this.settings = settings || {}; + this.settings = (settings ? lo_assign({}, settings) : {}); this.driver = driver || 'mysql'; this.connection_type = type || 'single'; this.drivers = require('./drivers/drivers.json'); @@ -127,4 +128,4 @@ var QueryBuilder = function(settings,driver,type) { return get_adapter(this); }; -exports.QueryBuilder = QueryBuilder; \ No newline at end of file +exports.QueryBuilder = QueryBuilder; diff --git a/package.json b/package.json index b41c8c8..5c63c34 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,12 @@ "Daehyub Kim " ], "dependencies": { + "lodash.assign": "^4.0.7", + "mongodb": "1.4.*", "mssql": "1.3.*", - "pg": "4.2.*", "mysql": "2.5.*", + "pg": "4.2.*", "sqlite3": "3.0.*", - "mongodb": "1.4.*", "underscore": "*" }, "devDependencies": { From 0ad451283e4abf0173db634f0dbdcc619c918aa7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Apr 2016 15:00:47 -0400 Subject: [PATCH 093/200] Patching due to db connection settings reference being modified by library. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c63c34..da2803e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.11.0", + "version": "0.11.1", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 4b2f272ed4972060fbcf09ab5e172484141590da Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Apr 2016 15:42:33 -0400 Subject: [PATCH 094/200] Removed some uneccessary bloat and updated the MySQL version. --- package.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/package.json b/package.json index da2803e..b6c55f5 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,7 @@ ], "dependencies": { "lodash.assign": "^4.0.7", - "mongodb": "1.4.*", - "mssql": "1.3.*", - "mysql": "2.5.*", - "pg": "4.2.*", - "sqlite3": "3.0.*", - "underscore": "*" + "mysql": "2.10.*" }, "devDependencies": { "chai": "~1.4.2", From 73d32487b576f71b2f2600544216e9f438a8c888 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Apr 2016 15:43:21 -0400 Subject: [PATCH 095/200] Upping minor due to removal of bloat and upping version of MySQL dependency. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6c55f5..768a853 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.11.1", + "version": "0.12.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 0fbb727a474b2066456c5eb8948b031a1cb6c1c3 Mon Sep 17 00:00:00 2001 From: Matthijs Alles Date: Mon, 5 Sep 2016 12:51:47 +0200 Subject: [PATCH 096/200] fix reference to var sqls --- drivers/mysql/query_exec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 1bbe6ba..476142e 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -160,7 +160,7 @@ var QueryExec = function(qb, conn) { errors.push(err); } - if (sql.length > 0) { + if (sqls.length > 0) { setTimeout(next_batch,0); } else { return callback(errors, results); From b60990121feac67868817167263b350602aa829b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 12:00:43 -0400 Subject: [PATCH 097/200] Cleaned up README file and added new valid value to set method. Fixes #3 --- README.md | 1490 ++++++++++++++++---------------- drivers/mysql/query_builder.js | 14 +- test/mysql/tests-set.js | 187 ++-- 3 files changed, 824 insertions(+), 867 deletions(-) diff --git a/README.md b/README.md index 24e0e2e..6235547 100755 --- a/README.md +++ b/README.md @@ -1,134 +1,116 @@ [![Build Status](https://travis-ci.org/kylefarris/node-querybuilder.svg?branch=master)](https://travis-ci.org/kylefarris/node-querybuilder) -Universal QueryBuilder for Node.js -====================================== +# Universal QueryBuilder for Node.js +Node-QueryBuilder is an ambitious attempt to create a kind of "universal translator" which provides programmers a consistent API to connect to and query _any_ database (traditional and NoSQL) supported by the module. The module is highly extensible and, in theory, can suppport any database provided that the driver has been written for it. -Node-QueryBuilder is an ambitious attempt to create a kind of "universal translator" which provides programmers a consistent API to connect to and query *any* database (traditional and NoSQL) supported by the module. The module is highly extensible and, in theory, can suppport any database provided that the driver has been written for it. - -The API of this module very closely mimics Codeigniter's Active Record (now called "Query Builder") library and much of the code has been directly translated from the PHP libraries in Codeigniter to JavaScript. A lot of credit needs to go to the folks over at EllisLab (https://ellislab.com/codeigniter) and all the contributors to the Codeigniter project (of which I am one): https://github.com/EllisLab/CodeIgniter/ +The API of this module very closely mimics Codeigniter's Active Record (now called "Query Builder") library and much of the code has been directly translated from the PHP libraries in Codeigniter to JavaScript. A lot of credit needs to go to the folks over at EllisLab ([https://ellislab.com/codeigniter](https://ellislab.com/codeigniter)) and all the contributors to the Codeigniter project (of which I am one): [https://github.com/EllisLab/CodeIgniter/](https://github.com/EllisLab/CodeIgniter/) The primary benefits of this module (currently) are: - -* Ability to write queries agnostically to the database you intend to query -* Supports all basic database commands (insert, update, delete, select, etc...) -* Extend capabilities from the most popular native database drivers in NPM. -* Supports method chaining -* Automatically escapes field values and identifiers by default -* Is fully unit tested -* **Very thoroughly documented** -* Allows for greater flexibility and more control over a full ORM -* Ligher-weight than an ORM -* Allows you to drop down to the native methods of your driver if you choose to -* Allows for different drivers for different versions (SQLite 2 vs SQLite 3) -* The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. -* Can used as a learning tool/Rosetta stone - -Table of Contents -================= - -* [Database Drivers](#database-drivers) -* [How to Install](#how-to-install) -* [License Info](#license-info) -* [Quick Example](#quick-example) -* [Connecting to Your Database](#connecting-to-your-database) - * [Quick Reference](#quick-reference) - * [Standard Connection Settings](#standard-connection-settings) - * [Choosing the Database Type](#choosing-the-database-type) - * [Choosing the Connection Type](#choosing-the-connection-type) -* [API Methods](#api-methods) - * [Chainable Methods](#chainable-methods) - * [Execution Methods](#execution-methods) - * [Other Library-Specific Methods](#other-library-specific-methods) -* [Contribute](#contribute) - -Database Drivers -================ - -Currently Written: ------------------- -* MySQL / MariaDB - -Coming Soon: ------------- - -* Postgres -* Microsoft SQL Server -* Oracle -* SQLite -* MongoDB - -How to install -============== - - npm install node-querybuilder - -License Info -============ - +- Ability to write queries agnostically to the database you intend to query +- Supports all basic database commands (insert, update, delete, select, etc...) +- Extend capabilities from the most popular native database drivers in NPM. +- Supports method chaining +- Automatically escapes field values and identifiers by default +- Is fully unit tested +- **Very thoroughly documented** +- Allows for greater flexibility and more control over a full ORM +- Ligher-weight than an ORM +- Allows you to drop down to the native methods of your driver if you choose to +- Allows for different drivers for different versions (SQLite 2 vs SQLite 3) +- The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. +- Can used as a learning tool/Rosetta stone + +# Table of Contents +- [Database Drivers](#database-drivers) +- [How to Install](#how-to-install) +- [License Info](#license-info) +- [Quick Example](#quick-example) +- [Connecting to Your Database](#connecting-to-your-database) + - [Quick Reference](#quick-reference) + - [Standard Connection Settings](#standard-connection-settings) + - [Choosing the Database Type](#choosing-the-database-type) + - [Choosing the Connection Type](#choosing-the-connection-type) + +- [API Methods](#api-methods) + - [Chainable Methods](#chainable-methods) + - [Execution Methods](#execution-methods) + - [Other Library-Specific Methods](#other-library-specific-methods) + +- [Contribute](#contribute) + +# Database Drivers +## Currently Written: +- MySQL / MariaDB + +## Coming Soon: +- Postgres +- Microsoft SQL Server +- Oracle +- SQLite +- MongoDB + +# How to install + +``` +npm install node-querybuilder +``` + +# License Info Licensed under the GPL license and MIT: +- [http://www.opensource.org/licenses/GPL-license.php](http://www.opensource.org/licenses/GPL-license.php) +- [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) -* http://www.opensource.org/licenses/GPL-license.php -* http://www.opensource.org/licenses/mit-license.php - -Quick Example -============= - +# Quick Example This quick example shows how to connect to and asynchronously query a MySQL database using a single connection. ```javascript var settings = { - host: 'localhost', - database: 'mydatabase', - user: 'myuser', - password: 'MyP@ssw0rd' + host: 'localhost', + database: 'mydatabase', + user: 'myuser', + password: 'MyP@ssw0rd' }; var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') - .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', function(err,response) { - if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets', function(err,response) { + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); - // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 - console.log("Query Ran: " + qb.last_query()); + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); - // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] - console.dir(response); - } + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.dir(response); + } ); ``` -Connecting to Your Database -=========================== - -Quick Reference ---------------- +# Connecting to Your Database +## Quick Reference -| Driver | Default | Ready | single | pool | cluster | Additional Connection Options | -| :---------------------------------------- | :------ | :----- | :-------- | :---- | :---- | :-------------------------------------------------------- | -| [mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) | -| [mssql](//www.npmjs.com/package/mssql) | | No | Yes | ??? | ??? | | -| [sqlite3](//www.npmjs.com/package/sqlite3)| | No | Yes | ??? | ??? | | -| [oracle](//www.npmjs.com/package/oracle) | | No | Yes | ??? | ??? | | -| [postgres](//www.npmjs.com/package/pg) | | Pending | Yes | Yes | ??? | | -| [mongodb](//www.npmjs.com/package/mongodb)| | No | Yes | ??? | ??? | | - - -Standard Connection Settings ----------------------------- +Driver | Default | Ready | single | pool | cluster | Additional Connection Options +:----------------------------------------- | :------- | :------ | :----- | :--- | :------ | :---------------------------------------------------------------------------------------- +[mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) +[mssql](//www.npmjs.com/package/mssql) | | No | Yes | ??? | ??? | +[sqlite3](//www.npmjs.com/package/sqlite3) | | No | Yes | ??? | ??? | +[oracle](//www.npmjs.com/package/oracle) | | No | Yes | ??? | ??? | +[postgres](//www.npmjs.com/package/pg) | | Pending | Yes | Yes | ??? | +[mongodb](//www.npmjs.com/package/mongodb) | | No | Yes | ??? | ??? | +## Standard Connection Settings The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. -| Option | Default | Optional | Description | -| :------------ | :-------- | :-------- | :-------------------------------------------- | -| **host** | localhost | No | The server you're connecting to | -| **user** | NULL | No | The database user | -| **password** | NULL | Yes | The database `user`'s password | -| **database** | NULL | Yes | The database to connect to | -| **port** | NULL | Yes | The database port to use when connecting | -| **pool_size** | 10 | Yes | Max connections for `pool` connection type | -| **debug** | false | Yes | If true, debug info will be place in app log | -| **version** | default | Yes | Version of database driver to use | +Option | Default | Optional | Description +:------------ | :-------- | :------- | :------------------------------------------- +**host** | localhost | No | The server you're connecting to +**user** | NULL | No | The database user +**password** | NULL | Yes | The database `user`'s password +**database** | NULL | Yes | The database to connect to +**port** | NULL | Yes | The database port to use when connecting +**pool_size** | 10 | Yes | Max connections for `pool` connection type +**debug** | false | Yes | If true, debug info will be place in app log +**version** | default | Yes | Version of database driver to use The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. @@ -138,11 +120,11 @@ We'll call this `db.json`. ```javascript { - "host": "db.myserver.com", - "user": "myusername", - "password": "P@s$w0rD", - "database": "myDB", - "pool_size": 50 + "host": "db.myserver.com", + "user": "myusername", + "password": "P@s$w0rD", + "database": "myDB", + "pool_size": 50 } ``` @@ -160,40 +142,36 @@ Of course you can also just have a normal javascript object directly within your ```javascript var qb = require('node-querybuilder').QueryBuilder({ - host: 'db.myserver.com', - user: 'myusername', - password: 'P@s$w0rD', - database: 'MyDB', - pool_size: 50 + host: 'db.myserver.com', + user: 'myusername', + password: 'P@s$w0rD', + database: 'MyDB', + pool_size: 50 }); ``` -Choosing the Database Type --------------------------- - +## Choosing the Database Type This part is super simple. Just pass which one you'd like to use as the second paramter to the constructor (`mysql` is the default): -***Example:*** +**_Example:_** ```javascript var qb = require('node-querybuilder').QueryBuilder(settings, 'postgres'); ``` -Choosing the Connection Type ----------------------------- - +## Choosing the Connection Type This library currently supports 3 connection methods: +- **_single_** (default) + - This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. + - **All drivers must have this connection type**. + +- **_pool_** + - This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. -* ***single*** (default) - * This will use the driver's basic single connection capabilities. All connections to your app will use this single database connection. This is usually less than ideal for most web applications but might be quite suitable for command line scripts and the like. - * **All drivers must have this connection type**. -* ***pool*** - * This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. -* ***cluster*** - * When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. This is ideal for high-traffic web sites and applications that utilize a farm of database servers as opposed to just one. +- **_cluster_** + - When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. This is ideal for high-traffic web sites and applications that utilize a farm of database servers as opposed to just one. -**Note:** -You will specify the type of connection as the third parameter to the contructor +**Note:** You will specify the type of connection as the third parameter to the contructor **Example:** @@ -201,68 +179,59 @@ You will specify the type of connection as the third parameter to the contructor var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); ``` +# API Methods +**_NOTE:_** The compatibility portions of these tables are subject to change as features and drivers are written! - - -API Methods -=========== - -***NOTE:*** The compatibility portions of these tables are subject to change as features and drivers are written! - -Chainable Methods ------------------ - +## Chainable Methods Chainable methods can be called as many times as you'd like in any order you like. The final query will not be built and executed until one of the [execution methods](#execution-methods), like `get()`, are callled. As the name implies, the methods can be chained together indefinitely but this is not required. You definitely call them individually with the same effect at execution time. -| API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :------------------------------------ | :------------ | :-------: | :---: | :-------: | :-------: | :-------: | :---: | -| [select()](#select) | SELECT | ✓ | | | | | | -| [distinct()](#distinct) | DISTINCT | ✓ | | | | | | -| [select_min()](#min) | MIN | ✓ | | | | | | -| [select_max()](#max) | MAX | ✓ | | | | | | -| [select_avg()](#avg) | AVG | ✓ | | | | | | -| [select_sum()](#sum) | SUM | ✓ | | | | | | -| [from()](#from) | FROM | ✓ | | | | | | -| [join()](#join) | JOIN | ✓ | | | | | | -| [where()](#where) | WHERE | ✓ | | | | | | -| [where_in()](#where_in) | IN | ✓ | | | | | | -| [where_not_in()](#where_not_in) | WHERE | ✓ | | | | | | -| [or_where()](#or_where) | WHERE | ✓ | | | | | | -| [or_where_in()](#or_where_in) | WHERE | ✓ | | | | | | -| [or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | | -| [like()](#like) | LIKE | ✓ | | | | | | -| [or_like()](#or_like) | LIKE | ✓ | | | | | | -| [or_not_like()](#or_not_like) | LIKE | ✓ | | | | | | -| [not_like()](#not_like) | LIKE | ✓ | | | | | | -| [group_by()](#group-by) | GROUP BY | ✓ | | | | | | -| [having()](#having) | HAVING | ✓ | | | | | | -| [or_having()](#or_having) | HAVING | ✓ | | | | | | -| [order_by()](#order-by) | ORDER BY | ✓ | | | | | | -| [limit()](#limit) | LIMIT | ✓ | | | | | | -| [offset()](#offset) | OFFSET | ✓ | | | | | | -| [set()](#set) | SET | ✓ | | | | | | - -------------- +API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:------------------------------------ | :---------- | :------: | :---: | :----: | :----: | :------: | :---: +[select()](#select) | SELECT | ✓ | | | | | +[distinct()](#distinct) | DISTINCT | ✓ | | | | | +[select_min()](#min) | MIN | ✓ | | | | | +[select_max()](#max) | MAX | ✓ | | | | | +[select_avg()](#avg) | AVG | ✓ | | | | | +[select_sum()](#sum) | SUM | ✓ | | | | | +[from()](#from) | FROM | ✓ | | | | | +[join()](#join) | JOIN | ✓ | | | | | +[where()](#where) | WHERE | ✓ | | | | | +[where_in()](#where_in) | IN | ✓ | | | | | +[where_not_in()](#where_not_in) | WHERE | ✓ | | | | | +[or_where()](#or_where) | WHERE | ✓ | | | | | +[or_where_in()](#or_where_in) | WHERE | ✓ | | | | | +[or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | +[like()](#like) | LIKE | ✓ | | | | | +[or_like()](#or_like) | LIKE | ✓ | | | | | +[or_not_like()](#or_not_like) | LIKE | ✓ | | | | | +[not_like()](#not_like) | LIKE | ✓ | | | | | +[group_by()](#group-by) | GROUP BY | ✓ | | | | | +[having()](#having) | HAVING | ✓ | | | | | +[or_having()](#or_having) | HAVING | ✓ | | | | | +[order_by()](#order-by) | ORDER BY | ✓ | | | | | +[limit()](#limit) | LIMIT | ✓ | | | | | +[offset()](#offset) | OFFSET | ✓ | | | | | +[set()](#set) | SET | ✓ | | | | | + +-------------------------------------------------------------------------------- ### SELECT #### .select(fields[,escape]) - This method is used to specify the fields to pull into the resultset when running SELECT-like queries. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | -| fields | String/Array | Required | The fields in which to grab from the database | -| escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape | - +Parameter | Type | Default | Description +:-------- | :----------- | :------- | :-------------------------------------------- +fields | String/Array | Required | The fields in which to grab from the database +escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): -***NOTE:*** If the select method is never called before an execution method is ran, 'SELECT *' will be assumed. +**_NOTE:_** If the select method is never called before an execution method is ran, 'SELECT *' will be assumed. +- String with fields seperated by a comma: + - `.select('foo, bar, baz')` -* String with fields seperated by a comma: - * `.select('foo, bar, baz')` -* Array of field names - * `.select(['foo', 'bar', 'baz'])` +- Array of field names + - `.select(['foo', 'bar', 'baz'])` **Examples** @@ -302,16 +271,15 @@ You can optionally choose not to have the driver auto-escape the fieldnames (dan qb.select('CONCAT(first_name,' ',last_name) AS `full_name`',false); ``` -***NOTE:*** If you use this technique to add driver-specific functions, it may (and probably will) cause unexpected outcomes with other database drivers! +**_NOTE:_** If you use this technique to add driver-specific functions, it may (and probably will) cause unexpected outcomes with other database drivers! -------------- +-------------------------------------------------------------------------------- ### DISTINCT #### .distinct() - This SQL command is used to prevent duplicate rows from being returned in the resultset at the database level. It should only be used when querying data (execution methods: `.get()` & `.get_where()`) (not inserting, updating or removing). If it's provided to another execution method, it will simply be ignored. -***This method takes no parameters*** +**_This method takes no parameters_** **Example** @@ -320,17 +288,16 @@ This SQL command is used to prevent duplicate rows from being returned in the re qb.distinct().select('id,name,description').get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### MIN #### .select_min(field[,alias]) - This SQL command is used to find the minimum value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the minimum value of | -| alias | String | NULL | Optional alias to rename field | +Parameter | Type | Default | Description +:-------- | :----- | :------- | :------------------------------------ +field | String | Required | The field to get the minimum value of +alias | String | NULL | Optional alias to rename field **Examples** @@ -346,17 +313,16 @@ You can optionally include a second parameter to rename the resulting field qb.select_min('age', 'min_age').get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### MAX #### .select_max(field[,alias]) - This SQL command is used to find the maximum value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the maximum value of | -| alias | String | NULL | Optional alias to rename field | +Parameter | Type | Default | Description +:-------- | :----- | :------- | :------------------------------------ +field | String | Required | The field to get the maximum value of +alias | String | NULL | Optional alias to rename field **Examples** @@ -372,17 +338,16 @@ You can optionally include a second parameter to rename the resulting field qb.select_max('age', 'max_age').get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### AVG #### .select_avg(field[,alias]) - This SQL command is used to find the average value for a specific field within a resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------| -| field | String | Required | The field to get the average value of | -| alias | String | NULL | Optional alias to rename field | +Parameter | Type | Default | Description +:-------- | :----- | :------- | :------------------------------------ +field | String | Required | The field to get the average value of +alias | String | NULL | Optional alias to rename field **Examples** @@ -398,17 +363,16 @@ You can optionally include a second parameter to rename the resulting field qb.select_avg('age', 'avg_age').get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### SUM #### .select_sum(field[,alias]) - This SQL command is used to find the minimum value for a specific field within a result set. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------ | -| field | String | Required | The field to get the minimum value of | -| alias | String | NULL | Optional alias to rename field | +Parameter | Type | Default | Description +:-------- | :----- | :------- | :------------------------------------ +field | String | Required | The field to get the minimum value of +alias | String | NULL | Optional alias to rename field **Examples** @@ -424,83 +388,80 @@ You can optionally include a second parameter to rename the resulting field qb.select_sum('age', 'sum_age').get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### FROM #### .from(tables) - This SQL command is used to determine which sources, available to the active connection, to obtain data from. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :-------- | :-------------------------------------------- | -| tables | String/Array | Required | Table(s), view(s), etc... to grab data from | +Parameter | Type | Default | Description +:-------- | :----------- | :------- | :------------------------------------------ +tables | String/Array | Required | Table(s), view(s), etc... to grab data from You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). Aliases can be provided and they will be escaped properly. -***NOTE:*** You can also pass table/view names into the `.get()` and `.get_where()` methods and forego this method entirely. +**_NOTE:_** You can also pass table/view names into the `.get()` and `.get_where()` methods and forego this method entirely. **Examples** -***Basic*** +**_Basic_** ```javascript // SELECT `id`, `name`, `description` FROM `users` qb.select('id,name,description').from('users').get(callback); ``` -***Comma-Seperated*** +**_Comma-Seperated_** ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) qb.select('u.id,u.name,u,description,g.name as group_name') - .from('users u, groups g') - .get(callback); + .from('users u, groups g') + .get(callback); ``` -***Array of Tables*** +**_Array of Tables_** ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) qb.select('u.id,u.name,u,description,g.name as group_name') - .from(['users u', 'groups g']) - .get(callback); + .from(['users u', 'groups g']) + .get(callback); ``` -***Multiple From Calls*** +**_Multiple From Calls_** ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') - .from('users u') - .get(callback); + .from('users u') + .get(callback); ``` -------------- +-------------------------------------------------------------------------------- ### JOIN #### .join(table,relation[,direction]) - This SQL command is used query multiple tables related and connected by keys and get a single resultset. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :-------- | :-------------------------------------------------------- | -| table | String | Required | The table or view to join to. | -| relation | String | Required | The "ON" statement that relates two tables together | -| direction | String | "left" | Direction of the join (see join types list below) | +Parameter | Type | Default | Description +:-------- | :----- | :------- | :-------------------------------------------------- +table | String | Required | The table or view to join to. +relation | String | Required | The "ON" statement that relates two tables together +direction | String | "left" | Direction of the join (see join types list below) **Join Types/Directions** - -* left -* right -* outer -* inner -* left outer -* right outer +- left +- right +- outer +- inner +- left outer +- right outer The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. @@ -513,8 +474,8 @@ If no direction is specified, "left" will be used: // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') - .join('types t', 't.id=u.type_id') - .get(callback); + .join('types t', 't.id=u.type_id') + .get(callback); ``` You may specify a direction: @@ -524,8 +485,8 @@ You may specify a direction: // FROM `users` `u` // RIGHT OUTER JOIN `types` `t` ON `t`.`id`=`u`.`type_id` qb.select('u.id,u.name,t.name as type_name').from('users u') - .join('types t', 't.id=u.type_id', 'right outer') - .get(callback); + .join('types t', 't.id=u.type_id', 'right outer') + .get(callback); ``` Multiple function calls can be made if you need several joins in one query: @@ -537,26 +498,24 @@ Multiple function calls can be made if you need several joins in one query: // LEFT JOIN `locations` `l` ON `l`.`id`=`u`.`location_id` var select = ['u.id', 'u.name', 't.name as type', 'l.name as location']; qb.select(select).from('users u') - .join('types t', 't.id=u.type_id', 'right outer') - .join('locations l', 'l.id=u.location_id', 'left') - .get(callback); + .join('types t', 't.id=u.type_id', 'right outer') + .join('locations l', 'l.id=u.location_id', 'left') + .get(callback); ``` -------------- +-------------------------------------------------------------------------------- ### WHERE - This SQL command is used to limit the resultset based on filters. -| Parameter | Type | Default | Description | -| :------------ | :------------ | :-------- | :------------------------------------------------------------ | -| field/filters | String/Object | Required | A field name, a WHERE clause, or an object of key/value pairs | -| value | Mixed | N/A | When the first parameter is a field name, this is the value | -| escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping | +Parameter | Type | Default | Description +:------------ | :------------ | :------- | :------------------------------------------------------------ +field/filters | String/Object | Required | A field name, a WHERE clause, or an object of key/value pairs +value | Mixed | N/A | When the first parameter is a field name, this is the value +escape | Boolean | TRUE | TRUE: Escape field names and values; FALSE: No escaping #### .where(field[,value[,escape]]) - -This method can be called in many different ways depending on your style and the format of the data that you have at the time of calling it. For standard SQL, all clauses will be joined with 'AND'—if you need to join clauses by 'OR', please us `.or_where()`. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. +This method can be called in many different ways depending on your style and the format of the data that you have at the time of calling it. For standard SQL, all clauses will be joined with 'AND'—if you need to join clauses by 'OR', please us `.or_where()`. By default, all values and field names passed to this function will be escaped automatically to produce safer queries. You can turn this off by passing **false** into the third parameter. If a valid field name is passed in the first parameter, you can pass an array the second parameter and the call will be treated as a [.where_in()](#where_in). @@ -583,7 +542,7 @@ You can conveniently pass an object of key:value pairs (which can also contain c qb.select('planet').where({'order <=':3, class:'M'}).get('planets',callback); ``` -You can construct complex WHERE clauses manually and they will be escaped properly as long as there are no paranthesis within it. *Please, for custom clauses containing subqueries, make sure you escape everything properly!* ***ALSO NOTE:*** with this method, there may be conflicts between database drivers! +You can construct complex WHERE clauses manually and they will be escaped properly as long as there are no paranthesis within it. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' @@ -596,26 +555,26 @@ You can pass a non-empty array as a value and that portion will be treated as a // SELECT `star_system` FROM `star_systems` // WHERE `planet_count` >= 4, `star` IN('Sun', 'Betelgeuse') qb.select('star_system') - .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']) - .get('star_systems',callback); + .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']) + .get('star_systems',callback); ``` -#### .or_where(field[,value[,escape]]) +#### .or_where(field[,value[,escape]]) This method functions identically to [.where()](#where) except that it joins clauses with 'OR' instead of 'AND'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` = 'Sun' OR `star` = 'Betelgeuse' qb.select('star_system').where('star', 'Sun') - .or_where('star', 'Betelgeuse') - .get('star_systems',callback); + .or_where('star', 'Betelgeuse') + .get('star_systems',callback); ``` -#### .where_in(field,values[,escape]) +#### .where_in(field,values[,escape]) This will create a "WHERE IN" statement in traditional SQL which is useful when you're trying to find rows with fields matching many different values... It will be joined with existing "WHERE" statements with 'AND'. ```javascript @@ -626,8 +585,8 @@ qb.select('star_system').where_in('star',stars).get('star_systems',callback); ``` -#### .or_where_in(field,values[,escape]) +#### .or_where_in(field,values[,escape]) Same as `.where_in()` except the clauses are joined by 'OR'. ```javascript @@ -635,13 +594,13 @@ Same as `.where_in()` except the clauses are joined by 'OR'. // WHERE `planet_count` = 4 OR `star` IN('Sun', 'Betelgeuse') var stars = ['Sun', 'Betelgeuse']; qb.select('star_system').where('planet_count',4) - .or_where_in('star',stars) - .get('star_systems',callback); + .or_where_in('star',stars) + .get('star_systems',callback); ``` -#### .where_not_in(field,values[,escape]) +#### .where_not_in(field,values[,escape]) Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clauses are joined with 'AND'. ```javascript @@ -652,8 +611,8 @@ qb.select('star_system').where_not_in('star',stars).get('star_systems',callback) ``` -#### .or_where_not_in(field,values[,escape]) +#### .or_where_not_in(field,values[,escape]) Same as `.where_not_in()` except that clauses are joined with 'OR'. ```javascript @@ -663,27 +622,25 @@ Same as `.where_not_in()` except that clauses are joined with 'OR'. var stars = ['Sun', 'Betelgeuse']; var planet_sizes = [2,4,6,8]; qb.select('star_system') - .where_not_in('star',stars) - .or_where_not_in('planet_size',planet_sizes) - .get('star_systems',callback); + .where_not_in('star',stars) + .or_where_not_in('planet_size',planet_sizes) + .get('star_systems',callback); ``` -------------- +-------------------------------------------------------------------------------- ### LIKE - This SQL command is used to find close matches where as the "WHERE" command is for precise matches. This is useful for doing searches. -| Parameter | Type | Default | Description | -| :------------ | :------------ | :-------- | :------------------------------------------------ | -| field/filters | String/Object | Required | Field name or object of field/match pairs | -| value | String/Number | Required | The value you want the field to closely match | -| side | String | 'both' | before: '%value'; after: 'value%', both: '%value%'| +Parameter | Type | Default | Description +:------------ | :------------ | :------- | :------------------------------------------------- +field/filters | String/Object | Required | Field name or object of field/match pairs +value | String/Number | Required | The value you want the field to closely match +side | String | 'both' | before: '%value'; after: 'value%', both: '%value%' **NOTE:** You can, alternatively, use `'right'` and `'left'` in place of `'before'` and '`after`' if you prefer. #### .like(field,match[,side]) - All fields are escaped automatically, no exceptions. Multiple calls will be joined together with 'AND'. You can also pass an object of field/match pairs. Wildcard sides are interchangeable between before/left and after/right--choose the one that makes the most sense to you (there are examples of each below). **Examples** @@ -724,10 +681,10 @@ If you'd like to have multiple like clauses, you can do that by calling like mul // AND `middle_name` LIKE '%lyt%' // AND `last_name` LIKE '%arris' qb.select('first_name') - .like('first_name', 'Kim', 'right') - .like('middle_name', 'lyt') - .like('last_name', 'arris', 'left') - .get('users',callback); + .like('first_name', 'Kim', 'right') + .like('middle_name', 'lyt') + .like('last_name', 'arris', 'left') + .get('users',callback); ``` Or you can do it with an object of field/match pairs. If you want to pass a wildcard side, provide `null` as the second paramter and the side as the third. **Note**: All `match` values in an object will share the same wildcard side. @@ -738,13 +695,13 @@ Or you can do it with an object of field/match pairs. If you want to pass a wild // AND `middle_name` LIKE '%the' // AND `last_name` LIKE '%is' qb.select('first_name') - .like({first_name: 'ly', middle_name: 'the', last_name: 'is'}, null, 'before') - .get('users',callback); + .like({first_name: 'ly', middle_name: 'the', last_name: 'is'}, null, 'before') + .get('users',callback); ``` -#### .or_like(field,match[,side]) +#### .or_like(field,match[,side]) This is exactly the same as the `.like()` method except that the clauses are joined by 'OR' not 'AND'. **Example** @@ -755,15 +712,15 @@ This is exactly the same as the `.like()` method except that the clauses are joi // OR `middle_name` LIKE '%lyt%' // OR `last_name` LIKE '%arris' qb.select('first_name') - .or_like('first_name', 'Kim', 'right') - .or_like('middle_name', 'lyt') - .or_like('last_name', 'arris', 'left') - .get('users',callback); + .or_like('first_name', 'Kim', 'right') + .or_like('middle_name', 'lyt') + .or_like('last_name', 'arris', 'left') + .get('users',callback); ``` -#### .not_like(field,match[,side]) +#### .not_like(field,match[,side]) This is exactly the same as the `.like()` method except that it creates "NOT LIKE" statements. **Example** @@ -774,13 +731,13 @@ This is exactly the same as the `.like()` method except that it creates "NOT LIK // AND `middle_name` NOT LIKE 'B%' // AND `last_name` NOT LIKE 'C%' qb.select('first_name') - .not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') - .get('users',callback); + .not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') + .get('users',callback); ``` -#### .or_not_like(field,match[,side]) +#### .or_not_like(field,match[,side]) This is exactly the same as the `.not_like()` method except that the clauses are joined by 'OR' not 'AND'. **Example** @@ -791,20 +748,19 @@ This is exactly the same as the `.not_like()` method except that the clauses are // OR `middle_name` NOT LIKE 'B%' // OR `last_name` NOT LIKE 'C%' qb.select('first_name') - .or_not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') - .get('users',callback); + .or_not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') + .get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### GROUP BY #### .group_by(fields) - This SQL command allows you to get the first (depending on ORDER) result of a group of results related by a shared value or values. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :------- | :------------------------------------ | -| field(s) | String/Object | Required | Field name or array of field names | +Parameter | Type | Default | Description +:-------- | :------------ | :------- | :--------------------------------- +field(s) | String/Object | Required | Field name or array of field names **Examples** @@ -822,18 +778,17 @@ Group by multiple fields: qb.group_by(['department_id', 'position_id']).get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### HAVING #### .having(field,value) - This SQL command is similar to the 'WHERE' command but is used when aggregate functions are used in the "SELECT" portion of the query. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :-------- | :----------------------------------------------------- | -| field/filters | String/Object | Required | Field name or object of field/value pairs to filter on | -| value | Mixed | NULL | Value to filter by | -| escape | Boolean | true | TRUE: Escape fields and values; FALSE: Don't escape. | +Parameter | Type | Default | Description +:------------ | :------------ | :------- | :----------------------------------------------------- +field/filters | String/Object | Required | Field name or object of field/value pairs to filter on +value | Mixed | NULL | Value to filter by +escape | Boolean | true | TRUE: Escape fields and values; FALSE: Don't escape. This method works exactly the same way as the `.where()` method works with the exception of the fact that there is no 'HAVING' equivalent to 'WHERE IN'. See the [.where()](#where) documentation if you need additional information. @@ -866,7 +821,7 @@ You can conveniently pass an object of key:value pairs (which can also contain c qb.group_by('id').having({'num_planets >': 5}).count('star_systems',callback); ``` -You can construct complex WHERE clauses manually and they will be escaped properly. *Please, for custom clauses containing subqueries, make sure you escape everything properly!* ***ALSO NOTE:*** with this method, there may be conflicts between database drivers! +You can construct complex WHERE clauses manually and they will be escaped properly. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! ```javascript // SELECT COUNT(*) AS `num_planets` FROM `star_systems` @@ -876,8 +831,8 @@ qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems ``` -#### .or_having(field[,value[,escape]]) +#### .or_having(field[,value[,escape]]) This method functions identically to [.having()](#having) except that it joins clauses with 'OR' instead of 'AND'. ```javascript @@ -885,31 +840,29 @@ This method functions identically to [.having()](#having) except that it joins c // GROUP BY `id` // HAVING `num_planets` >= 5 OR `num_moons` <= 10 qb.group_by('id') - .having('num_planets >=',5) - .or_having('num_moons <=', 10) - .count('star_systems',callback); + .having('num_planets >=',5) + .or_having('num_moons <=', 10) + .count('star_systems',callback); ``` -------------- +-------------------------------------------------------------------------------- ### ORDER BY #### .order_by(field[,direction]) - This SQL command is used to order the resultset by a field or fields in descending, ascending, or random order(s). -| Parameter | Type | Default | Description | -| :-------- | :------------ | :-------- | :------------------------------------------------------------------------ | -| fields | String/Array | Required | Field name or an array of field names, possibly with directions as well | -| direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. | +Parameter | Type | Default | Description +:-------- | :----------- | :------- | :---------------------------------------------------------------------- +fields | String/Array | Required | Field name or an array of field names, possibly with directions as well +direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. This is a very flexible method, offerring a wide variety of ways you can call it. Variations include: - -* Pass the field name and ommit the direction -* Pass the field name and the direction as the first and second parameters, respectively (most common) -* Pass an array of fields to first paramter, direction to second parameter. -* Pass an array of fields + directions in first parameter and ommit the second one. -* Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter. -* Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. +- Pass the field name and ommit the direction +- Pass the field name and the direction as the first and second parameters, respectively (most common) +- Pass an array of fields to first paramter, direction to second parameter. +- Pass an array of fields + directions in first parameter and ommit the second one. +- Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter. +- Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. **Examples** @@ -955,17 +908,16 @@ Pass a raw comma-seperated string of field + directions in first parameter and o qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies',callback); ``` -------------- +-------------------------------------------------------------------------------- ### LIMIT #### .limit(limit_to,offset) - This SQL command is used to limit a result set to a maximum number of results, regardless of the actual number of results that might be returned by a non-limited query. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :-------- | :---------------------------------------------------- | -| limit_to | Integer | Required | The maximum number of results you want from the query | -| offset | Integer | NULL | Optional offset value (where to start before limiting)| +Parameter | Type | Default | Description +:-------- | :------ | :------- | :----------------------------------------------------- +limit_to | Integer | Required | The maximum number of results you want from the query +offset | Integer | NULL | Optional offset value (where to start before limiting) **Example** @@ -981,16 +933,15 @@ You can provide an option offset value instead of calling [.offset()](#offset) s qb.limit(5,5).get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### OFFSET #### .offset(offset) - This SQL command is tell the "LIMIT" where to start grabbing data. If cannot be used without a limit having been set first. -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :----------------------------- | -| offset | Integer | NULL | where to start before limiting | +Parameter | Type | Default | Description +:-------- | :------ | :------ | :----------------------------- +offset | Integer | NULL | where to start before limiting The practical uses of this method are probably miniscule since the `.limit()` method must be called in order to use it and the limit method provides a means by which to set the offset. In any case, the method is very simple: pass the result row index that you want to start from when limiting. This is most useful for pagination of search results and similar scenarios. @@ -1001,18 +952,17 @@ The practical uses of this method are probably miniscule since the `.limit()` me qb.limit(5).offset(25).get('users',callback); ``` -------------- +-------------------------------------------------------------------------------- ### SET #### .set(key[, value[, escape]]) - This SQL is used to set values to fields when utilizing the `update`, and `insert` methods. More than likely, you will choose use the shorthand notation provided by the aforementioned methods, but, this can be handy in some cases. -| Parameter | Type | Default | Description | -| :-------- | :------------ | :----- | :-------------------------------------------------------------------------------------------------------- | -| key | String/Object | Required | The key of field to be set or an object of key:value pairs | -| value | Mixed | NULL | Required if `key` is a string. Pass NULL if `key` is an object and you'd like to use the 3rd parameter | -| escape | String/Object | true | If false, keys and values will not be escaped. | +Parameter | Type | Default | Description +:-------- | :------------ | :------- | :----------------------------------------------------------------------------------------------------- +key | String/Object | Required | The key of field to be set or an object of key:value pairs +value | Mixed | NULL | Required if `key` is a string. Pass NULL if `key` is an object and you'd like to use the 3rd parameter +escape | String/Object | true | If false, keys and values will not be escaped. **Examples** @@ -1031,63 +981,59 @@ var birthday = new Date(1986, 7, 5, 8, 15, 23); qb.set({birthday: birthday, anniversary: '2010-05-15'}).update('users', callback); ``` -------------- +-------------------------------------------------------------------------------- -Execution Methods ------------------ +## Execution Methods -| API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :-------------------------------- | :------------ | :-------: | :---: | :-------: | :-------: | :-------: | :---: | -| [query()](#query) | N/A | ✓ | | | | | | -| [get()](#get) | N/A | ✓ | | | | | | -| [get_where()](#get_where) | N/A | ✓ | | | | | | -| [count()](#count) | COUNT | ✓ | | | | | | -| [update()](#update) | UPDATE | ✓ | | | | | | -| [update_batch()](#update_batch) | N/A | ✓ | | | | | | -| [insert()](#insert) | INSERT | ✓ | | | | | | -| [insert_batch()](#insert_batch) | N/A | ✓ | | | | | | -| [insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | | -| [delete()](#delete) | DELETE | ✓ | | | | | | -| [truncate()](#truncate) | TRUNCATE | ✓ | | | | | | -| [empty_table()](#empty_table) | DELETE | ✓ | | | | | | +API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:-------------------------------- | :------------ | :------: | :---: | :----: | :----: | :------: | :---: +[query()](#query) | N/A | ✓ | | | | | +[get()](#get) | N/A | ✓ | | | | | +[get_where()](#get_where) | N/A | ✓ | | | | | +[count()](#count) | COUNT | ✓ | | | | | +[update()](#update) | UPDATE | ✓ | | | | | +[update_batch()](#update_batch) | N/A | ✓ | | | | | +[insert()](#insert) | INSERT | ✓ | | | | | +[insert_batch()](#insert_batch) | N/A | ✓ | | | | | +[insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | +[delete()](#delete) | DELETE | ✓ | | | | | +[truncate()](#truncate) | TRUNCATE | ✓ | | | | | +[empty_table()](#empty_table) | DELETE | ✓ | | | | | ### What are "Execution Methods"?? - Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. Both (if provided) will be supplied to the callback function. ### Handling Error Messages and Results - The final parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. ### Response Format Examples -| API Method(s) | Response Format | -| :-------------------------------- | :-------------------------------------------------------------------------------------------- | -| get(), get_where() | `[{field:value,field2:value2},{field:value, field2:value2}]` | -| count() | Integer (ex. `578`) | -| insert(), update(), delete() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | -| insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` | - +API Method(s) | Response Format +:----------------------------- | :----------------------------------------------------------------------------------------- +get(), get_where() | `[{field:value,field2:value2},{field:value, field2:value2}]` +count() | Integer (ex. `578`) +insert(), update(), delete() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` +insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` #### Callback Example ```javascript var callback = function(err, response) { - qb.release(); - if (err) { - console.error(err); - } - else { - for (var i in response) { - var row = response[i]; - /* - Do something with each row... - */ - } - } + qb.release(); + if (err) { + console.error(err); + } + else { + for (var i in response) { + var row = response[i]; + /* + Do something with each row... + */ + } + } }; pool.get_connection(function(qb) { - qb.get('foo',callback); + qb.get('foo',callback); }); ``` @@ -1098,35 +1044,36 @@ var pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); var data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; pool.get_connection(function(qb) { - qb.insert('employees', data, function(err, res) { - if (err) { - console.error(err); - } - else { - if (res.affected_rows > 0) { - var insert_id = res.insert_id; - qb.get_where('employees', {id: insert_id}, function(err, res) { - qb.release(); - console.dir(res); - }); - } - else { - console.error("New user was not added to database!"); - } - } - }); + qb.insert('employees', data, function(err, res) { + if (err) { + console.error(err); + } + else { + if (res.affected_rows > 0) { + var insert_id = res.insert_id; + qb.get_where('employees', {id: insert_id}, function(err, res) { + qb.release(); + console.dir(res); + }); + } + else { + console.error("New user was not added to database!"); + } + } + }); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .query(query_string,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| query_string | String | Required | Query to send directly to your database driver | -| callback | Function | Required | What to do when the driver has responded | +Parameter | Type | Default | Description +:----------- | :------- | :------- | :--------------------------------------------- +query_string | String | Required | Query to send directly to your database driver +callback | Function | Required | What to do when the driver has responded *****This method bypasses the entire QueryBuilder portion of this module***** is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. @@ -1136,21 +1083,22 @@ There are scenarios when using this method may be required; for instance, if you ```javascript var sql = qb.select(['f.foo', 'b.bar']) - .from('foo f') - .join('bar b', 'b.foo_id=f.id', 'left') - .get_compiled_select(); + .from('foo f') + .join('bar b', 'b.foo_id=f.id', 'left') + .get_compiled_select(); qb.query("CREATE VIEW `foobar` AS " + sql, callback); ``` -------------- +-------------------------------------------------------------------------------- + ### .get([table,]callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------- | -| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | -| callback | Function | Required | What to do when the driver has responded | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :------------------------------------------------------------ +table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. +callback | Function | Required | What to do when the driver has responded This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. @@ -1179,11 +1127,11 @@ Just a more-complicated example for the sake of it (note: using connection pool) ```javascript /** * SELECT - * `g`.`name`, - * `g`.`diameter`, - * `g`.`type_id`, - * `gt`.`name` AS `type`, - * COUNT(`s`.`id`) as `num_stars` + * `g`.`name`, + * `g`.`diameter`, + * `g`.`type_id`, + * `gt`.`name` AS `type`, + * COUNT(`s`.`id`) as `num_stars` * FROM `galaxies` `g` * LEFT JOIN `galaxy_types` `gt` ON `gt`.`id`=`g`.`type_id` * LEFT JOIN `stars` `s` ON `s`.`galaxy_id`=`g`.`id` @@ -1192,38 +1140,39 @@ Just a more-complicated example for the sake of it (note: using connection pool) * LIMIT 10 **/ qb.limit(10) - .select(['g.name', 'g.diameter', 'gt.name as type']) - .select('COUNT(`s`.`id`) as `num_stars`',null,false) - .from('galaxies g') - .join('galaxy_types gt', 'gt.id=g.type_id', 'left') - .join('stars s', 's.galaxy_id=g.id', 'left') - .group_by('g.id') - .order_by('g.name', 'asc') - .get(function(err, response, conn) { - conn.release(); - if (err) return console.error(err); - - for (var i in response) { - var row = response[i]; - console.log("The " + row.name + " is a " + row.diameter - + " lightyear-wide " + row.type + " galaxy with " - + row.num_stars + " stars."); - } - }); -``` - -------------- + .select(['g.name', 'g.diameter', 'gt.name as type']) + .select('COUNT(`s`.`id`) as `num_stars`',null,false) + .from('galaxies g') + .join('galaxy_types gt', 'gt.id=g.type_id', 'left') + .join('stars s', 's.galaxy_id=g.id', 'left') + .group_by('g.id') + .order_by('g.name', 'asc') + .get(function(err, response, conn) { + conn.release(); + if (err) return console.error(err); + + for (var i in response) { + var row = response[i]; + console.log("The " + row.name + " is a " + row.diameter + + " lightyear-wide " + row.type + " galaxy with " + + row.num_stars + " stars."); + } + }); +``` + +-------------------------------------------------------------------------------- + ### .get_where(table,where,callback) -| Parameter | Type | Default | Description | -| :-------- | :---------------- | :-------- | :------------------------------------------------------------ | -| table | String or Array | Required | Used to avoid having to call `.from()` seperately. | -| where | Object | Required | Used to avoid having to call `.where()` seperately | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :-------------- | :------- | :------------------------------------------------- +table | String or Array | Required | Used to avoid having to call `.from()` seperately. +where | Object | Required | Used to avoid having to call `.where()` seperately +callback | Function | Required | What to do when the driver has responded. -This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that *all* parameters are required and they must be in the proper order. +This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that _all_ parameters are required and they must be in the proper order. **Type of Response Sent to Callback** @@ -1238,22 +1187,23 @@ Basic example: qb.get_where('galaxies', {'num_stars >': 100000000}, callback); ``` -You can still provide other where statements if you want—they'll all work hapilly together: +You can still provide other where statements if you want—they'll all work hapilly together: ```javascript // SELECT * FROM `galaxies` WHERE `num_stars` > 100000000 AND `galaxy_type_id` = 3 qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, callback); ``` -------------- +-------------------------------------------------------------------------------- + ### .count([table,]callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :------------------------------------------------------------ +table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. +callback | Function | Required | What to do when the driver has responded. This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. @@ -1269,22 +1219,23 @@ Integer // SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 3 var type = 3; qb.where('type',type).count('galaxies', function(err, count) { - if (err) return console.error(err); - console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); + if (err) return console.error(err); + console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .update(table,data[,where],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | -| table | String | null | (suggested) The table/collection you'd like to update | -| data | Object | null | (suggested) The data to update (ex. `{field: value}`) | -| where | Object | null | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- +table | String | null | (suggested) The table/collection you'd like to update +data | Object | null | (suggested) The data to update (ex. `{field: value}`) +where | Object | null | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +callback | Function | Required | What to do when the driver has responded. This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... @@ -1307,28 +1258,28 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/update_account', function(req, res) { - var user_id = req.session.user_id; - var sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; - var sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; - - var data = { - first_name: sanitize_name(req.body.first_name), - last_name: sanitize_name(req.body.last_name), - age: sanitize_age(req.body.last_name), - bio: req.body.bio, - }; - - pool.get_connection(function(qb) { - qb.update('users', data, {id:user_id}, function(err, res) { - qb.release(); - if (err) return console.error(err); - - var page_data = { - prefill: data, - } - return res.render('/account_updated', page_data); - }); - }); + var user_id = req.session.user_id; + var sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; + var sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; + + var data = { + first_name: sanitize_name(req.body.first_name), + last_name: sanitize_name(req.body.last_name), + age: sanitize_age(req.body.last_name), + bio: req.body.bio, + }; + + pool.get_connection(function(qb) { + qb.update('users', data, {id:user_id}, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + prefill: data, + } + return res.render('/account_updated', page_data); + }); + }); }); ``` @@ -1337,30 +1288,31 @@ Here's another (more-direct) example where one decided to supply the table, data ```javascript var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); qb.where('id', 42) - .from('users') - .set('email', 'email@domain.net') - .update(null, null, null, function(err, res) { - if (err) return console.error(err); - console.log("Updated: " + res.affected_rows + " rows"); - }); + .from('users') + .set('email', 'email@domain.net') + .update(null, null, null, function(err, res) { + if (err) return console.error(err); + console.log("Updated: " + res.affected_rows + " rows"); + }); ``` -------------- +-------------------------------------------------------------------------------- + ### .update_batch(table,dataset,index[,where],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to insert into | -| dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) | -| index | String | Required | Name of the key in each data object that represents a `where` clause. | -| where | Object | NULL | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- +table | String | Required | The table/collection you'd like to insert into +dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) +index | String | Required | Name of the key in each data object that represents a `where` clause. +where | Object | NULL | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +callback | Function | Required | What to do when the driver has responded. This method is a somewhat-complex one and, when using transactional databases, a bit pointless. Nevertheless, this will allow you to update a batch of rows with one query which, in theory, should be faster than running multiple update queries. -The important thing to understand is that there are, essentially, *two* `where` clause portions with this method: a local one, and a global one. The `index` you specify in the 3rd parameter represents the name of the key in each data object of the dataset that will act as the local `where` clause for that particular row to be updated. That row, however, will only be updated if the global where clause(s) (4th param) have been satisfied as well. +The important thing to understand is that there are, essentially, _two_ `where` clause portions with this method: a local one, and a global one. The `index` you specify in the 3rd parameter represents the name of the key in each data object of the dataset that will act as the local `where` clause for that particular row to be updated. That row, however, will only be updated if the global where clause(s) (4th param) have been satisfied as well. **NOTE:** This method will create batches of up to 100 rows at a time. So, if you have 250 rows to update, this will make 3 queries to your database. @@ -1374,52 +1326,53 @@ var key = 'id'; // All objects in this dataset must have an id key var dataset = [ - {id: 4569, name: 'Cartwheel', constellation: 'Sculptor'}, - {id: 5631, name: 'Black Eye', constellation: 'Coma Berenices'}, - {id: 1238, name: 'Sombrero', constellation: 'Virgo'} + {id: 4569, name: 'Cartwheel', constellation: 'Sculptor'}, + {id: 5631, name: 'Black Eye', constellation: 'Coma Berenices'}, + {id: 1238, name: 'Sombrero', constellation: 'Virgo'} ]; var where = {'last_updated <' : '2015-01-01'} qb.update_batch('galaxies', dataset, key, where, function(err, res) { - if (err) return console.error(err); - - /* - * UPDATE `galaxies` - * SET - * `name` = CASE - * WHEN `id` = 4569 THEN 'Cartwheel' - * WHEN `id` = 5631 THEN 'Black Eye' - * WHEN `id` = 1238 THEN 'Sombrero' - * ELSE `name` - * END, - * `constellation` = CASE - * WHEN `id` = 4569 THEN 'Sculptor' - * WHEN `id` = 5631 THEN 'Coma Berenices' - * WHEN `id` = 1238 THEN 'Virgo' - * ELSE `constellation` - * END - * WHERE `id` IN(4569, 5631, 1238) - * AND `last_updated` < '2015-01-01' - */ - var last_query = qb.last_query(); + if (err) return console.error(err); + + /* + * UPDATE `galaxies` + * SET + * `name` = CASE + * WHEN `id` = 4569 THEN 'Cartwheel' + * WHEN `id` = 5631 THEN 'Black Eye' + * WHEN `id` = 1238 THEN 'Sombrero' + * ELSE `name` + * END, + * `constellation` = CASE + * WHEN `id` = 4569 THEN 'Sculptor' + * WHEN `id` = 5631 THEN 'Coma Berenices' + * WHEN `id` = 1238 THEN 'Virgo' + * ELSE `constellation` + * END + * WHERE `id` IN(4569, 5631, 1238) + * AND `last_updated` < '2015-01-01' + */ + var last_query = qb.last_query(); }); ``` As you can see, in each `CASE` statement, the `key` and it's value are being used to determine what to set the other items to. It's important to know that the `key` and it's `value` will not be updated in the batch update... they are just there to make sure we set the right values in the right place. -------------- +-------------------------------------------------------------------------------- + ### .insert(table,data[,ignore[,on_dupe]],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to insert into | -| data | Object | Required | The data to insert (ex. `{field: value}`) | -| ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. | -| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :-------------------------------------------------------------------------------------------------------------- +table | String | Required | The table/collection you'd like to insert into +data | Object | Required | The data to insert (ex. `{field: value}`) +ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. +on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). +callback | Function | Required | What to do when the driver has responded. This method is used to insert new data into a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the ID of the newly inserted item, the affected rows (should be 1), etc... @@ -1438,43 +1391,44 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/add_article', function(req, res) { - var user_id = req.session.user_id; - - var data = { - title: req.body.first_name, - body: req.body.last_name, - author: user_id, - publish_date: sanitize_age(req.body.last_name) - }; - - pool.get_connection(function(qb) { - qb.insert('articles', data, function(err, res) { - qb.release(); - if (err) return console.error(err); - - var page_data = { - article_id: res.insert_id, - } - return res.render('/article_manager', page_data); - }); - }); + var user_id = req.session.user_id; + + var data = { + title: req.body.first_name, + body: req.body.last_name, + author: user_id, + publish_date: sanitize_age(req.body.last_name) + }; + + pool.get_connection(function(qb) { + qb.insert('articles', data, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + article_id: res.insert_id, + } + return res.render('/article_manager', page_data); + }); + }); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .insert_batch(table,dataset[,ignore[,on_dupe]],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to delete records from. | -| dataset | Array | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. | -| ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. | -| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ +table | String | Required | The table/collection you'd like to delete records from. +dataset | Array | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. +ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. +on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). +callback | Function | Required | What to do when the driver has responded. -The goal of this method is to speed the insertion of many rows. For instance, if you were insert 1,000 rows... Instead of making 1,000 queries to the server, you could just call `insert_batch()` and it would generate a single query to insert 1,000 rows. This is *much* more efficient and less taxing on your app and database server. +The goal of this method is to speed the insertion of many rows. For instance, if you were insert 1,000 rows... Instead of making 1,000 queries to the server, you could just call `insert_batch()` and it would generate a single query to insert 1,000 rows. This is _much_ more efficient and less taxing on your app and database server. **Type of Response Sent to Callback** @@ -1486,31 +1440,32 @@ Object containing information about the result of the query. var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); var data = [ - {name: 'MySQL', version: '5.5.40'}, - {name: 'Mongo', version: '2.6.7' }, - {name: 'Postgres', version: '8.4'} + {name: 'MySQL', version: '5.5.40'}, + {name: 'Mongo', version: '2.6.7' }, + {name: 'Postgres', version: '8.4'} ]; qb.insert_batch('db_engines', data, function(err, res) { - if (err) throw err; + if (err) throw err; - // INSERT INTO `db_engines` (`name`, `version`) - // VALUES ('MySQL', '5.5.40'), ('Mongo', '2.6.7'), ('Postgres', '8.4'); - console.log(qb.last_query()); + // INSERT INTO `db_engines` (`name`, `version`) + // VALUES ('MySQL', '5.5.40'), ('Mongo', '2.6.7'), ('Postgres', '8.4'); + console.log(qb.last_query()); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .insert_ignore(table,data[,on_dupe],callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to delete records from. | -| data | Object | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. | -| on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ +table | String | Required | The table/collection you'd like to delete records from. +data | Object | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. +on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). +callback | Function | Required | What to do when the driver has responded. This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd paramter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). With the third parameter, you can create an 'upsert' of sorts. Without the third parameter, it's essentially just "ignoring" errors, or, rather, converting them to simple warnings. @@ -1525,22 +1480,22 @@ Object containing information about the result of the query. * Current Table Structure: * * [ - * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, - * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, - * {name: 'Postgres', version: '8.4', last_modified: 1423252248 } - * ] + * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, + * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, + * {name: 'Postgres', version: '8.4', last_modified: 1423252248 } + * ] */ var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, function(err, res) { - if (err) throw err; + if (err) throw err; - // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); - console.log(qb.last_query()); + // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); + console.log(qb.last_query()); - // 0 (because this data already exists...) - console.log(res.affected_rows); + // 0 (because this data already exists...) + console.log(res.affected_rows); }); ``` @@ -1549,36 +1504,37 @@ This time we'll do it with an `on_dupe` string ```javascript var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { - if (err) throw err; - - // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4') ON DUPLICATE KEY UPDATE last_modified = NOW(); - console.log(qb.last_query()); - - // 1 (because we updated the last_modified field) - console.log(res.affected_rows); - - /* - * Resulting Table Structure: - * - * [ - * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, - * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, - * {name: 'Postgres', version: '8.4', last_modified: 1423264972 } - * ] - */ + if (err) throw err; + + // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4') ON DUPLICATE KEY UPDATE last_modified = NOW(); + console.log(qb.last_query()); + + // 1 (because we updated the last_modified field) + console.log(res.affected_rows); + + /* + * Resulting Table Structure: + * + * [ + * {name: 'MySQL', version: '5.5.40', last_modified: 1423252221 }, + * {name: 'Mongo', version: '2.6.7', last_modified: 1423252232 }, + * {name: 'Postgres', version: '8.4', last_modified: 1423264972 } + * ] + */ }); ``` -------------- +-------------------------------------------------------------------------------- + ### .delete(table,where,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :---------------------------------------------------------------------------------------------------- | -| table | String | Required | The table/collection you'd like to delete records from. | -| where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :---------------------------------------------------------------------------------------------------- +table | String | Required | The table/collection you'd like to delete records from. +where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +callback | Function | Required | What to do when the driver has responded. This method is used to delete records from a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with the number of affected rows. @@ -1599,36 +1555,37 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/delete_comment/:id', function(req, res) { - var comment_id = req.params.id; - - pool.get_connection(function(qb) { - qb.get('comments', {id: id}, function(err, res) { - if (err) return console.error(err); - var article_id = res.article_id; - - qb.delete('comments', {id: id}, function(err, res) { - qb.release(); - if (err) return console.error(err); - - var page_data = { - num_removed: res.affected_rows, - } - return res.render('/article/' + article_id, page_data); - }); - }); - }); + var comment_id = req.params.id; + + pool.get_connection(function(qb) { + qb.get('comments', {id: id}, function(err, res) { + if (err) return console.error(err); + var article_id = res.article_id; + + qb.delete('comments', {id: id}, function(err, res) { + qb.release(); + if (err) return console.error(err); + + var page_data = { + num_removed: res.affected_rows, + } + return res.render('/article/' + article_id, page_data); + }); + }); + }); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .truncate(table,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | -| table | String | Required | The table/collection you'd like to truncate. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :------- | :------------------------------------------- +table | String | Required | The table/collection you'd like to truncate. +callback | Function | Required | What to do when the driver has responded. For drivers that support it (MySQL), this method will utilize the `TRUNCATE` directive to empty a table of all it's data. The main difference between the `truncate()` method and the `empty_table()` method is that, when available, and when possible, truncating a table will reset your AUTO_INCREMENT counter back to zero. If you simply delete every row from a table, the next item inserted will just continue with the next highest ID from the deleted records. @@ -1654,30 +1611,31 @@ var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); */ pool.get_connection(function(qb) { - qb.truncate('users', function(err, res) { - if (err) throw err; - qb.insert('users', {name: 'Bob'}, function(err, res) { - if (err) throw err; - qb.get_where('users', {id: res.insert_id}, function(err, res) { - qb.release(); - if (err) throw err; - // { id: 1, name: 'Bob' } (notice ID is 1) - console.dir(res); - }); - }); - }); + qb.truncate('users', function(err, res) { + if (err) throw err; + qb.insert('users', {name: 'Bob'}, function(err, res) { + if (err) throw err; + qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.release(); + if (err) throw err; + // { id: 1, name: 'Bob' } (notice ID is 1) + console.dir(res); + }); + }); + }); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .empty_table(table,callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :-------------------------------------------- | -| table | String | Required | The table/collection you'd like to truncate. | -| callback | Function | Required | What to do when the driver has responded. | +Parameter | Type | Default | Description +:-------- | :------- | :------- | :------------------------------------------- +table | String | Required | The table/collection you'd like to truncate. +callback | Function | Required | What to do when the driver has responded. This method will allow you to delete all records from a table/collection. @@ -1701,47 +1659,46 @@ var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); */ pool.get_connection(function(qb) { - qb.empty_table('users', function(err, res) { - if (err) throw err; - qb.insert('users', {name: 'Bob'}, function(err, res) { - if (err) throw err; - qb.get_where('users', {id: res.insert_id}, function(err, res) { - qb.release(); - if (err) throw err; - // { id: 4, name: 'Bob' } (notice ID is 4) - console.dir(res); - }); - }); - }); + qb.empty_table('users', function(err, res) { + if (err) throw err; + qb.insert('users', {name: 'Bob'}, function(err, res) { + if (err) throw err; + qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.release(); + if (err) throw err; + // { id: 4, name: 'Bob' } (notice ID is 4) + console.dir(res); + }); + }); + }); }); ``` -------------- - -Other Library-Specifc Methods ------------------------------ +-------------------------------------------------------------------------------- +## Other Library-Specifc Methods These are methods that aren't part of the query-building chain, but, rather, methods you might call before, after, or during (but not as part of) building a query. -| API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo | -| :-------------------------------------------- | :-------: | :---: | :-------: | :-------: | :-------: | :---: | -| [get_connection()](#get_connection) | ✓ | | | | | | -| [release()](#release) | ✓ | | | | | | -| [last_query()](#last_query) | ✓ | | | | | | -| [escape()](#escape) | ✓ | | | | | | -| [get_compiled_select()](#get_compiled_select) | ✓ | | | | | | -| [get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | | -| [get_compiled_update()](#get_compiled_update) | ✓ | | | | | | -| [get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | | +API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:-------------------------------------------- | :------: | :---: | :----: | :----: | :------: | :---: +[get_connection()](#get_connection) | ✓ | | | | | +[release()](#release) | ✓ | | | | | +[last_query()](#last_query) | ✓ | | | | | +[escape()](#escape) | ✓ | | | | | +[get_compiled_select()](#get_compiled_select) | ✓ | | | | | +[get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | +[get_compiled_update()](#get_compiled_update) | ✓ | | | | | +[get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | -------------- +-------------------------------------------------------------------------------- + ### .get_connection(callback) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| callback | Function | Required | What to do when the connection is retrieved from the pool. | +Parameter | Type | Default | Description +:-------- | :------- | :------- | :--------------------------------------------------------- +callback | Function | Required | What to do when the connection is retrieved from the pool. Used to get a new connection from the connection pool or cluster pool. An instances of the QueryBuilder adapter for your specific connection will be passed to the callback. Make sure that your connection is [release](#release)d when you are done with it! @@ -1752,19 +1709,19 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { - qb.limit(10).get('users', function(err, res) { - qb.release(); - // Do stuff with results or err - }); + qb.limit(10).get('users', function(err, res) { + qb.release(); + // Do stuff with results or err + }); }); ``` -------------- +-------------------------------------------------------------------------------- -### .release() -Releases a connection back to the pool when you are done with it. Calling this is *super* important! +### .release() +Releases a connection back to the pool when you are done with it. Calling this is _super_ important! **Examples** @@ -1775,21 +1732,21 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { - qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { - var users = users; - (function update_user() { - var user = users.shift(); - user.username = user.username.replace(/\^|/,''); - - qb.update('users', user, {id: user.id}, function(err, res) { - if (user.length > 0) { - setTimeout(update_user,0); - } else { - qb.release(); - } - }); - })(); - }); + qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { + var users = users; + (function update_user() { + var user = users.shift(); + user.username = user.username.replace(/\^|/,''); + + qb.update('users', user, {id: user.id}, function(err, res) { + if (user.length > 0) { + setTimeout(update_user,0); + } else { + qb.release(); + } + }); + })(); + }); }); ``` @@ -1800,19 +1757,19 @@ var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { - qb.get_where('users', {username: 'foobar'}, function(err, res) { - qb.release(); - if (err) throw err; - console.dir(res); - }); + qb.get_where('users', {username: 'foobar'}, function(err, res) { + qb.release(); + if (err) throw err; + console.dir(res); + }); }); ``` -------------- +-------------------------------------------------------------------------------- -### .last_query() +### .last_query() This is used to retrieve the query string that was most-recently executed. This MUST be called before closing the connection or releasing a connection back to the pool. This is useful for debugging what the `node-querybuilder` library is executing (or trying to execute). If you'd rather the engine not execute the query first, you can always use the appropriate [compilation methods](#compilation_methods) detailed below. @@ -1823,38 +1780,37 @@ If you'd rather the engine not execute the query first, you can always use the a var settings = require('db.json'); var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { - var id = 4531; - qb.get('comments', {id: id}, function(err, res) { - // SELECT * FROM `comments` WHERE `id` = 4531 - console.log(qb.last_query()); - qb.release(); - }); + var id = 4531; + qb.get('comments', {id: id}, function(err, res) { + // SELECT * FROM `comments` WHERE `id` = 4531 + console.log(qb.last_query()); + qb.release(); + }); }); ``` -------------- +-------------------------------------------------------------------------------- + ### .escape(value) -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| value | Mixed | Required | The value to escape based on your database driver | +Parameter | Type | Default | Description +:-------- | :---- | :------- | :------------------------------------------------ +value | Mixed | Required | The value to escape based on your database driver This can be used to excape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). -**What should happen:** -*Examples given are for MySQL* - -| Input Type | Output Type | Ex. Input | Ex. Output | -| :------------ | :-----------: | :---------------: | :---------------------------: | -| String | String | "\n\s\x1a" | "\\n\\s\\x1a" | -| Integer | String | 76 | '76' | -| Array | String | [1,2,3] | '1','2',3' | -| Date | String | new Date() | '2015-01-30 16:54:23.1856' | -| Buffer | String | new Buffer(1) | 'X\'00\'' | -| Object | String | {foo: 'bar', i: 3}| "`foo` = 'bar', `i` = 3" | +**What should happen:** _Examples given are for MySQL_ +Input Type | Output Type | Ex. Input | Ex. Output +:--------- | :---------: | :----------------: | :------------------------: +String | String | "\n\s\x1a" | "\\n\\s\\x1a" +Integer | String | 76 | '76' +Array | String | [1,2,3] | '1','2',3' +Date | String | new Date() | '2015-01-30 16:54:23.1856' +Buffer | String | new Buffer(1) | 'X\'00\'' +Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" **Example** @@ -1862,31 +1818,31 @@ This can be used to excape a value using your driver's native escape method. If var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; qb.query(sql, function(err, res) { - console.dir(res); + console.dir(res); }); ``` -------------- +-------------------------------------------------------------------------------- -### SQL Compilation Methods +### SQL Compilation Methods These methods can be used to build a query string without having to execute it. This is a fantastic option if you want to use the querybuilder to simply build queries and display the resulting string or to send the compiled query string off to a driver/engine other than the one offered by `node-querybuilder`. These are excellent educational tools and can be used like a SQL/NoSQL language rosetta stone of sorts. These methods are not asynchronous and, therefore, just return the compiled query string. -------------- +-------------------------------------------------------------------------------- -#### .get_compiled_select(table) -***Alias:*** *compile_select(table)* +#### .get_compiled_select(table) +**_Alias:_** _compile_select(table)_ -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +Parameter | Type | Default | Description +:-------- | :----- | :-------- | :---------------------------------------------------------- +table | String | Undefined | (optional) Used to avoid having to call .from() seperately. Compiles a SELECT-like query into a properly-escaped string. @@ -1898,25 +1854,25 @@ Get certain details of a user account var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = qb - .select(['id','username','first_name','last_name']) - .from('users') - .like('username','k','after') - .get_compiled_select(); + .select(['id','username','first_name','last_name']) + .from('users') + .like('username','k','after') + .get_compiled_select(); // SELECT `id`, `username`, `first_name`, `last_name` FROM `users` WHERE `username` LIKE 'k%' console.log(sql); ``` -------------- +-------------------------------------------------------------------------------- -#### .get_compiled_insert(table) -***Alias:*** *compile_insert(table)* +#### .get_compiled_insert(table) +**_Alias:_** _compile_insert(table)_ -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +Parameter | Type | Default | Description +:-------- | :----- | :-------- | :---------------------------------------------------------- +table | String | Undefined | (optional) Used to avoid having to call .from() seperately. Compiles a INSERT-like query into a properly-escaped string. @@ -1928,10 +1884,10 @@ Add a new user to a `users` table. var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var crypto = require('crypto'); var data = { - username: 'foobar', - password: crypto.createHash('sha1').update('password').digest('hex'), - first_name: 'Foo', - last_name: 'Bar' + username: 'foobar', + password: crypto.createHash('sha1').update('password').digest('hex'), + first_name: 'Foo', + last_name: 'Bar' }; var sql = qb.set(data).get_compiled_insert('users'); @@ -1940,16 +1896,16 @@ console.log(sql); }); ``` -------------- +-------------------------------------------------------------------------------- -#### .get_compiled_update(table) -***Alias:*** *compile_update(table)* +#### .get_compiled_update(table) +**_Alias:_** _compile_update(table)_ -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +Parameter | Type | Default | Description +:-------- | :----- | :-------- | :---------------------------------------------------------- +table | String | Undefined | (optional) Used to avoid having to call .from() seperately. Compiles an UPDATE-like query into a properly-escaped string. @@ -1961,27 +1917,27 @@ Update the password of a user var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var crypto = require('crypto'); var data = { - password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), + password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), }; var sql = qb - .where('id',4321) - .set(data) - .get_compiled_update('users'); + .where('id',4321) + .set(data) + .get_compiled_update('users'); // UPDATE `users` SET `password` = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' WHERE `id` = 4321 console.log(sql); ``` -------------- +-------------------------------------------------------------------------------- -#### .get_compiled_delete(table) -***Alias:*** *compile_delete(table)* +#### .get_compiled_delete(table) +**_Alias:_** _compile_delete(table)_ -| Parameter | Type | Default | Description | -| :-------- | :-------- | :----- | :------------------------------------------------------------ | -| table | String | Undefined | (optional) Used to avoid having to call .from() seperately. | +Parameter | Type | Default | Description +:-------- | :----- | :-------- | :---------------------------------------------------------- +table | String | Undefined | (optional) Used to avoid having to call .from() seperately. Compiles a SELECT-like query into a properly-escaped string. @@ -1998,9 +1954,7 @@ console.log(sql); }); ``` -------------- - -Contribute -========== +-------------------------------------------------------------------------------- +# Contribute Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request. diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index c4278ad..fb31b13 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1178,6 +1178,8 @@ var QueryBuilder = function() { var v = key[i]; if (typeof v === 'undefined') continue; + if (v instanceof Date) v = v.toString(); + if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); } @@ -1364,15 +1366,15 @@ var QueryBuilder = function() { get_where: function(table, where) { table = table || null; where = where || null; - + // Check if table is either a string or array if (typeof table !== 'string' && !Array.isArray(table)) throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - + // If table is a string, make sure it's not empty if (typeof table === 'string' && table.trim().length <= 0) throw new Error("Invalid table string specified!"); - + // If table is array, make sure there are only strings in there and that they are non-empty strings if (Array.isArray(table)) { for (var v in table) { @@ -1384,12 +1386,12 @@ var QueryBuilder = function() { } this.from(table); - + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - + this.where(where); - + return compile_select(this); }, diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index 9a30e66..e9f278b 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -3,100 +3,101 @@ var expect = require('chai').expect; var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('set()', function() { - it('should exist', function() { - should.exist(qb.set); - }); - it('should be a function', function() { - qb.set.should.be.a('function'); - }); - it('should have an object to put fields into', function() { - qb.should.have.property('set_array'); - }); - it('should have an empty array to put fields into at the beginning', function() { - qb.set_array.should.be.empty; - }); - it('should not accept anything but a non-empty string or a non-empty object as first param', function() { - qb.reset_query(); - expect(function() { qb.set(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.set(null); }, 'null provided').to.throw(Error); - expect(function() { qb.set(false); }, 'false provided').to.throw(Error); - expect(function() { qb.set(true); }, 'true provided').to.throw(Error); - expect(function() { qb.set({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.set(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.set(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.set(NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.set(Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.set([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.set([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.set(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.set(' '); }, 'string full of spaces provided').to.throw(Error); - expect(function() { qb.set(/foobar/); }, 'regex provided').to.throw(Error); + it('should exist', function() { + should.exist(qb.set); + }); + it('should be a function', function() { + qb.set.should.be.a('function'); + }); + it('should have an object to put fields into', function() { + qb.should.have.property('set_array'); + }); + it('should have an empty array to put fields into at the beginning', function() { + qb.set_array.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object as first param', function() { + qb.reset_query(); + expect(function() { qb.set(); }, 'nothing provided').to.throw(Error); + expect(function() { qb.set(null); }, 'null provided').to.throw(Error); + expect(function() { qb.set(false); }, 'false provided').to.throw(Error); + expect(function() { qb.set(true); }, 'true provided').to.throw(Error); + expect(function() { qb.set({}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.set(3); }, 'integer provided').to.throw(Error); + expect(function() { qb.set(3.5); }, 'float provided').to.throw(Error); + expect(function() { qb.set(NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.set(Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.set([]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.set([1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.set(''); }, 'empty string provided').to.throw(Error); + expect(function() { qb.set(' '); }, 'string full of spaces provided').to.throw(Error); + expect(function() { qb.set(/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.set('planet_position',3); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); - }); - it('should not accept anything but a string, number, null, or boolean as second param if first param is a string.', function() { - qb.reset_query(); - expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); - expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.set('planet_position',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.set('planet_position',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.set('planet_position',[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.set('planet_position',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.set('planet_position',/foobar/); }, 'regex provided').to.throw(Error); + expect(function() { qb.set('planet_position',3); }, 'valid string provided').to.not.throw(Error); + expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); + }); + it('should not accept anything but a string, number, date, null, or boolean as second param if first param is a string.', function() { + qb.reset_query(); + expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); + expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); + expect(function() { qb.set('planet_position',NaN); }, 'NaN provided').to.throw(Error); + expect(function() { qb.set('planet_position',Infinity); }, 'Infinity provided').to.throw(Error); + expect(function() { qb.set('planet_position',[]); }, 'empty array provided').to.throw(Error); + expect(function() { qb.set('planet_position',[1,2]); }, 'array of numbers provided').to.throw(Error); + expect(function() { qb.set('planet_position',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.set('planet_position',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',3); }, 'Integer provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',3.5); }, 'float provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',false); }, 'false provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',true); }, 'true provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',' '); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.set('planet_position','Three'); }, 'valid provided').to.not.throw(Error); - }); - it('should add first param (key) and second param (value) to hash and escape them properly', function() { - qb.reset_query(); - qb.set('galaxy_name','Milky Way'); - qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); - }); - it('should merge passed object into set_array and escape items properly', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way'}); - qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); + expect(function() { qb.set('planet_position',new Date()); }, 'date provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',null); }, 'null provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',3); }, 'Integer provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',3.5); }, 'float provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',false); }, 'false provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',true); }, 'true provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',''); }, 'empty string provided').to.not.throw(Error); + expect(function() { qb.set('planet_position',' '); }, 'string full of spaces provided').to.not.throw(Error); + expect(function() { qb.set('planet_position','Three'); }, 'non-empty string provided').to.not.throw(Error); + }); + it('should add first param (key) and second param (value) to hash and escape them properly', function() { + qb.reset_query(); + qb.set('galaxy_name','Milky Way'); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); + }); + it('should merge passed object into set_array and escape items properly', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); - qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}, {"`galaxy_class`": "'C'"}]); - }); - it('should not escape items if asked not to', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.set_array.should.eql([{galaxy_name: 'Milky Way'}]); - }); - it('should append more items to set_array as set() is called', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.set({galaxy_class: 'C'}, null, false); - qb.set('galaxy_size','D'); - qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); - }); - it('should be chainable', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); - qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); - }); - it('should overwrite values of keys that have been set already', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.set({galaxy_class: 'C'}); - qb.set('galaxy_class','D'); - qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'D'"}]); - }); - it('should NOT overwrite values of keys that are the same but have different escape flags', function() { - qb.reset_query(); - qb.set({galaxy_name: 'Milky Way'}, null, false); - qb.set({galaxy_class: 'C'}); - qb.set('galaxy_class','D', false); - qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'C'"}, {galaxy_class: 'D'}]); - }); + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); + qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}, {"`galaxy_class`": "'C'"}]); + }); + it('should not escape items if asked not to', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}]); + }); + it('should append more items to set_array as set() is called', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}, null, false); + qb.set('galaxy_size','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); + }); + it('should be chainable', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); + }); + it('should overwrite values of keys that have been set already', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'D'"}]); + }); + it('should NOT overwrite values of keys that are the same but have different escape flags', function() { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D', false); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'C'"}, {galaxy_class: 'D'}]); + }); }); From 05c1e6193cf078662a84c9b72ac5dcea098e966f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 12:09:36 -0400 Subject: [PATCH 098/200] Fixed build issues on Travis CI. --- test/mysql/tests-adapter.js | 65 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 0ec3c40..a8695a1 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -1,4 +1,3 @@ -var _ = require('underscore'); var should = require('chai').should(); var expect = require('chai').expect; var nqb = require('../../index.js'); @@ -24,7 +23,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { if (err) { console.error("Not connected!"); return; } console.log("connected!"); }; - var driver = 'mysql'; + var driver = 'mysql'; var settings = { host: '127.0.0.1', database: 'mock_db', @@ -32,13 +31,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { version: '2.5.4', port: 3306 }; - var bad_user = _.extend({},settings); bad_user.user = 'foobar'; - var bad_host = _.extend({},settings); bad_host.host = 'nonlocalhost'; - var bad_password = _.extend({},settings); bad_password.password = 'password'; - var bad_database = _.extend({},settings); bad_database.database = 'bad_mock_db'; - var bad_port = _.extend({},settings); bad_port.port = 1; - var bad_version = _.extend({},settings); bad_version.version = 12; - + var bad_user = Object.assign({},settings); bad_user.user = 'foobar'; + var bad_host = Object.assign({},settings); bad_host.host = 'nonlocalhost'; + var bad_password = Object.assign({},settings); bad_password.password = 'password'; + var bad_database = Object.assign({},settings); bad_database.database = 'bad_mock_db'; + var bad_port = Object.assign({},settings); bad_port.port = 1; + var bad_version = Object.assign({},settings); bad_version.version = 12; + it('should exist', function() { should.exist(nqb.QueryBuilder); }); @@ -46,22 +45,22 @@ describe('QueryBuilder() - MySQL Adapter', function() { nqb.QueryBuilder.should.be.a('function'); }); it('should have all the QueryBuilder methods', function() { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); var children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; expect(qb).to.include.keys(children); }); it('should have all the QueryExec methods', function() { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); var children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; expect(qb).to.include.keys(children); }); it('should have all the miscellaneous methods', function() { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); var children = ['last_query','escape','empty_table','truncate']; expect(qb).to.include.keys(children); }); it('should establish a single connection given valid connection credentials', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); expect(qb, 'should have connect property').to.have.property('connect'); qb.connect(function(err) { check(done, function() { @@ -70,14 +69,14 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to disconnect from MySQL', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { should.exist(qb.disconnect); qb.disconnect.should.be.a('function'); - + qb.disconnect(function(err) { var connection = qb.connection(); - + check(done, function() { expect(err, 'should be diconnected').to.not.be.instanceof(Error); expect(connection._protocol._ended).to.be.true; @@ -85,8 +84,8 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); }); - - + + it('should fail to establish a single connection given no parameters', function() { expect(function() { nqb.QueryBuilder(); }).to.throw(Error); }); @@ -170,15 +169,15 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); it('should fail to establish connection if an invalid driver version is specified', function() { var qb; - expect(function() { nqb.QueryBuilder( _.extend({}, settings), driver); }, 'valid driver version').to.not.throw(Error); + expect(function() { nqb.QueryBuilder( Object.assign({}, settings), driver); }, 'valid driver version').to.not.throw(Error); expect(function() { nqb.QueryBuilder(bad_version, driver); }, 'invalid driver version').to.throw(Error); }); - + it('should allow us to retrieve our connection settings for reference', function(done) { - var conn_settings = _.extend({}, settings, {password: undefined}); + var conn_settings = Object.assign({}, settings, {password: undefined}); delete conn_settings.version; - - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.connection_settings); @@ -191,7 +190,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to escape certain values', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.escape); @@ -206,7 +205,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to escape identifiers the MySQL way', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.escape_id); @@ -219,7 +218,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to execute a query', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", function(err, res) { check(done, function() { @@ -231,7 +230,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should not be able to release a non-pooled connection', function(done) { - var qb = nqb.QueryBuilder(_.extend({}, settings), driver); + var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { expect(function() { qb.release(); }).to.throw(Error); @@ -239,7 +238,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should create a connection pool object if asked', function() { - var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); expect(pool).to.be.instanceof.object; expect(pool).to.include.keys(['pool','get_connection','disconnect']); pool.pool.should.be.a('function'); @@ -247,8 +246,8 @@ describe('QueryBuilder() - MySQL Adapter', function() { pool.disconnect.should.be.a('function'); }); it('should create a QueryBuilder adapter when getting a connection from the pool', function(done) { - var qb2 = nqb.QueryBuilder(_.extend({}, settings), driver); - var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + var qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); + var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { check(done, function() { expect(qb).to.include.keys(Object.keys(qb2)); @@ -256,14 +255,14 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow one to release a connection from the pool', function(done) { - var qb2 = nqb.QueryBuilder(_.extend({}, settings), driver); - var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + var qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); + var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { check(done, function() { connection_released(qb); }); }); }); it('should allow one use the same connection pool connection for multiple queries', function(done) { - var pool = nqb.QueryBuilder(_.extend({}, settings), driver, 'pool'); + var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { qb.query('select * from `cities` where `city` = "Gainesville"', function(err, res) { From a38230caa6cc53a8d9de2e59308f3972ea62c3b8 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 12:15:36 -0400 Subject: [PATCH 099/200] Removed another reference to underscore. --- drivers/mysql/adapters.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 25c38e1..6330f29 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,7 +1,4 @@ var Adapters = function(nqb) { - - var _ = require('underscore'); - // Load MySQL Driver var mysql = require('mysql'); @@ -53,7 +50,7 @@ var Adapters = function(nqb) { delete nqb.settings.password // Merge any driver-specific settings into connection settings - that.connection_settings = _.extend(that.connection_settings, nqb.settings); + that.connection_settings = Object.assign(that.connection_settings, nqb.settings); } map_connection_settings(); @@ -110,7 +107,7 @@ var Adapters = function(nqb) { var qb = get_query_builder(); var qe = get_query_exec(qb, connection); - var adapter = _.extend({ + var adapter = Object.assign({ connection_settings: function() { return that.connection_settings; }, From 0cb397103c15d9b9d00490fb23a476b008c1aa50 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 12:18:40 -0400 Subject: [PATCH 100/200] Updated node version for Travis. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd8f5b8..35f2183 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ before_script: ./test/create_mock_dbs.sh language: node_js node_js: - - "0.10" + - "6.10.2" services: mysql test: adapter: mysql2 database: mock_db username: travis - encoding: utf8 \ No newline at end of file + encoding: utf8 From 931a9edc09126b9ce3281ac11c32c4d42c11fcb7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 12:26:45 -0400 Subject: [PATCH 101/200] 0.13.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 768a853..12cc03c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.12.0", + "version": "0.13.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 7cfc28af515602991abeebb7b3dd05725698ff82 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 17:43:00 -0400 Subject: [PATCH 102/200] Minor tweak. --- test/mysql/tests-like.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js index f93de08..de4ee16 100755 --- a/test/mysql/tests-like.js +++ b/test/mysql/tests-like.js @@ -19,7 +19,7 @@ describe('like()', function() { expect(function() { qb.like(3.5); }, 'float provided').to.throw(Error); expect(function() { qb.like([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.like(''); }, 'empty string provided').to.throw(Error); - + expect(function() { qb.like('planet_name','ear','after'); }, 'valid string').to.not.throw(Error); expect(function() { qb.like({planet_name: 'ear'}); }, 'valid object').to.not.throw(Error); }); @@ -34,7 +34,7 @@ describe('like()', function() { expect(function() { qb.like('planet_name',[]); }, 'empty array provided').to.throw(Error); expect(function() { qb.like('planet_name',NaN); }, 'empty array provided').to.throw(Error); expect(function() { qb.like('planet_name',Infinity);}, 'empty array provided').to.throw(Error); - + expect(function() { qb.like('planet_name',false); }, 'false provided').to.not.throw(Error); expect(function() { qb.like('planet_name',true); }, 'true provided').to.not.throw(Error); expect(function() { qb.like('planet_name',3); }, 'integer provided').to.not.throw(Error); @@ -54,7 +54,7 @@ describe('like()', function() { expect(function() { qb.like('galaxy_name','milk',3.5); }, 'float provided').to.throw(Error); expect(function() { qb.like('galaxy_name','milk',''); }, 'empty string provided').to.throw(Error); expect(function() { qb.like('galaxy_name','milk','foo'); }, 'non-empty string provided').to.throw(Error); - + expect(function() { qb.like('galaxy_name','milk'); }, 'no third param provided').to.not.throw(Error); expect(function() { qb.like('galaxy_name','milk','right'); }, 'right as third param').to.not.throw(Error); expect(function() { qb.like('galaxy_name','milk','left'); }, 'left as third param').to.not.throw(Error); @@ -66,7 +66,7 @@ describe('like()', function() { qb.reset_query(); qb.like('galaxy_name', 'milky', 'after'); qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - + qb.reset_query(); qb.like('galaxy_name', 'milky', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); @@ -75,7 +75,7 @@ describe('like()', function() { qb.reset_query(); qb.like('galaxy_name', 'milky', 'before'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - + qb.reset_query(); qb.like('galaxy_name', 'milky', 'left'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); @@ -84,7 +84,7 @@ describe('like()', function() { qb.reset_query(); qb.like('galaxy_name', 'milky'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - + qb.reset_query(); qb.like('galaxy_name', 'milky', 'both'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); @@ -185,4 +185,4 @@ describe('or_not_like()', function() { qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); }); -}); \ No newline at end of file +}); From 380bf061e180fd7c81bb00601805a6b4062f86c2 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Apr 2017 17:43:33 -0400 Subject: [PATCH 103/200] 0.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12cc03c..b6d5de2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.13.0", + "version": "0.14.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From ca4bf6dea6c484304b33d61b22b1d904c35a664d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 27 Apr 2017 16:58:08 -0400 Subject: [PATCH 104/200] Fixed and documented the join method's escape parameter. --- HISTORY.md | 4 ++++ README.md | 28 +++++++++++++++++++++++----- drivers/mysql/query_builder.js | 5 ++++- test/mysql/tests-join.js | 11 ++++++++--- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index cd90eda..a113a43 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -36,3 +36,7 @@ you spot any mistakes. * Added public `escape()` method * Added funtional `update_batch()` method. * Added `truncate()` and `empty_table()` methods. + +## v0.15.0 (2017-04-27) + +* Fixed and documented the escape property of the `join` method. diff --git a/README.md b/README.md index 6235547..ed6d52b 100755 --- a/README.md +++ b/README.md @@ -449,11 +449,12 @@ qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') #### .join(table,relation[,direction]) This SQL command is used query multiple tables related and connected by keys and get a single resultset. -Parameter | Type | Default | Description -:-------- | :----- | :------- | :-------------------------------------------------- -table | String | Required | The table or view to join to. -relation | String | Required | The "ON" statement that relates two tables together -direction | String | "left" | Direction of the join (see join types list below) +Parameter | Type | Default | Description +:-------- | :------ | :------- | :-------------------------------------------------- +table | String | Required | The table or view to join to. +relation | String | Required | The "ON" statement that relates two tables together +direction | String | "left" | Direction of the join (see join types list below) +escape | Boolean | true | TRUE: Escape table name and conditions; FALSE: No escaping **Join Types/Directions** - left @@ -503,6 +504,23 @@ qb.select(select).from('users u') .get(callback); ``` +If you have a very complex condition you can choose to forego escaping (not recommended unless +you know what you're doing). NOTE: Please make sure to escape values manually. + +```javascript +// SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type` +// FROM `users` `u` +// LEFT JOIN `user_meta` `um` ON +// CASE +// WHEN `u`.`id` = 4132 THEN `um`.`id` = `um`.`userId` +// WHEN `u`.`name` = 4132 THEN `um`.`name` = `u`.`id` +var select = ['u.id', 'u.name', 'um.name as user_name']; +var user_data = req.body; +qb.select(select).from('users u') + .join('`user_meta` `um`', 'CASE WHEN `u`.`id` = ' + user_data.id + ' THEN `um`.`id` = `um`.`userId` WHEN `u`.`name` = ' + user_data.id + ' THEN `um`.`name` = `u`.`id`', 'right outer', false) + .get(callback); +``` + -------------------------------------------------------------------------------- ### WHERE diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index fb31b13..0ab5506 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -270,7 +270,7 @@ var QueryBuilder = function() { var has_operator = function (str) { if(typeof str === 'string' && str.length > 0) { - var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i); + var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i); if(match === null) { return false; } @@ -833,6 +833,9 @@ var QueryBuilder = function() { else if (!has_operator(relation)) { relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } else { relation = ' '; } diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index 69a58bb..382343c 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -41,7 +41,7 @@ describe('join()', function() { it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', function() { var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; var invalid_direction = /Invalid join direction provided as third parameter/; - + expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); expect(function() { qb.join('universe','','left'); }, 'empty string 2nd param').to.throw(Error,invalid_2nd_param); @@ -57,7 +57,7 @@ describe('join()', function() { it('should allow valid join direction to be passed in third parameter', function() { // NOTE: A lot of this functionality was already tested when testing second param var invalid_direction = /Invalid join direction provided as third parameter/; - + expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); expect(function() { qb.join('universe','foo = bar',''); }, 'empty third param').to.not.throw(Error); @@ -105,4 +105,9 @@ describe('join()', function() { qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); }); -}); \ No newline at end of file + it('should NOT escape any part of join query when asked not to', function() { + qb.reset_query(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left', false); + qb.join_array.should.eql(["LEFT JOIN star_system s ON s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'"]); + }); +}); From c766b2918d56fd669dfc512ef709d0890877b873 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 27 Apr 2017 16:58:18 -0400 Subject: [PATCH 105/200] 0.15.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6d5de2..74877cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.14.0", + "version": "0.15.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 7d46469caf54a1b3d38e93008c8abf97662d9ef9 Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 07:03:04 -0400 Subject: [PATCH 106/200] Changed .match to .test for better preformance (and because in those cases, it's only checking for a boolean and doesn't care that .match returns an array --- drivers/mysql/query_builder.js | 55 ++++++++++++++++------------------ 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 0ab5506..b52157f 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -15,13 +15,13 @@ var QueryBuilder = function() { type = type || 'limit'; type = type.toLowerCase(); - if ((typeof item).match(/^(string|number)$/) === null) { + if (!/^(string|number)$/.test(typeof item)) { throw new Error("Only integers or integers in the form of a string are allowed"); } if (typeof item === 'string') { item = item.trim(); - if (item.match(/^\d+$/) === null) { + if (!/^\d+$/.test(item)) { throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); } // Force to an integer @@ -160,7 +160,7 @@ var QueryBuilder = function() { } return item; } - else if ((typeof item === 'string' && item.match(/^\d+$/)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { return item; } @@ -216,7 +216,7 @@ var QueryBuilder = function() { // If the item has an alias declaration we remove it and set it aside. // Basically we remove everything to the right of the first space - if (item.match(/\sAS\s/ig)) { + if (/\sAS\s/ig.test(item)) { var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); item = item.substr(0,alias_index); @@ -270,8 +270,8 @@ var QueryBuilder = function() { var has_operator = function (str) { if(typeof str === 'string' && str.length > 0) { - var match = str.trim().match(/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i); - if(match === null) { + var match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); + if(!match) { return false; } } @@ -284,7 +284,7 @@ var QueryBuilder = function() { if (typeof str === 'boolean') { str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && str.match(/^\d+$/))) { + } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str))) { str *= 1; } else { str = do_escape(str); @@ -539,7 +539,7 @@ var QueryBuilder = function() { // If it's a actual where clause string (with no paranthesis), // not just a field name, split it into individual parts to escape it properly - if (key.match(/(<=|>=|<>|>|<|!=|=)/) && key.indexOf('(') === -1 && escape === true) { + if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { var filters = key.split(/\s+(AND|OR)\s+/i); if (filters.length > 1) { var that = this; @@ -548,7 +548,7 @@ var QueryBuilder = function() { if (parsed.length >= 4) { var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || joiner.match(/AND/i)) { + if (joiner === null || /AND/i.test(joiner)) { that.where(key, value, true); } else { that.or_where(key, value, true); @@ -682,11 +682,8 @@ var QueryBuilder = function() { return this._like(field, match, 'OR ', side, ' NOT'); }, - _like: function(field, match, type, side, not) { - match = ((typeof match).match(/^(string|number|boolean)$/) !== null ? match : null); - type = type || 'AND '; - side = (typeof side === 'undefined' ? 'both' : side); - not = not || ''; + _like: function(field, match, type='AND ', side='both', not='') { + match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); if (typeof field === 'string' && field.length == 0) { throw new Error("like(): The field you provided is empty."); @@ -694,7 +691,7 @@ var QueryBuilder = function() { else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { throw new Error("like(): The object you provided is empty."); } - else if ((typeof field).match(/^(string|object)$/) === null) { + else if (!/^(string|object)$/.test(typeof field)) { throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); } @@ -714,7 +711,7 @@ var QueryBuilder = function() { var k = protect_identifiers(this,k.trim()); // Make sure value is only string, number, or boolean - if ((typeof v).match(/^(string|number|boolean)$/) === null) { + if (!/^(string|number|boolean)$/.test(typeof v)) { throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } // If number, don't allow Infinity or NaN @@ -826,7 +823,7 @@ var QueryBuilder = function() { } // Split apart the condition and protect the identifiers - else if (escape === true && relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i)) { + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); } @@ -1011,8 +1008,8 @@ var QueryBuilder = function() { var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); - if ((typeof value).match(/^(string|number|boolean)$/) !== null) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || key.match(/^\W+$/i)) { // if the key is not a string... + if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); } key_array[key] = value; @@ -1092,7 +1089,7 @@ var QueryBuilder = function() { throw new Error("You haven't provided any fields to order by!!"); } orderby = orderby.split(','); - } else if (!orderby && direction.match(/(random|RAND|RAND\(\))/i)) { + } else if (!orderby && /(random|RAND|RAND\(\))/i.test(direction)) { this.order_by_array.push(rand_word); return this; } @@ -1115,7 +1112,7 @@ var QueryBuilder = function() { } orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; } else { - if (direction.match(/^(ASC|DESC)$/i) || direction === '') { + if (/^(ASC|DESC)$/i.test(direction) || direction === '') { orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; } else { throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); @@ -1183,7 +1180,7 @@ var QueryBuilder = function() { if (v instanceof Date) v = v.toString(); - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { @@ -1228,7 +1225,7 @@ var QueryBuilder = function() { ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { throw new Error("insert(): Invalid data provided to insert into database!"); } @@ -1248,7 +1245,7 @@ var QueryBuilder = function() { table = table.trim(); - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } @@ -1282,7 +1279,7 @@ var QueryBuilder = function() { table = table.trim(); - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } @@ -1309,7 +1306,7 @@ var QueryBuilder = function() { for (var i in row) { var v = row[i]; - if ((typeof v).match(/^(number|string|boolean)$/) === null && v !== null) { + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { throw new Error("set(): Invalid value provided!"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { @@ -1432,7 +1429,7 @@ var QueryBuilder = function() { } // If set is a number, boolean, a non-empty string, or regex, fail - if ((typeof set).match(/^(number|boolean)$/) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { throw new Error("update(): Invalid data provided to update database!"); } @@ -1458,7 +1455,7 @@ var QueryBuilder = function() { table = table.trim(); // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { throw new Error("update(): You have not set any tables to update!"); } @@ -1533,7 +1530,7 @@ var QueryBuilder = function() { table = table.trim(); // Table name must be in a legitimate format - if (table !== '' && !table.match(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/)) { + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { throw new Error("update(): You have not set any tables to update!"); } From 355815d3be8d16e58d7033dae38416be260026c6 Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 07:04:39 -0400 Subject: [PATCH 107/200] Changed many instances of stuff like parameterVar = parameterVar || null in favor of default parameters (es6) --- drivers/mysql/query_builder.js | 54 ++++++++++++---------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index b52157f..d0e6922 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -11,8 +11,8 @@ var QueryBuilder = function() { return values; }; - var prepare_for_limit_and_offset = function(item,type) { - type = type || 'limit'; + var prepare_for_limit_and_offset = function(item, type = 'limit') { + type = type.toLowerCase(); if (!/^(string|number)$/.test(typeof item)) { @@ -149,8 +149,8 @@ var QueryBuilder = function() { return item; }; - var escape_identifiers = function(item) { - if (!item || item === '*') { + var escape_identifiers = function(item = '*') { + if (item === '*') { return item; } @@ -427,10 +427,9 @@ var QueryBuilder = function() { return build_limit_clause(sql, limit_to, offset_val); }; - var compile_insert = function(qb, ignore, suffix) { + var compile_insert = function(qb, ignore, suffix='') { var keys = []; var values = []; - suffix = suffix || ''; for (var i in qb.set_array) { var key = Object.keys(qb.set_array[i])[0]; @@ -496,12 +495,11 @@ var QueryBuilder = function() { } }, - where: function(key, value, escape) { + where: function(key, value = null, escape) { if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { escape = (typeof escape === 'boolean' ? escape : value); } - value = (typeof value === 'undefined' ? null : value); escape = (typeof escape === 'boolean' ? escape : true); if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { @@ -510,9 +508,8 @@ var QueryBuilder = function() { return this._where(key, value, 'AND ', escape); }, - or_where: function(key, value, escape) { + or_where: function(key, value=null, escape) { escape = (typeof escape === 'boolean' ? escape : true); - value = value || null; if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { return this._where_in(key, value, false, 'OR '); @@ -520,9 +517,7 @@ var QueryBuilder = function() { return this._where(key, value, 'OR ', escape); }, - _where: function(key, value, type, escape) { - value = (typeof value === 'undefined' ? null : value); - type = type || 'AND '; + _where: function(key, value=null, type='AND ', escape) { escape = (typeof escape === 'boolean' ? escape : true); // Must be an object or a string @@ -632,10 +627,7 @@ var QueryBuilder = function() { return this._where_in(key,values,true,'OR ', escape); }, - _where_in: function(key, values, not, type, escape) { - key = key || ''; - values = values || []; - type = type || 'AND '; + _where_in: function(key='', values=[], not, type='AND ', escape) { not = (not ? ' NOT' : ''); escape = (typeof escape === 'boolean' ? escape : true); @@ -918,10 +910,7 @@ var QueryBuilder = function() { return this._min_max_avg_sum(select,alias,'SUM'); }, - _min_max_avg_sum: function(select,alias,type) { - select = select || ''; - alias = alias || ''; - type = type || 'MAX'; + _min_max_avg_sum: function(select='',alias='',type='MAX') { if (typeof select !== 'string' || select === '') { throw Error("Invalid query!"); @@ -1000,8 +989,7 @@ var QueryBuilder = function() { return this._having(key, value, 'OR ', escape); }, - _having: function(key, value, type, escape) { - type = type || 'AND '; + _having: function(key, value, type='AND ', escape) { var m; var key_array = {}; @@ -1221,7 +1209,7 @@ var QueryBuilder = function() { }, insert: function(table, set, ignore, suffix) { - table = table || ''; + table = table || '' ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); @@ -1265,10 +1253,9 @@ var QueryBuilder = function() { return this.insert(table, set, true, suffix); }, - insert_batch: function(table,set,ignore,suffix) { + insert_batch: function(table,set=null,ignore,suffix) { var self = this; var orig_table = table = table || ''; - set = set || null; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); if (suffix == ' ') suffix = ''; @@ -1363,9 +1350,7 @@ var QueryBuilder = function() { return compile_select(this); }, - get_where: function(table, where) { - table = table || null; - where = where || null; + get_where: function(table=null, where=null) { // Check if table is either a string or array if (typeof table !== 'string' && !Array.isArray(table)) @@ -1408,10 +1393,10 @@ var QueryBuilder = function() { return sql; }, - update: function(table, set, where) { + update: function(table, set, where=null) { + table = table || ''; set = set || null; - where = where || null; // Send to batch_update if the data param is an array if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { @@ -1478,11 +1463,8 @@ var QueryBuilder = function() { return compile_update(this); }, - update_batch: function(table, set, index, where) { - table = table || ''; - set = set || null; - index = index || null; - where = where || null; + update_batch: function(table='', set=null, index=null, where=null) { + // Make sure an index has been provided! if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { From 874b3c1680723c459b69a0df1836df7678c91ac2 Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 07:46:03 -0400 Subject: [PATCH 108/200] Traded var for let or const to make scoping clearer --- drivers/mysql/query_builder.js | 363 ++++++++++++++++----------------- 1 file changed, 181 insertions(+), 182 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d0e6922..efe4d9c 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,17 +1,17 @@ -var QueryBuilder = function() { +const QueryBuilder = function() { // ------------------------------ GENERIC FUNCTIONS ------------------------------// - var array_values = function(item) { - var keys = Object.keys(item); - var length = keys.length; - var values = Array(length); - for (var i = 0; i < length; i++) { + const array_values = function(item) { + const keys = Object.keys(item); + const length = keys.length; + const values = Array(length); + for (const i = 0; i < length; i++) { values[i] = item[keys[i]]; } return values; }; - var prepare_for_limit_and_offset = function(item, type = 'limit') { + const prepare_for_limit_and_offset = function(item, type = 'limit') { type = type.toLowerCase(); @@ -52,13 +52,13 @@ var QueryBuilder = function() { return item; } - var extract_having_parts = function(key,key_array) { - var m; + const extract_having_parts = function(key,key_array) { + let m; key = key.trim().replace(/\s+/g,' '); - var str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - var sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - var num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - var bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; + const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + const sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + const num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + const bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; if (m = str_condition.exec(key)) { key_array[m[1]] = m[3]; @@ -87,13 +87,13 @@ var QueryBuilder = function() { } // Simply setting all properties to [] causes reference issues in the parent class. - var clear_array = function(a,debug) { + const clear_array = function(a,debug) { if (debug === true) { console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); console.dir(a); } if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (var key in a) { + for (const key in a) { if (a.hasOwnProperty(key)) { delete a[key]; } @@ -111,10 +111,10 @@ var QueryBuilder = function() { }; // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - var track_aliases = function(qb,table) { + const track_aliases = function(qb,table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (var i in table) { - var t = table[i]; + for (const i in table) { + const t = table[i]; track_aliases(qb,t); } return; @@ -132,7 +132,7 @@ var QueryBuilder = function() { table = table.replace(/\s+AS\s+/gi, ' '); // Grab the alias - var alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); + const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist if(qb.aliased_tables.indexOf(alias) == -1) { @@ -141,7 +141,7 @@ var QueryBuilder = function() { } }; - var create_aliases_from_table = function(item) { + const create_aliases_from_table = function(item) { if (item.indexOf('.') !== -1) { return item.split('.').reverse()[0]; } @@ -149,13 +149,13 @@ var QueryBuilder = function() { return item; }; - var escape_identifiers = function(item = '*') { + const escape_identifiers = function(item = '*') { if (item === '*') { return item; } if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (var i in item) { + for (const i in item) { item[i] = escape_identifiers(item[i]); } return item; @@ -164,7 +164,7 @@ var QueryBuilder = function() { return item; } - var str; + let str; if (item.indexOf('.' + '*') !== -1) { str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); } @@ -176,16 +176,16 @@ var QueryBuilder = function() { return str.replace(/[`]+/g,'`'); }; - var protect_identifiers = function(qb,item,protect_identifiers) { + const protect_identifiers = function(qb,item,protect_identifiers) { if (item === '') return item; protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - var escaped_array = {}; + const escaped_array = {}; for (k in item) { - var v = item[k]; + const v = item[k]; escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); } @@ -199,8 +199,8 @@ var QueryBuilder = function() { // If a parenthesis is found we know that we do not need to // escape the data or add a prefix. if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - var has_alias = item.lastIndexOf(')'); - var alias; + const has_alias = item.lastIndexOf(')'); + let alias; if (has_alias >= 0) { alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); alias = escape_identifiers(alias); @@ -214,37 +214,36 @@ var QueryBuilder = function() { return item + alias; } + let alias = ''; + // If the item has an alias declaration we remove it and set it aside. // Basically we remove everything to the right of the first space if (/\sAS\s/ig.test(item)) { - var alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - var alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); item = item.substr(0,alias_index); } else if (item.indexOf(' ') !== -1) { - var alias_index = item.indexOf(' '); + const alias_index = item.indexOf(' '); - var alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); item = item.substr(0,alias_index); } - else { - var alias = ''; - } // Break the string apart if it contains periods, then insert the table prefix // in the correct location, assuming the period doesn't indicate that we're dealing // with an alias. While we're at it, we will escape the components if (item.indexOf('.') !== -1) { - var parts = item.split('.'); - var first_seg = parts[0].trim().replace(/`/g,''); + const parts = item.split('.'); + const first_seg = parts[0].trim().replace(/`/g,''); // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item if (qb.aliased_tables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { - for (var key in parts) { - var val = parts[key]; + for (const key in parts) { + const val = parts[key]; if (val !== '*') { parts[key] = escape_identifiers(val); } @@ -268,9 +267,9 @@ var QueryBuilder = function() { return item + alias; }; - var has_operator = function (str) { + const has_operator = function (str) { if(typeof str === 'string' && str.length > 0) { - var match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); + const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); if(!match) { return false; } @@ -278,9 +277,9 @@ var QueryBuilder = function() { return true; }; - var qb_escape = function(qb,str) { - var SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); - var do_escape = SqlString.escape; + const qb_escape = function(qb,str) { + const SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); + const do_escape = SqlString.escape; if (typeof str === 'boolean') { str = (str === false ? 0 : 1); @@ -295,8 +294,8 @@ var QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// - var build_where_clause = function(qb) { - var sql = ''; + const build_where_clause = function(qb) { + let sql = ''; if(qb.where_array.length > 0) { sql += " WHERE "; } @@ -304,8 +303,8 @@ var QueryBuilder = function() { return sql; }; - var build_from_clause = function(qb) { - var sql = ''; + const build_from_clause = function(qb) { + let sql = ''; if(qb.from_array.length > 0) { sql += " FROM "; } else { @@ -315,39 +314,39 @@ var QueryBuilder = function() { return sql; }; - var build_join_string = function(qb) { - var sql = ''; + const build_join_string = function(qb) { + let sql = ''; sql += qb.join_array.join(' '); if(sql.length > 0) sql = ' ' + sql; return sql; }; - var build_group_by_clause = function(qb) { + const build_group_by_clause = function(qb) { if (qb.group_by_array.length <= 0) return ''; - var sql = ' GROUP BY '; + let sql = ' GROUP BY '; sql += qb.group_by_array.join(', '); return sql; }; - var build_having_clause = function(qb) { + const build_having_clause = function(qb) { if (qb.having_array.length <= 0) return ''; - var sql = ' HAVING '; + let sql = ' HAVING '; sql += qb.having_array.join(' '); return sql; }; - var build_order_by_clause = function(qb) { + const build_order_by_clause = function(qb) { if (qb.order_by_array.length <= 0) return ''; - var sql = ' ORDER BY '; + let sql = ' ORDER BY '; sql += qb.order_by_array.join(', '); return sql; }; - var build_limit_clause = function(sql, limit, offset) { + const build_limit_clause = function(sql, limit, offset) { if (!limit) return sql; sql += ' '; @@ -361,9 +360,9 @@ var QueryBuilder = function() { return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - var compile_select = function(qb) { - var distinct_clause = qb.distinct_clause[0] || ''; - var sql = 'SELECT ' + distinct_clause; + const compile_select = function(qb) { + const distinct_clause = qb.distinct_clause[0] || ''; + let sql = 'SELECT ' + distinct_clause; if (qb.select_array.length === 0) { sql += '*'; } else { @@ -377,14 +376,14 @@ var QueryBuilder = function() { + build_having_clause(qb) + build_order_by_clause(qb); - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; sql = build_limit_clause(sql,limit_to,offset_val); return sql; }; - var compile_delete = function(qb) { + const compile_delete = function(qb) { if (qb.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); return ''; @@ -392,24 +391,22 @@ var QueryBuilder = function() { qb.from_array = qb.from_array.slice(0,1); - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; - var sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); + const sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); return build_limit_clause(sql,limit_to,offset_val); }; - var compile_update = function(qb) { - var valstr = []; - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; + const compile_update = function(qb) { + const valstr = []; + for (const i in qb.set_array) { + const key = Object.keys(qb.set_array[i])[0]; + const val = qb.set_array[i][key]; valstr.push(key + ' = ' + val); } - if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); - } else { + if (qb.from_array.length !== 1) { if (qb.from_array.length === 0) { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; @@ -418,31 +415,33 @@ var QueryBuilder = function() { return ''; } - var limit_to = qb.limit_to[0] || false; - var offset_val = qb.offset_val[0] || false; + const table = qb.from_array.toString(); + + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; - var sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); + let sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); sql += build_where_clause(qb); sql += build_order_by_clause(qb); return build_limit_clause(sql, limit_to, offset_val); }; - var compile_insert = function(qb, ignore, suffix='') { - var keys = []; - var values = []; + const compile_insert = function(qb, ignore, suffix='') { + const keys = []; + const values = []; - for (var i in qb.set_array) { - var key = Object.keys(qb.set_array[i])[0]; - var val = qb.set_array[i][key]; + for (const i in qb.set_array) { + const key = Object.keys(qb.set_array[i])[0]; + const val = qb.set_array[i][key]; keys.push(key); values.push(val); } - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); if (qb.from_array.length === 1) { - var table = qb.from_array.toString(); + const table = qb.from_array.toString(); } else { if (qb.from_array.length === 0) { throw new Error("You haven't provided any tables to build INSERT querty with!"); @@ -535,14 +534,14 @@ var QueryBuilder = function() { // If it's a actual where clause string (with no paranthesis), // not just a field name, split it into individual parts to escape it properly if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { - var filters = key.split(/\s+(AND|OR)\s+/i); + const filters = key.split(/\s+(AND|OR)\s+/i); if (filters.length > 1) { - var that = this; - var parse_statement = function(statement,joiner) { - var parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + const that = this; + const parse_statement = function(statement,joiner) { + const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); if (parsed.length >= 4) { - var key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - var value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); if (joiner === null || /AND/i.test(joiner)) { that.where(key, value, true); } else { @@ -552,15 +551,15 @@ var QueryBuilder = function() { }; parse_statement(filters.shift(),null); while (filters.length > 0) { - var joiner = filters.shift(); - var statement = filters.shift(); + const joiner = filters.shift(); + const statement = filters.shift(); parse_statement(statement, joiner); } return this; } } - var key_array = {}; + const key_array = {}; key_array[key] = value; key = key_array; } @@ -569,14 +568,14 @@ var QueryBuilder = function() { throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); } - for (var k in key) { - var v = key[k]; + for (let k in key) { + let v = key[k]; if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { return this._where_in(k,v,false,type,escape); } - var prefix = (this.where_array.length == 0 ? '' : type); + const prefix = (this.where_array.length == 0 ? '' : type); if (v === null && !has_operator(k)) { k += ' IS NULL'; @@ -645,12 +644,12 @@ var QueryBuilder = function() { } } - for (var i in values) { + for (const i in values) { this.where_in_array.push(qb_escape(this,values[i])); } - var prefix = (this.where_array.length == 0 ? '' : type); - var where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + const prefix = (this.where_array.length == 0 ? '' : type); + const where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; this.where_array.push(where_in); // reset the array for multiple calls @@ -692,15 +691,15 @@ var QueryBuilder = function() { throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } - var field_array = {}; + const field_array = {}; field_array[field] = match; field = field_array; } - for(k in field) { - var like_statement; - var v = field[k]; - var k = protect_identifiers(this,k.trim()); + for(let k in field) { + let like_statement; + const v = field[k]; + k = protect_identifiers(this,k.trim()); // Make sure value is only string, number, or boolean if (!/^(string|number|boolean)$/.test(typeof v)) { @@ -737,15 +736,15 @@ var QueryBuilder = function() { if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { from = [from]; } - for (var i in from) { - var val = from[i]; + for (const i in from) { + let val = from[i]; if (val.trim() === '') continue; if (val.indexOf(',') !== -1) { - var objects = val.split(','); - for (var j in objects) { - var v = objects[j].trim(); + const objects = val.split(','); + for (const j in objects) { + const v = objects[j].trim(); track_aliases(this,v); @@ -775,7 +774,7 @@ var QueryBuilder = function() { direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); escape = (typeof escape === 'boolean' ? escape : true); - var valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + const valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; if (direction != '') { direction = direction.toUpperCase().trim(); @@ -792,19 +791,19 @@ var QueryBuilder = function() { track_aliases(this,table); // Split multiple conditions - var regex = /\sAND\s|\sOR\s/ig; - var m = relation.match(regex); - var matches = []; - var k, temp, temp_match, match; + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; if (escape === true && m) { while(k = regex.exec(relation)) { matches.push(k); } - var new_relation = ''; + let new_relation = ''; matches.push(['']); matches[(matches.length - 1)].index = relation.length; - for (var j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { temp = relation.substr(s, matches[j].index - s); temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); @@ -834,7 +833,7 @@ var QueryBuilder = function() { table = protect_identifiers(this,table,true); } - var join = direction + 'JOIN ' + table + relation; + const join = direction + 'JOIN ' + table + relation; this.join_array.push(join); return this; @@ -865,8 +864,8 @@ var QueryBuilder = function() { select = select.split(','); } else { if (escape === true) { - var m, open_paren_index, inner_parenthesis; - var reg = /\)/g; + let m, open_paren_index, inner_parenthesis; + const reg = /\)/g; while ((m = reg.exec(select) !== null)) { open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); if (open_paren_index !== -1) { @@ -884,8 +883,8 @@ var QueryBuilder = function() { } } - for (var i in select) { - var val = select[i].trim(); + for (const i in select) { + const val = select[i].trim(); if(val !== '') { this.select_array.push(protect_identifiers(this,val,escape)); @@ -928,7 +927,7 @@ var QueryBuilder = function() { alias = create_aliases_from_table(select.trim()); } - var sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + const sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; this.select_array.push(sql); @@ -965,12 +964,12 @@ var QueryBuilder = function() { throw new Error("You haven't provided any fields to group by!"); } - for (var key in by) { + for (const key in by) { if (typeof by[key] !== 'string') { throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); } - var val = by[key].trim(); + const val = by[key].trim(); if (val !== '') { this.group_by_array.push(protect_identifiers(this,val)); @@ -991,10 +990,10 @@ var QueryBuilder = function() { _having: function(key, value, type='AND ', escape) { - var m; - var key_array = {}; - var key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - var key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); + let m; + let key_array = {}; + const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + const key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... @@ -1016,7 +1015,7 @@ var QueryBuilder = function() { } else if (key_is_array === true) { //console.log("Key is NOT a string"); - for (var i in key) { + for (const i in key) { if (typeof key[i] !== 'string') { throw new Error("having(): You've provided an unparseable format to the having() method.."); } @@ -1033,9 +1032,9 @@ var QueryBuilder = function() { } - for (var k in key) { - var v = key[k]; - var prefix = (this.having_array.length == 0 ? '' : type); + for (let k in key) { + let v = key[k]; + const prefix = (this.having_array.length == 0 ? '' : type); if (escape === true) { k = protect_identifiers(this,k); @@ -1059,8 +1058,8 @@ var QueryBuilder = function() { }, order_by: function(orderby, direction) { - var m; - var rand_word = 'RAND()'; + let m; + const rand_word = 'RAND()'; direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); // Don't need to do anything below if the direction provided is random @@ -1077,7 +1076,7 @@ var QueryBuilder = function() { throw new Error("You haven't provided any fields to order by!!"); } orderby = orderby.split(','); - } else if (!orderby && /(random|RAND|RAND\(\))/i.test(direction)) { + } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { this.order_by_array.push(rand_word); return this; } @@ -1090,7 +1089,7 @@ var QueryBuilder = function() { throw new Error("You haven't provided any fields to order by!"); } - for (var i in orderby) { + for (const i in orderby) { orderby[i] = orderby[i].replace(/\s+/g, ' '); if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { @@ -1143,7 +1142,7 @@ var QueryBuilder = function() { if (typeof value === 'undefined') throw new Error("set(): First param was string but no value (second param) provided to set!"); - var key_array = {}; + const key_array = {}; key_array[key] = value; key = key_array; } @@ -1162,8 +1161,8 @@ var QueryBuilder = function() { // Add each key:value pair to the set_array - for (var i in key) { - var v = key[i]; + for (const i in key) { + let v = key[i]; if (typeof v === 'undefined') continue; if (v instanceof Date) v = v.toString(); @@ -1176,10 +1175,10 @@ var QueryBuilder = function() { } // Escape the key to be DRY - var escaped_key = protect_identifiers(this,i,escape); + const escaped_key = protect_identifiers(this,i,escape); // Build a temporary object with escaped key and val - var temp = {}; + const temp = {}; if (escape === false) { temp[escaped_key] = v; } else { @@ -1187,8 +1186,8 @@ var QueryBuilder = function() { } // Determine if this key has already been set - var found_index = null; - for (var j in this.set_array) { + let found_index = null; + for (const j in this.set_array) { if (this.set_array[j].hasOwnProperty(escaped_key)) { found_index = j; break; @@ -1254,8 +1253,8 @@ var QueryBuilder = function() { }, insert_batch: function(table,set=null,ignore,suffix) { - var self = this; - var orig_table = table = table || ''; + const self = this; + const orig_table = table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); if (suffix == ' ') suffix = ''; @@ -1284,14 +1283,14 @@ var QueryBuilder = function() { throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); } - for (var key in set) { - var row = set[key]; - var is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + for (const key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); if (!is_object || (is_object && Object.keys(row).length === 0)) { throw new Error('insert_batch(): An invalid item was found in the data array!'); } else { - for (var i in row) { - var v = row[i]; + for (const i in row) { + const v = row[i]; if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { throw new Error("set(): Invalid value provided!"); @@ -1307,11 +1306,11 @@ var QueryBuilder = function() { return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); } - var map = []; - var columns = []; + const map = []; + const columns = []; // Obtain all the column names - for (var key in set[0]) { + for (const key in set[0]) { if (set[0].hasOwnProperty(key)) { if (columns.indexOf(key) == -1) { columns.push(protect_identifiers(this,key)); @@ -1319,10 +1318,10 @@ var QueryBuilder = function() { } } - for (var i = 0; i < set.length; i++) { + for (let i = 0; i < set.length; i++) { (function(i,qb) { - var row = []; - for (var key in set[i]) { + const row = []; + for (const key in set[i]) { if (set[i].hasOwnProperty(key)) { row.push(qb_escape(qb,set[i][key])); } @@ -1334,7 +1333,7 @@ var QueryBuilder = function() { })(i,self); } - var verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; }, @@ -1362,7 +1361,7 @@ var QueryBuilder = function() { // If table is array, make sure there are only strings in there and that they are non-empty strings if (Array.isArray(table)) { - for (var v in table) { + for (const v in table) { if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { throw new Error("Invalid table string specified in array of tables!"); break; @@ -1385,7 +1384,7 @@ var QueryBuilder = function() { this.from(table); } - var sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + const sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + build_from_clause(this) + build_join_string(this) + build_where_clause(this); @@ -1400,7 +1399,7 @@ var QueryBuilder = function() { // Send to batch_update if the data param is an array if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { - var index = null; + let index = null; if (set.length > 0) { if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { index = Object.keys(set[0])[0]; @@ -1483,13 +1482,13 @@ var QueryBuilder = function() { // Make sure each item in the dataset has the specified index and then add data to set_array //console.dir(set); - for (var i in set) { - var clean = {}; - var row = set[i]; + for (const i in set) { + const clean = {}; + const row = set[i]; if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - var keys = Object.keys(row); + const keys = Object.keys(row); if (keys.indexOf(index) !== -1) { - for (var j in row) { + for (const j in row) { clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); } this.set_array.push(clean); @@ -1533,32 +1532,32 @@ var QueryBuilder = function() { } // Verify there is a table in the from_array - if (this.from_array.length === 1) { - var table = this.from_array.toString(); - } else { + if (this.from_array.length !== 1) { if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build batch UPDATE query with!"); } throw new Error("You have provided too many tables to build batch UPDATE query with!"); } + table = this.from_array.toString(); + // Limit to 100 rows per batch - var batches = []; - for (var i = 0, total = this.set_array.length; i < total; i += 100) { - var when_then = {}; - var ids = []; - var where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); - var chunk = this.set_array.slice(i,100); + const batches = []; + for (let i = 0, total = this.set_array.length; i < total; i += 100) { + const when_then = {}; + const ids = []; + const where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); + const chunk = this.set_array.slice(i,100); // Escape the index index = protect_identifiers(this, index); - for (var j in chunk) { + for (const j in chunk) { ids.push(chunk[j][index]); - var keys = Object.keys(chunk[j]); - for (var k in keys) { + const keys = Object.keys(chunk[j]); + for (const k in keys) { if (keys[k] != index) { if (!when_then.hasOwnProperty(keys[k])) { when_then[keys[k]] = []; @@ -1569,13 +1568,13 @@ var QueryBuilder = function() { } // Build the actual SQL statement - var sql = 'UPDATE (' + table + ') SET '; - var cases = ''; + let sql = 'UPDATE (' + table + ') SET '; + let cases = ''; - for (var l in when_then) { + for (const l in when_then) { cases += l + ' = CASE '; - for (var m in when_then[l]) { + for (const m in when_then[l]) { cases += when_then[l][m]; } From e8bb2090d95c0eddaf5cb9d06898c54ca5480afd Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 07:51:00 -0400 Subject: [PATCH 109/200] Traded var for const when variable equals function or require --- README.md | 88 ++++++++++++------------- drivers/mysql/adapters.js | 20 +++--- drivers/mysql/query_exec.js | 4 +- examples/connection_pool_example.js | 2 +- examples/connection_single_example.js | 2 +- index.js | 66 +++++++++---------- test/mysql/tests-adapter.js | 14 ++-- test/mysql/tests-compilation_methods.js | 6 +- test/mysql/tests-count.js | 6 +- test/mysql/tests-delete.js | 14 ++-- test/mysql/tests-distinct.js | 6 +- test/mysql/tests-empty_table.js | 12 ++-- test/mysql/tests-from.js | 16 ++--- test/mysql/tests-general.js | 6 +- test/mysql/tests-get.js | 22 +++---- test/mysql/tests-groupby.js | 10 +-- test/mysql/tests-having.js | 20 +++--- test/mysql/tests-insert.js | 8 +-- test/mysql/tests-insert_batch.js | 22 +++---- test/mysql/tests-join.js | 6 +- test/mysql/tests-like.js | 6 +- test/mysql/tests-limit.js | 6 +- test/mysql/tests-offset.js | 6 +- test/mysql/tests-orderby.js | 18 ++--- test/mysql/tests-select.js | 30 ++++----- test/mysql/tests-set.js | 6 +- test/mysql/tests-truncate.js | 6 +- test/mysql/tests-update.js | 24 +++---- test/mysql/tests-update_batch.js | 6 +- test/mysql/tests-where.js | 26 ++++---- test/mysql/tests-where_in.js | 10 +-- 31 files changed, 247 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index ed6d52b..33c723f 100755 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ var settings = { user: 'myuser', password: 'MyP@ssw0rd' }; -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) @@ -131,9 +131,9 @@ We'll call this `db.json`. **Example App** ```javascript -var settings = require('db.json'); +const settings = require('db.json'); // Second and third parameters of the QueryBuilder method default to 'mysql' and 'standard', respectively -var qb = require('node-querybuilder').QueryBuilder(settings); +const qb = require('node-querybuilder').QueryBuilder(settings); ``` Of course you can also just have a normal javascript object directly within your code somwhere if you're honing your inner Chuck Norris: @@ -141,7 +141,7 @@ Of course you can also just have a normal javascript object directly within your **Chuck Norris App** ```javascript -var qb = require('node-querybuilder').QueryBuilder({ +const qb = require('node-querybuilder').QueryBuilder({ host: 'db.myserver.com', user: 'myusername', password: 'P@s$w0rD', @@ -156,7 +156,7 @@ This part is super simple. Just pass which one you'd like to use as the second p **_Example:_** ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings, 'postgres'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'postgres'); ``` ## Choosing the Connection Type @@ -176,7 +176,7 @@ This library currently supports 3 connection methods: **Example:** ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); ``` # API Methods @@ -515,7 +515,7 @@ you know what you're doing). NOTE: Please make sure to escape values manually. // WHEN `u`.`id` = 4132 THEN `um`.`id` = `um`.`userId` // WHEN `u`.`name` = 4132 THEN `um`.`name` = `u`.`id` var select = ['u.id', 'u.name', 'um.name as user_name']; -var user_data = req.body; +const user_data = req.body; qb.select(select).from('users u') .join('`user_meta` `um`', 'CASE WHEN `u`.`id` = ' + user_data.id + ' THEN `um`.`id` = `um`.`userId` WHEN `u`.`name` = ' + user_data.id + ' THEN `um`.`name` = `u`.`id`', 'right outer', false) .get(callback); @@ -1058,7 +1058,7 @@ pool.get_connection(function(qb) { #### Using the Same Connection Pool Connection for Successive Calls ```javascript -var pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); +const pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); var data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; pool.get_connection(function(qb) { @@ -1270,15 +1270,15 @@ Object containing information about the results of the query. Here's a contrived example of how it might be used in an app made with the Express framework: ```javascript -var express = require('express'); +const express = require('express'); var app = express(); -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/update_account', function(req, res) { - var user_id = req.session.user_id; - var sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; - var sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; + const user_id = req.session.user_id; + const sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; + const sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; var data = { first_name: sanitize_name(req.body.first_name), @@ -1304,7 +1304,7 @@ app.post('/update_account', function(req, res) { Here's another (more-direct) example where one decided to supply the table, data, and filters through alternative methods: ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); qb.where('id', 42) .from('users') .set('email', 'email@domain.net') @@ -1403,13 +1403,13 @@ Object containing information about the result of the query. Here's a contrived example of how it might be used in an app made with the Express framework: ```javascript -var express = require('express'); +const express = require('express'); var app = express(); -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/add_article', function(req, res) { - var user_id = req.session.user_id; + const user_id = req.session.user_id; var data = { title: req.body.first_name, @@ -1455,7 +1455,7 @@ Object containing information about the result of the query. **Example** ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); var data = [ {name: 'MySQL', version: '5.5.40'}, @@ -1504,7 +1504,7 @@ Object containing information about the result of the query. * ] */ -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); var data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, function(err, res) { if (err) throw err; @@ -1567,13 +1567,13 @@ Object containing information about the result of the query. Here's a contrived example of how it might be used in an app made with the Express framework (NOTE: you should do better with error handling): ```javascript -var express = require('express'); +const express = require('express'); var app = express(); -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/delete_comment/:id', function(req, res) { - var comment_id = req.params.id; + const comment_id = req.params.id; pool.get_connection(function(qb) { qb.get('comments', {id: id}, function(err, res) { @@ -1616,8 +1616,8 @@ Object containing information about the result of the query. **Examples** ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); /* * Assume we have a table like this to start with... @@ -1664,8 +1664,8 @@ Object containing information about the result of the query. **Examples** ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); /* * Assume we have a table like this to start with... @@ -1723,8 +1723,8 @@ Used to get a new connection from the connection pool or cluster pool. An instan **Example** ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { qb.limit(10).get('users', function(err, res) { @@ -1746,8 +1746,8 @@ Releases a connection back to the pool when you are done with it. Calling this i Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { @@ -1771,8 +1771,8 @@ pool.get_connection(function(qb) { Here's a simpler example so you can better see how it will most often be used ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { qb.get_where('users', {username: 'foobar'}, function(err, res) { @@ -1795,8 +1795,8 @@ If you'd rather the engine not execute the query first, you can always use the a **Examples** ```javascript -var settings = require('db.json'); -var pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const settings = require('db.json'); +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { var id = 4531; qb.get('comments', {id: id}, function(err, res) { @@ -1833,7 +1833,7 @@ Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" **Example** ```javascript -var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; qb.query(sql, function(err, res) { console.dir(res); @@ -1869,7 +1869,7 @@ Compiles a SELECT-like query into a properly-escaped string. Get certain details of a user account ```javascript -var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = qb .select(['id','username','first_name','last_name']) @@ -1899,8 +1899,8 @@ Compiles a INSERT-like query into a properly-escaped string. Add a new user to a `users` table. ```javascript -var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -var crypto = require('crypto'); +const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const crypto = require('crypto'); var data = { username: 'foobar', password: crypto.createHash('sha1').update('password').digest('hex'), @@ -1932,8 +1932,8 @@ Compiles an UPDATE-like query into a properly-escaped string. Update the password of a user ```javascript -var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -var crypto = require('crypto'); +const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const crypto = require('crypto'); var data = { password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), }; @@ -1964,7 +1964,7 @@ Compiles a SELECT-like query into a properly-escaped string. Delete a user ```javascript -var qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); var sql = qb.where('id',4321).get_compiled_delete('users'); // DELETE FROM `users` WHERE `id` = 4321 diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 6330f29..254ba48 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,6 +1,6 @@ -var Adapters = function(nqb) { +const Adapters = function(nqb) { // Load MySQL Driver - var mysql = require('mysql'); + const mysql = require('mysql'); // Verify setting property exists if (!nqb.hasOwnProperty('settings')) { @@ -29,7 +29,7 @@ var Adapters = function(nqb) { // NOTE: MySQL connection settings names are the same as Node Querybuilder, // it's just good practice to go ahead and do this in case things change. // **************************************************************************** - var map_connection_settings = function() { + const map_connection_settings = function() { that.connection_settings = { host: nqb.settings.host, user: nqb.settings.user, @@ -62,7 +62,7 @@ var Adapters = function(nqb) { // @param Object qb The QueryBuilder object // @return Object QueryBuilder object // **************************************************************************** - var get_query_builder = function() { + const get_query_builder = function() { try { return require('./query_builder.js').QueryBuilder(); } catch(e) { @@ -78,7 +78,7 @@ var Adapters = function(nqb) { // @param Object conn The Connnection object // @return Object QueryExec Object // **************************************************************************** - var get_query_exec = function(qb, conn) { + const get_query_exec = function(qb, conn) { try { return require('./query_exec.js').QueryExec(qb, conn); } catch(e) { @@ -91,7 +91,7 @@ var Adapters = function(nqb) { // ----- // @return Object Adapter object // **************************************************************************** - var Adapter = function(settings) { + const Adapter = function(settings) { var pool, connection; // If the Pool object is instatiating this Adapter, use it's connection @@ -146,9 +146,9 @@ var Adapters = function(nqb) { // ----- // @return Object Adapter object // **************************************************************************** - var Pool = function() { + const Pool = function() { // Return Pool Object - var return_pool = function() { + const return_pool = function() { return { pool: function() { return nqb.pool; @@ -205,7 +205,7 @@ var Adapters = function(nqb) { // ----- // @return Object Adapter object // **************************************************************************** - var Cluster = function() { + const Cluster = function() { }; @@ -215,7 +215,7 @@ var Adapters = function(nqb) { // @param // @return // **************************************************************************** - var determine_adapter = function() { + const determine_adapter = function() { switch(nqb.connection_type) { case 'cluster': return new Cluster(); diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 476142e..7ed4ba7 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -4,9 +4,9 @@ // @param Object qb The QueryBuilder object // @param Object adapter The connection adapter object // **************************************************************************** -var QueryExec = function(qb, conn) { +const QueryExec = function(qb, conn) { - var exec = function(sql, callback) { + const exec = function(sql, callback) { if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { conn.query(sql, function(err, results) { // Standardize some important properties diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js index 5fe6589..756ef0b 100755 --- a/examples/connection_pool_example.js +++ b/examples/connection_pool_example.js @@ -4,7 +4,7 @@ var settings = { user: 'myuser', password: 'MyP@ssw0rd' }; -var nqb = require('node-querybuilder'); +const nqb = require('node-querybuilder'); var pool = new QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js index 50198fd..52a7fd5 100755 --- a/examples/connection_single_example.js +++ b/examples/connection_single_example.js @@ -4,7 +4,7 @@ var settings = { user: 'myuser', password: 'MyP@ssw0rd' }; -var nqb = require('node-querybuilder'); +const nqb = require('node-querybuilder'); var qb = new QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') diff --git a/index.js b/index.js index 08ba8bf..8f7a162 100755 --- a/index.js +++ b/index.js @@ -2,9 +2,9 @@ * QueryBuilder for Node.js * (C) Kyle Farris 2014-2015 * kyle@chomponllc.com - * - * A generic Query Builder for any SQL or NOSQL database adapter. - * + * + * A generic Query Builder for any SQL or NOSQL database adapter. + * * Current adapters: * - MySQL * @@ -15,9 +15,9 @@ * - sqlite3 * - oracle * - mongo - * + * * Dual licensed under the MIT and GPL licenses. - * + * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -25,10 +25,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -36,11 +36,11 @@ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. - * + * **/ -var lo_assign = require('lodash.assign'); -var QueryBuilder = function(settings,driver,type) { - +const lo_assign = require('lodash.assign'); +const QueryBuilder = function(settings,driver,type) { + this.settings = (settings ? lo_assign({}, settings) : {}); this.driver = driver || 'mysql'; this.connection_type = type || 'single'; @@ -48,32 +48,32 @@ var QueryBuilder = function(settings,driver,type) { this.driver_version = 'default'; this.driver_info = null; this.pool = []; - - // **************************************************************************** - // Get information about the driver the user wants to use and modify QB object - // ----- + + // **************************************************************************** + // Get information about the driver the user wants to use and modify QB object + // ----- // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object + // @return Object Modified QueryBuilder object // **************************************************************************** - var get_driver_info = function(qb) { + const get_driver_info = function(qb) { // A driver must be specified if (typeof driver !== 'string') { throw new Error("No database driver specified!"); } - + qb.driver = driver.toLowerCase(); - + // Verify that the driver is one we fundamentally support if (Object.keys(qb.drivers).indexOf(qb.driver) === -1) { throw new Error("Invalid driver specified!"); } - + // Determine version of driver to use if (qb.settings.hasOwnProperty('version') && (typeof qb.settings.version).match(/^(string|number)$/i)) { qb.driver_version = qb.settings.version; delete qb.settings.version; } - + // Retrieve info about driver if available, error if not if (qb.drivers[qb.driver].versions.hasOwnProperty(qb.driver_version)) { if (qb.drivers[qb.driver].versions[qb.driver_version].hasOwnProperty('version')) { @@ -84,23 +84,23 @@ var QueryBuilder = function(settings,driver,type) { } else { throw new Error(qb.driver_version + " is not a version of the " + qb.driver + " driver that this library specifically supports. Try being more generic."); } - + // Fail if specified driver is inactive if (qb.driver_info.active === false) { - var err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) + var err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) + " of the " + qb.driver + " driver you are attempting to load is not currently available!"; throw new Error(err); } }; get_driver_info(this); - + // **************************************************************************** // Determine the type of connection (single, pool, cluster, etc...) // ----- // @param Object qb The QueryBuilder object // @return Object Modified QueryBuilder object // **************************************************************************** - var get_connection_type = function(qb) { + const get_connection_type = function(qb) { if (Object.keys(qb.drivers[qb.driver].connection_types).indexOf(qb.connection_type) === -1) { throw new Error("You have specified a invalid database connection method: " + qb.connection_type); } @@ -110,22 +110,22 @@ var QueryBuilder = function(settings,driver,type) { return qb; } get_connection_type(this); - + // **************************************************************************** // Returns the single, pool, or cluster adapter // ----- // @return VOID This method responds asychronously via a callback // **************************************************************************** - var get_adapter = function(qb) { + const get_adapter = function(qb) { try { - var adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); + const adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); return adapter; } catch(e) { throw new Error("Couldn't load the Connection library for " + qb.driver + "(" + JSON.stringify(qb.settings) + "): " + e); } }; - - return get_adapter(this); -}; - -exports.QueryBuilder = QueryBuilder; + + return get_adapter(this); +}; + +exports.QueryBuilder = QueryBuilder; diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index a8695a1..da2553d 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -1,8 +1,8 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var nqb = require('../../index.js'); +const should = require('chai').should(); +const expect = require('chai').expect; +const nqb = require('../../index.js'); -var check = function(done, f) { +const check = function(done, f) { try { f(); done(); @@ -11,7 +11,7 @@ var check = function(done, f) { } }; -var connection_released = function(qb) { +const connection_released = function(qb) { var connection = qb.connection(); expect(connection._pool._freeConnections).to.have.length(0); qb.release(); @@ -19,13 +19,13 @@ var connection_released = function(qb) { }; describe('QueryBuilder() - MySQL Adapter', function() { - var on_connect = function(err) { + const on_connect = function(err) { if (err) { console.error("Not connected!"); return; } console.log("connected!"); }; var driver = 'mysql'; var settings = { - host: '127.0.0.1', + host: '127.0.0.1', database: 'mock_db', user: 'travis', version: '2.5.4', diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index 41c27ab..7d8f970 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('get_compiled_select()', function() { it('should exist', function() { diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js index caf0ea9..7db463e 100755 --- a/test/mysql/tests-count.js +++ b/test/mysql/tests-count.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('count()', function() { it('should exist', function() { diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 2687d0e..7eabfd7 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('delete()', function() { it('should exist', function() { @@ -15,8 +15,8 @@ describe('delete()', function() { qb.from_array.should.eql(['`galaxies`']); }); it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { - qb.reset_query(); - + qb.reset_query(); + expect(function() { qb.delete([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.delete({}); }, 'empty object provided').to.throw(Error); expect(function() { qb.delete(3); }, 'integer provided').to.throw(Error); @@ -30,7 +30,7 @@ describe('delete()', function() { expect(function() { qb.delete(''); }, 'empty string provided').to.throw(Error); expect(function() { qb.delete(' '); }, 'string full of spaces provided').to.throw(Error); expect(function() { qb.delete(null); }, 'null provided').to.throw(Error); - + // An undefined/nothing option will only work if a table has already been provided qb.from('galaxies'); expect(function() { qb.delete(undefined); },'undefined provided').to.not.throw(Error); qb.from('galaxies'); expect(function() { qb.delete(); },'nothing provided').to.not.throw(Error); @@ -67,7 +67,7 @@ describe('delete()', function() { qb.from('galaxies'); var sql = qb.delete(); sql.should.eql('DELETE FROM `galaxies`'); - + qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql('DELETE FROM `galaxies`'); diff --git a/test/mysql/tests-distinct.js b/test/mysql/tests-distinct.js index da55495..630ee97 100755 --- a/test/mysql/tests-distinct.js +++ b/test/mysql/tests-distinct.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('distinct()', function() { it('should exist', function() { diff --git a/test/mysql/tests-empty_table.js b/test/mysql/tests-empty_table.js index 7d17165..f3c504e 100755 --- a/test/mysql/tests-empty_table.js +++ b/test/mysql/tests-empty_table.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('emtpy_table()', function() { it('should exist', function() { @@ -22,8 +22,8 @@ describe('emtpy_table()', function() { sql.should.eql('DELETE FROM `galaxies`'); }); it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { - qb.reset_query(); - + qb.reset_query(); + expect(function() { qb.empty_table([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.empty_table({}); }, 'empty object provided').to.throw(Error); expect(function() { qb.empty_table(3); }, 'integer provided').to.throw(Error); @@ -37,7 +37,7 @@ describe('emtpy_table()', function() { expect(function() { qb.empty_table(''); }, 'empty string provided').to.throw(Error); expect(function() { qb.empty_table(' '); }, 'string full of spaces provided').to.throw(Error); expect(function() { qb.empty_table(null); }, 'null provided').to.throw(Error); - + // An undefined/nothing option will only work if a table has already been provided qb.from('galaxies'); expect(function() { qb.empty_table(undefined); },'undefined provided').to.not.throw(Error); qb.from('galaxies'); expect(function() { qb.empty_table(); },'nothing provided').to.not.throw(Error); diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js index 6de989c..e6b4c3d 100755 --- a/test/mysql/tests-from.js +++ b/test/mysql/tests-from.js @@ -1,5 +1,5 @@ -var should = require('chai').should(); -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('from()', function() { it('should exist', function() { @@ -37,17 +37,17 @@ describe('from()', function() { qb.from(['universe','galaxy','star_system','planet']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); - it('should not double-escape an item', function() { + it('should not double-escape an item', function() { qb.reset_query(); qb.from('`do`'); qb.from_array.should.eql(['`do`']); }); - it('should not double-escape items when provided with an array of pre-escaped items', function() { + it('should not double-escape items when provided with an array of pre-escaped items', function() { qb.reset_query(); qb.from(['`universe`','`galaxy`','`star_system`']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); }); - it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', function() { + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', function() { qb.reset_query(); qb.from(['`universe`','galaxy','`star_system`']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); @@ -91,7 +91,7 @@ describe('from()', function() { qb.reset_query(); qb.from('star_system.planet'); qb.from_array.should.eql(['`star_system`.`planet`']); - + qb.reset_query(); qb.from('galaxy.star_system.planet'); qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); @@ -110,11 +110,11 @@ describe('from()', function() { qb.reset_query(); qb.from(''); qb.from_array.should.be.empty; - + qb.reset_query(); qb.from(['','']); qb.from_array.should.be.empty; - + qb.reset_query(); qb.from(['','foobar']); qb.from_array.should.eql(['`foobar`']); diff --git a/test/mysql/tests-general.js b/test/mysql/tests-general.js index d53cddb..35dd338 100755 --- a/test/mysql/tests-general.js +++ b/test/mysql/tests-general.js @@ -1,8 +1,8 @@ -var should = require('chai').should(); +const should = require('chai').should(); -describe('QueryBuilder', function() { +describe('QueryBuilder', function() { it('actually exists and can be initialized', function() { - var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); + const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); qb.should.be.instanceOf(Object); }); }); \ No newline at end of file diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index 13ffa41..dfe00b7 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('get()', function() { it('should exist', function() { @@ -61,7 +61,7 @@ describe('get()', function() { qb.from('galaxies'); var sql = qb.get(); sql.should.eql('SELECT * FROM `galaxies`'); - + qb.reset_query(); var sql = qb.from(['galaxies','star_systems','planets']).get(); sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); @@ -88,22 +88,22 @@ describe('get()', function() { }); it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', function() { qb.reset_query(); - + // MIN var sql = qb.select_min('size','min_size').get('galaxies'); sql.should.eql("SELECT MIN(`size`) AS min_size FROM `galaxies`"); qb.reset_query(); - + // MAX var sql = qb.select_max('size','max_size').get('galaxies'); sql.should.eql("SELECT MAX(`size`) AS max_size FROM `galaxies`"); qb.reset_query(); - + // AVG var sql = qb.select_avg('size','avg_size').get('galaxies'); sql.should.eql("SELECT AVG(`size`) AS avg_size FROM `galaxies`"); qb.reset_query(); - + // SUM var sql = qb.select_sum('size','total_size').get('galaxies'); sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); @@ -118,7 +118,7 @@ describe('get()', function() { it('should include any GROUP BY statements added using the group_by() method.', function() { qb.reset_query(); var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); }); it('should add the ORDER BY clause of the order_by() method was called in the chain', function() { qb.reset_query(); @@ -128,7 +128,7 @@ describe('get()', function() { it('should include any HAVING clauses added using the having() method', function() { qb.reset_query(); var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); }); }); @@ -176,6 +176,6 @@ describe('get_where()', function() { var sql = qb.get_where('galaxies', {type: 'spiral'}); qb.from_array.should.eql(['`galaxies`']); qb.where_array.should.eql(["`type` = 'spiral'"]); - sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); + sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); }); }); diff --git a/test/mysql/tests-groupby.js b/test/mysql/tests-groupby.js index 733a6bf..0659478 100755 --- a/test/mysql/tests-groupby.js +++ b/test/mysql/tests-groupby.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('group_by()', function() { it('should exist', function() { @@ -42,10 +42,10 @@ describe('group_by()', function() { expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); - + // valid string expect(function() { qb.group_by('planet_type'); }, 'valid string provided').to.not.throw(Error); expect(function() { qb.group_by(['planet_type']); }, 'array of string(s) provided').to.not.throw(Error); - + }); }); \ No newline at end of file diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js index cbb0dce..952395f 100755 --- a/test/mysql/tests-having.js +++ b/test/mysql/tests-having.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('having()', function() { it('should exist', function() { @@ -16,27 +16,27 @@ describe('having()', function() { qb.reset_query(); qb.having('planet_class > "M"'); qb.having_array.should.eql(["`planet_class` > 'M'"]); - + qb.reset_query(); qb.having('planet_class < "M"'); qb.having_array.should.eql(["`planet_class` < 'M'"]); - + qb.reset_query(); qb.having('planet_class <> "M"'); qb.having_array.should.eql(["`planet_class` <> 'M'"]); - + qb.reset_query(); qb.having('planet_class >= "M"'); qb.having_array.should.eql(["`planet_class` >= 'M'"]); - + qb.reset_query(); qb.having('planet_class <= "M"'); qb.having_array.should.eql(["`planet_class` <= 'M'"]); - + qb.reset_query(); qb.having('planet_class = "M"'); qb.having_array.should.eql(["`planet_class` = 'M'"]); - + qb.reset_query(); qb.having('planet_class != "M"'); qb.having_array.should.eql(["`planet_class` != 'M'"]); @@ -69,7 +69,7 @@ describe('having()', function() { expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); - + // valid string expect(function() { qb.group_by('planet_type = "M"'); }, 'valid string provided').to.not.throw(Error); expect(function() { qb.group_by(['planet_type = "M"']); }, 'array of string(s) provided').to.not.throw(Error); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 69dc796..48e1475 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_data = {id:3, name:'Milky Way', type: 'spiral'}; var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; @@ -101,7 +101,7 @@ describe('insert()', function() { }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); - var func = function() { console.log("foo"); }; + const func = function() { console.log("foo"); }; var regex = /foobar/; var arr = [1,2,3]; var obj = {foo: 'bar'}; diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index eba7c60..4394e29 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_where = {id:3}; var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; @@ -19,10 +19,10 @@ describe('insert_batch()', function() { }); it('should only accept nothing or a string for the table (first) parameter', function() { qb.reset_query(); - + // Doing these to prevent other errors - qb.from('galaxies'); - + qb.from('galaxies'); + expect(function() { qb.insert_batch([], test_data); }, 'empty array provided').to.throw(Error); expect(function() { qb.insert_batch({}, test_data); }, 'empty object provided').to.throw(Error); expect(function() { qb.insert_batch(3, test_data); }, 'integer provided').to.throw(Error); @@ -31,7 +31,7 @@ describe('insert_batch()', function() { expect(function() { qb.insert_batch(Infinity, test_data); }, 'Infinity provided').to.throw(Error); expect(function() { qb.insert_batch([1,2], test_data); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.insert_batch(/foobar/, test_data); }, 'regex provided').to.throw(Error); - + expect(function() { qb.insert_batch(NaN, test_data); }, 'NaN provided').to.not.throw(Error); expect(function() { qb.insert_batch(false, test_data); }, 'false provided').to.not.throw(Error); expect(function() { qb.insert_batch('', test_data); }, 'empty string provided').to.not.throw(Error); @@ -46,10 +46,10 @@ describe('insert_batch()', function() { }); it('should only accept an array as the second parameter', function() { qb.reset_query(); - + expect(function() { qb.insert_batch('galaxies',test_data); }, 'array of objects provided').to.not.throw(Error); expect(function() { qb.insert_batch('galaxies',[]); }, 'empty array provided').to.not.throw(Error); - + expect(function() { qb.insert_batch('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); expect(function() { qb.insert_batch('galaxies',[test_data,test_data]); }, 'array of arrays provided').to.throw(Error); expect(function() { qb.insert_batch('galaxies',{}); }, 'empty object provided').to.throw(Error); @@ -82,11 +82,11 @@ describe('insert_batch()', function() { }); it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { qb.reset_query(); - var func = function() { console.log("foo"); }; + const func = function() { console.log("foo"); }; var regex = /foobar/; var arr = [1,2,3]; var obj = {foo: 'bar'}; - + expect(function() { qb.insert_batch('galaxies',[{id: func}]); }, 'function in data').to.throw(Error); expect(function() { qb.insert_batch('galaxies',[{id: regex}]); }, 'regex in data').to.throw(Error); expect(function() { qb.insert_batch('galaxies',[{id: Infinity}]); }, 'Infinity in data').to.throw(Error); diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index 382343c..d681626 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('join()', function() { it('should exist', function() { diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js index de4ee16..957870f 100755 --- a/test/mysql/tests-like.js +++ b/test/mysql/tests-like.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('like()', function() { it('should exist', function() { diff --git a/test/mysql/tests-limit.js b/test/mysql/tests-limit.js index f6ff94f..f7ebef7 100755 --- a/test/mysql/tests-limit.js +++ b/test/mysql/tests-limit.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('limit()', function() { it('should exist', function() { diff --git a/test/mysql/tests-offset.js b/test/mysql/tests-offset.js index 9b13290..185a847 100755 --- a/test/mysql/tests-offset.js +++ b/test/mysql/tests-offset.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('offset()', function() { it('should exist', function() { diff --git a/test/mysql/tests-orderby.js b/test/mysql/tests-orderby.js index 377fbc9..9a29a7f 100755 --- a/test/mysql/tests-orderby.js +++ b/test/mysql/tests-orderby.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('order_by()', function() { it('should exist', function() { @@ -72,7 +72,7 @@ describe('order_by()', function() { expect(function() { qb.order_by('planet_position',[]); }).to.not.throw(Error); expect(function() { qb.order_by('planet_position',{}); }).to.not.throw(Error); expect(function() { qb.order_by('planet_position',''); }).to.not.throw(Error); - + // Only an invalid string will throw an error expect(function() { qb.order_by('planet_position','FAKE'); }).to.throw(Error); }); @@ -90,19 +90,19 @@ describe('order_by()', function() { qb.reset_query(); qb.order_by('planet_position', 'random'); qb.order_by_array.should.eql(['RAND()']); - + qb.reset_query(); qb.order_by('planet_size', 'RAND'); qb.order_by_array.should.eql(['RAND()']); - + qb.reset_query(); qb.order_by('planet_position, planet_size', 'rand'); qb.order_by_array.should.eql(['RAND()']); - + qb.reset_query(); qb.order_by(null, 'RAND()'); qb.order_by_array.should.eql(['RAND()']); - + qb.reset_query(); qb.order_by('', 'rand'); qb.order_by_array.should.eql(['RAND()']); @@ -116,7 +116,7 @@ describe('order_by()', function() { qb.reset_query(); qb.order_by(['planet_position', 'planet_size'], 'desc'); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); - + qb.reset_query(); qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index 888d51e..8428767 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('select()', function() { it('should exist', function() { @@ -78,17 +78,17 @@ describe('select()', function() { qb.select(['`sol`','la','ti','`do`'],false); qb.select_array.should.eql(['`sol`','la','ti','`do`']); }); - it('should not double-escape a field', function() { + it('should not double-escape a field', function() { qb.reset_query(); qb.select('`do`'); qb.select_array.should.eql(['`do`']); }); - it('should not double-escape fields when provided with an array of pre-escaped fields', function() { + it('should not double-escape fields when provided with an array of pre-escaped fields', function() { qb.reset_query(); qb.select(['`do`','`re`','`mi`']); qb.select_array.should.eql(['`do`','`re`','`mi`']); }); - it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', function() { + it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', function() { qb.reset_query(); qb.select(['`do`','re','`mi`']); qb.select_array.should.eql(['`do`','`re`','`mi`']); @@ -122,11 +122,11 @@ describe('select()', function() { qb.reset_query(); qb.select('star_system.planet'); qb.select_array.should.eql(['`star_system`.`planet`']); - + qb.reset_query(); qb.select('galaxy.star_system.planet'); qb.select_array.should.eql(['`galaxy`.`star_system`.`planet`']); - + qb.reset_query(); qb.select('universe.galaxy.star_system.planet'); qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); @@ -138,11 +138,11 @@ describe('select()', function() { }); it('should not allow subqueries or functions with commas in them without the second parameter being false', function() { qb.reset_query(); - expect(function() { + expect(function() { qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); }).to.throw(Error); - - expect(function() { + + expect(function() { qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); }).to.not.throw(Error); }); @@ -150,19 +150,19 @@ describe('select()', function() { qb.reset_query(); qb.select('count(*) as count', false); qb.select_array.should.eql(['count(*) AS `count`']); - + qb.reset_query(); qb.select('count(*) as count, m.*, MIN(id) as min', false); qb.select_array.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); - + qb.reset_query(); qb.select('(select count(p.*) as count from planets p) as num_planets', false); qb.select_array.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); - + qb.reset_query(); qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); qb.select_array.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); - + }); }); var prefixes = ['min','max','avg','sum']; diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index e9f278b..aa5a300 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('set()', function() { it('should exist', function() { diff --git a/test/mysql/tests-truncate.js b/test/mysql/tests-truncate.js index b6d387e..72bc745 100755 --- a/test/mysql/tests-truncate.js +++ b/test/mysql/tests-truncate.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('truncate()', function() { it('should exist', function() { diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index eb52058..8b32c42 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_where = {id:3}; var test_data = {name:'Milky Way', type: 'spiral'}; @@ -22,10 +22,10 @@ describe('update()', function() { }); it('should accept a string or falsy value for the table (first) parameter', function() { qb.reset_query(); - + // One could choose to pass a falsy value to the first param because they have or will // supply it with the from() method instead. - + qb.reset_query(); expect(function() { qb.from('galaxies').update([], test_data); }, 'empty array provided').to.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update({}, test_data); }, 'empty object provided').to.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update(3, test_data); }, 'integer provided').to.throw(Error); @@ -34,7 +34,7 @@ describe('update()', function() { qb.reset_query(); expect(function() { qb.from('galaxies').update(Infinity, test_data); }, 'Infinity provided').to.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update([1,2], test_data); }, 'array of numbers provided').to.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update(/foobar/, test_data); }, 'regex provided').to.throw(Error); - + qb.reset_query(); expect(function() { qb.from('galaxies').update(NaN, test_data); }, 'NaN provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update(false, test_data); }, 'false provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.from('galaxies').update('', test_data); }, 'empty string provided').to.not.throw(Error); @@ -45,17 +45,17 @@ describe('update()', function() { it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { // One could choose to pass a falsy value to the second param because they have or will // supply data with the set() method instead. - + qb.reset_query(); expect(function() { qb.update('galaxies',test_data); }, 'non-empty array provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',test_data_set); }, 'array of non-empty standard objects provided').to.not.throw(Error); - + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',NaN); }, 'NaN provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',false); }, 'false provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',''); }, 'empty string provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',null); }, 'null provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',undefined); }, 'undefined provided').to.not.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies'); }, 'nothing provided').to.not.throw(Error); - + qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3); }, 'integer provided').to.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3.5); }, 'float provided').to.throw(Error); qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',true); }, 'true provided').to.throw(Error); @@ -103,11 +103,11 @@ describe('update()', function() { }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); - var func = function() { console.log("foo"); }; + const func = function() { console.log("foo"); }; var regex = /foobar/; var arr = [1,2,3]; var obj = {foo: 'bar'}; - + qb.reset_query(); expect(function() { qb.update('galaxies',{id: func}); }, 'function in data').to.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); @@ -115,7 +115,7 @@ describe('update()', function() { qb.reset_query(); expect(function() { qb.update('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',{id: arr}); }, 'array in data').to.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',{id: obj}); }, 'object in data').to.throw(Error); - + }); /* it('should support insert ignore statements', function() { diff --git a/test/mysql/tests-update_batch.js b/test/mysql/tests-update_batch.js index bfc2610..9dd6845 100755 --- a/test/mysql/tests-update_batch.js +++ b/test/mysql/tests-update_batch.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); var test_where = {id:3}; var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js index 7275ac1..4ac74be 100755 --- a/test/mysql/tests-where.js +++ b/test/mysql/tests-where.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('where()', function() { it('should exist', function() { @@ -27,10 +27,10 @@ describe('where()', function() { expect(function() { qb.where([]); }, 'empty array provided').to.throw(Error); expect(function() { qb.where([1,2]); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.where(''); }, 'empty string provided').to.throw(Error); - + expect(function() { qb.where('planet_position',3); }, 'valid string provided').to.not.throw(Error); expect(function() { qb.where({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); - + }); it('should accept a field name in the form of a string as the first parameter', function() { qb.reset_query(); @@ -51,7 +51,7 @@ describe('where()', function() { qb.reset_query(); qb.where('planet',true); qb.where_array.should.eql(['`planet` = 1']); - + qb.reset_query(); qb.where('planet',false); qb.where_array.should.eql(['`planet` = 0']); @@ -60,7 +60,7 @@ describe('where()', function() { qb.reset_query(); qb.where('planet',5); qb.where_array.should.eql(['`planet` = 5']); - + qb.reset_query(); qb.where('planet',123.456); qb.where_array.should.eql(['`planet` = 123.456']); @@ -69,7 +69,7 @@ describe('where()', function() { qb.reset_query(); qb.where('planet','Earth'); qb.where_array.should.eql(["`planet` = 'Earth'"]); - + qb.reset_query(); qb.where('galaxy','Milky Way'); qb.where_array.should.eql(["`galaxy` = 'Milky Way'"]); @@ -104,23 +104,23 @@ describe('where()', function() { qb.reset_query(); qb.where('position >',3); qb.where_array.should.eql(["`position` > 3"]); - + qb.reset_query(); qb.where('position <',3); qb.where_array.should.eql(["`position` < 3"]); - + qb.reset_query(); qb.where('position >=',3); qb.where_array.should.eql(["`position` >= 3"]); - + qb.reset_query(); qb.where('position <=',3); qb.where_array.should.eql(["`position` <= 3"]); - + qb.reset_query(); qb.where('position <>',3); qb.where_array.should.eql(["`position` <> 3"]); - + qb.reset_query(); qb.where('position !=',3); qb.where_array.should.eql(["`position` != 3"]); diff --git a/test/mysql/tests-where_in.js b/test/mysql/tests-where_in.js index 48b43ba..970a0c6 100755 --- a/test/mysql/tests-where_in.js +++ b/test/mysql/tests-where_in.js @@ -1,6 +1,6 @@ -var should = require('chai').should(); -var expect = require('chai').expect; -var qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const should = require('chai').should(); +const expect = require('chai').expect; +const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); describe('where_in()', function() { it('should exist', function() { @@ -31,7 +31,7 @@ describe('where_in()', function() { expect(function() { qb.where_in([1,2]); }, 'array of numbers provided').to.throw(Error); expect(function() { qb.where_in(''); }, 'empty string provided').to.throw(Error); expect(function() { qb.where_in(/foobar/); }, 'regex provided').to.throw(Error); - + expect(function() { qb.where_in('planet_position',[1,2,3]); }, 'valid string provided').to.not.throw(Error); }); it('should not accept anything but a non-empty array of values as second parameter', function() { @@ -49,7 +49,7 @@ describe('where_in()', function() { expect(function() { qb.where_in('planet',[]); }, 'empty array provided').to.throw(Error); expect(function() { qb.where_in('planet',''); }, 'empty string provided').to.throw(Error); expect(function() { qb.where_in('planet',/foobar/); }, 'regex provided').to.throw(Error); - + expect(function() { qb.where_in('planet',['Mars','Earth','Venus','Mercury']); }, 'non-empty array provided').to.not.throw(Error); }); it('should require both a field name an array of values as first and second parameters, respectively', function() { From e1646cf36cada12a59da2782b04d3c6f3f9e3483 Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 08:08:06 -0400 Subject: [PATCH 110/200] Changed all variable declarations to const or let instead of var --- README.md | 92 ++++++++++++------------- drivers/mysql/adapters.js | 14 ++-- drivers/mysql/query_exec.js | 28 ++++---- drivers/template/connect.js | 16 ++--- examples/connection_pool_example.js | 4 +- examples/connection_single_example.js | 4 +- index.js | 2 +- test/mysql/tests-adapter.js | 89 ++++++++++++++---------- test/mysql/tests-compilation_methods.js | 8 +-- test/mysql/tests-count.js | 14 ++-- test/mysql/tests-delete.js | 18 ++--- test/mysql/tests-empty_table.js | 6 +- test/mysql/tests-get.js | 46 ++++++------- test/mysql/tests-having.js | 2 +- test/mysql/tests-insert.js | 34 ++++----- test/mysql/tests-insert_batch.js | 18 ++--- test/mysql/tests-join.js | 8 +-- test/mysql/tests-select.js | 12 ++-- test/mysql/tests-truncate.js | 4 +- test/mysql/tests-update.js | 26 +++---- test/mysql/tests-update_batch.js | 6 +- tests.md | 22 +++--- 22 files changed, 243 insertions(+), 230 deletions(-) diff --git a/README.md b/README.md index 33c723f..fb5cf22 100755 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Licensed under the GPL license and MIT: This quick example shows how to connect to and asynchronously query a MySQL database using a single connection. ```javascript -var settings = { +const settings = { host: 'localhost', database: 'mydatabase', user: 'myuser', @@ -497,7 +497,7 @@ Multiple function calls can be made if you need several joins in one query: // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` // LEFT JOIN `locations` `l` ON `l`.`id`=`u`.`location_id` -var select = ['u.id', 'u.name', 't.name as type', 'l.name as location']; +const select = ['u.id', 'u.name', 't.name as type', 'l.name as location']; qb.select(select).from('users u') .join('types t', 't.id=u.type_id', 'right outer') .join('locations l', 'l.id=u.location_id', 'left') @@ -514,7 +514,7 @@ you know what you're doing). NOTE: Please make sure to escape values manually. // CASE // WHEN `u`.`id` = 4132 THEN `um`.`id` = `um`.`userId` // WHEN `u`.`name` = 4132 THEN `um`.`name` = `u`.`id` -var select = ['u.id', 'u.name', 'um.name as user_name']; +const select = ['u.id', 'u.name', 'um.name as user_name']; const user_data = req.body; qb.select(select).from('users u') .join('`user_meta` `um`', 'CASE WHEN `u`.`id` = ' + user_data.id + ' THEN `um`.`id` = `um`.`userId` WHEN `u`.`name` = ' + user_data.id + ' THEN `um`.`name` = `u`.`id`', 'right outer', false) @@ -598,7 +598,7 @@ This will create a "WHERE IN" statement in traditional SQL which is useful when ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') -var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; +const stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_in('star',stars).get('star_systems',callback); ``` @@ -610,7 +610,7 @@ Same as `.where_in()` except the clauses are joined by 'OR'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `planet_count` = 4 OR `star` IN('Sun', 'Betelgeuse') -var stars = ['Sun', 'Betelgeuse']; +const stars = ['Sun', 'Betelgeuse']; qb.select('star_system').where('planet_count',4) .or_where_in('star',stars) .get('star_systems',callback); @@ -624,7 +624,7 @@ Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clau ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') -var stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; +const stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); ``` @@ -637,8 +637,8 @@ Same as `.where_not_in()` except that clauses are joined with 'OR'. // SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun', 'Betelgeuse') // OR `planet_count` NOT IN [2,4,6,8] -var stars = ['Sun', 'Betelgeuse']; -var planet_sizes = [2,4,6,8]; +const stars = ['Sun', 'Betelgeuse']; +const planet_sizes = [2,4,6,8]; qb.select('star_system') .where_not_in('star',stars) .or_where_not_in('planet_size',planet_sizes) @@ -874,7 +874,7 @@ Parameter | Type | Default | Description fields | String/Array | Required | Field name or an array of field names, possibly with directions as well direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. -This is a very flexible method, offerring a wide variety of ways you can call it. Variations include: +This is a very flexible method, offerring a wide constiety of ways you can call it. constiations include: - Pass the field name and ommit the direction - Pass the field name and the direction as the first and second parameters, respectively (most common) - Pass an array of fields to first paramter, direction to second parameter. @@ -994,7 +994,7 @@ qb.set('birthday','2015-02-04').update('users', callback); Set multiple keys and values at once ```javascript -var birthday = new Date(1986, 7, 5, 8, 15, 23); +const birthday = new Date(1986, 7, 5, 8, 15, 23); // UPDATE `users` SET `birthday` = '2015-02-04', `anniversary` = '2010-05-15' qb.set({birthday: birthday, anniversary: '2010-05-15'}).update('users', callback); ``` @@ -1036,14 +1036,14 @@ insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, ch #### Callback Example ```javascript -var callback = function(err, response) { +const callback = function(err, response) { qb.release(); if (err) { console.error(err); } else { - for (var i in response) { - var row = response[i]; + for (const i in response) { + const row = response[i]; /* Do something with each row... */ @@ -1059,7 +1059,7 @@ pool.get_connection(function(qb) { ```javascript const pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); -var data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; +const data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; pool.get_connection(function(qb) { qb.insert('employees', data, function(err, res) { @@ -1068,7 +1068,7 @@ pool.get_connection(function(qb) { } else { if (res.affected_rows > 0) { - var insert_id = res.insert_id; + const insert_id = res.insert_id; qb.get_where('employees', {id: insert_id}, function(err, res) { qb.release(); console.dir(res); @@ -1100,7 +1100,7 @@ There are scenarios when using this method may be required; for instance, if you **Example** ```javascript -var sql = qb.select(['f.foo', 'b.bar']) +const sql = qb.select(['f.foo', 'b.bar']) .from('foo f') .join('bar b', 'b.foo_id=f.id', 'left') .get_compiled_select(); @@ -1169,8 +1169,8 @@ qb.limit(10) conn.release(); if (err) return console.error(err); - for (var i in response) { - var row = response[i]; + for (const i in response) { + const row = response[i]; console.log("The " + row.name + " is a " + row.diameter + " lightyear-wide " + row.type + " galaxy with " + row.num_stars + " stars."); @@ -1235,7 +1235,7 @@ Integer ```javascript // SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 3 -var type = 3; +const type = 3; qb.where('type',type).count('galaxies', function(err, count) { if (err) return console.error(err); console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); @@ -1271,7 +1271,7 @@ Here's a contrived example of how it might be used in an app made with the Expre ```javascript const express = require('express'); -var app = express(); +const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); @@ -1280,7 +1280,7 @@ app.post('/update_account', function(req, res) { const sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; const sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; - var data = { + const data = { first_name: sanitize_name(req.body.first_name), last_name: sanitize_name(req.body.last_name), age: sanitize_age(req.body.last_name), @@ -1292,7 +1292,7 @@ app.post('/update_account', function(req, res) { qb.release(); if (err) return console.error(err); - var page_data = { + const page_data = { prefill: data, } return res.render('/account_updated', page_data); @@ -1337,19 +1337,19 @@ The important thing to understand is that there are, essentially, _two_ `where` **Example:** ```javascript -var qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); // The key to use as the local where clause -var key = 'id'; +const key = 'id'; // All objects in this dataset must have an id key -var dataset = [ +const dataset = [ {id: 4569, name: 'Cartwheel', constellation: 'Sculptor'}, {id: 5631, name: 'Black Eye', constellation: 'Coma Berenices'}, {id: 1238, name: 'Sombrero', constellation: 'Virgo'} ]; -var where = {'last_updated <' : '2015-01-01'} +const where = {'last_updated <' : '2015-01-01'} qb.update_batch('galaxies', dataset, key, where, function(err, res) { if (err) return console.error(err); @@ -1372,7 +1372,7 @@ qb.update_batch('galaxies', dataset, key, where, function(err, res) { * WHERE `id` IN(4569, 5631, 1238) * AND `last_updated` < '2015-01-01' */ - var last_query = qb.last_query(); + const last_query = qb.last_query(); }); ``` @@ -1404,14 +1404,14 @@ Here's a contrived example of how it might be used in an app made with the Expre ```javascript const express = require('express'); -var app = express(); +const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); app.post('/add_article', function(req, res) { const user_id = req.session.user_id; - var data = { + const data = { title: req.body.first_name, body: req.body.last_name, author: user_id, @@ -1423,7 +1423,7 @@ app.post('/add_article', function(req, res) { qb.release(); if (err) return console.error(err); - var page_data = { + const page_data = { article_id: res.insert_id, } return res.render('/article_manager', page_data); @@ -1457,7 +1457,7 @@ Object containing information about the result of the query. ```javascript const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); -var data = [ +const data = [ {name: 'MySQL', version: '5.5.40'}, {name: 'Mongo', version: '2.6.7' }, {name: 'Postgres', version: '8.4'} @@ -1505,7 +1505,7 @@ Object containing information about the result of the query. */ const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); -var data = {name: 'Postgres', version: '8.4'}; +const data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, function(err, res) { if (err) throw err; @@ -1520,7 +1520,7 @@ qb.insert_ignore('db_engines', data, function(err, res) { This time we'll do it with an `on_dupe` string ```javascript -var data = {name: 'Postgres', version: '8.4'}; +const data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { if (err) throw err; @@ -1568,7 +1568,7 @@ Here's a contrived example of how it might be used in an app made with the Expre ```javascript const express = require('express'); -var app = express(); +const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); @@ -1578,13 +1578,13 @@ app.post('/delete_comment/:id', function(req, res) { pool.get_connection(function(qb) { qb.get('comments', {id: id}, function(err, res) { if (err) return console.error(err); - var article_id = res.article_id; + const article_id = res.article_id; qb.delete('comments', {id: id}, function(err, res) { qb.release(); if (err) return console.error(err); - var page_data = { + const page_data = { num_removed: res.affected_rows, } return res.render('/article/' + article_id, page_data); @@ -1751,9 +1751,9 @@ const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool' pool.get_connection(function(qb) { qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { - var users = users; + const users = users; (function update_user() { - var user = users.shift(); + const user = users.shift(); user.username = user.username.replace(/\^|/,''); qb.update('users', user, {id: user.id}, function(err, res) { @@ -1798,7 +1798,7 @@ If you'd rather the engine not execute the query first, you can always use the a const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { - var id = 4531; + const id = 4531; qb.get('comments', {id: id}, function(err, res) { // SELECT * FROM `comments` WHERE `id` = 4531 console.log(qb.last_query()); @@ -1834,7 +1834,7 @@ Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -var sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; +const sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; qb.query(sql, function(err, res) { console.dir(res); }); @@ -1871,7 +1871,7 @@ Get certain details of a user account ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -var sql = qb +const sql = qb .select(['id','username','first_name','last_name']) .from('users') .like('username','k','after') @@ -1901,13 +1901,13 @@ Add a new user to a `users` table. ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); const crypto = require('crypto'); -var data = { +const data = { username: 'foobar', password: crypto.createHash('sha1').update('password').digest('hex'), first_name: 'Foo', last_name: 'Bar' }; -var sql = qb.set(data).get_compiled_insert('users'); +const sql = qb.set(data).get_compiled_insert('users'); // INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES ('foobar', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Foo', 'Bar') console.log(sql); @@ -1934,10 +1934,10 @@ Update the password of a user ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); const crypto = require('crypto'); -var data = { +const data = { password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), }; -var sql = qb +const sql = qb .where('id',4321) .set(data) .get_compiled_update('users'); @@ -1965,7 +1965,7 @@ Delete a user ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -var sql = qb.where('id',4321).get_compiled_delete('users'); +const sql = qb.where('id',4321).get_compiled_delete('users'); // DELETE FROM `users` WHERE `id` = 4321 console.log(sql); diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 254ba48..8e4528d 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -21,7 +21,7 @@ const Adapters = function(nqb) { //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); this.connection_settings = {}; - var that = this; + const that = this; // **************************************************************************** // Map generic NQB connection settings to node-mysql's format @@ -92,7 +92,7 @@ const Adapters = function(nqb) { // @return Object Adapter object // **************************************************************************** const Adapter = function(settings) { - var pool, connection; + let pool, connection; // If the Pool object is instatiating this Adapter, use it's connection if (settings && settings.pool) { @@ -104,10 +104,10 @@ const Adapters = function(nqb) { connection = new mysql.createConnection(that.connection_settings); } - var qb = get_query_builder(); - var qe = get_query_exec(qb, connection); + const qb = get_query_builder(); + const qe = get_query_exec(qb, connection); - var adapter = Object.assign({ + const adapter = Object.assign({ connection_settings: function() { return that.connection_settings; }, @@ -155,14 +155,14 @@ const Adapters = function(nqb) { }, get_connection: function(callback) { if (null === nqb.pool) { - var error_msg = "Connection pool not available!"; + const error_msg = "Connection pool not available!"; if (console && console.hasOwnProperty('error')) console.error(error_msg); throw new Error(error_msg); } nqb.pool.getConnection(function (err, connection) { if (err) throw err; - var adapter = new Adapter({ + const adapter = new Adapter({ pool: { pool: nqb.pool, connection: connection diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 7ed4ba7..a3d6d26 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -46,7 +46,7 @@ const QueryExec = function(qb, conn) { callback = table; } - var sql = qb.count(table); + const sql = qb.count(table); qb.reset_query(sql); exec(sql, function(err, row) { if (!err) { @@ -68,7 +68,7 @@ const QueryExec = function(qb, conn) { throw new Error("No callback function has been provided in your 'get' call!"); } - var sql = qb.get(table); + const sql = qb.get(table); qb.reset_query(sql); exec(sql,callback); }, @@ -80,13 +80,13 @@ const QueryExec = function(qb, conn) { if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { throw new Error("Second parameter of get_where() must be an object with key:value pairs."); } - var sql = qb.get_where(table,where); + const sql = qb.get_where(table,where); qb.reset_query(sql); exec(sql,callback); }, insert: function(table,set,callback,ignore,suffix) { - var sql = qb.insert(table,set,ignore,suffix); + const sql = qb.insert(table,set,ignore,suffix); qb.reset_query(sql); exec(sql,callback); }, @@ -96,13 +96,13 @@ const QueryExec = function(qb, conn) { callback = on_dupe; on_dupe = null; } - var sql = qb.insert_ignore(table,set,on_dupe); + const sql = qb.insert_ignore(table,set,on_dupe); qb.reset_query(sql); exec(sql,callback); }, insert_batch: function(table,set,callback) { - var sql = qb.insert_batch(table,set); + const sql = qb.insert_batch(table,set); qb.reset_query(sql); exec(sql,callback); }, @@ -120,7 +120,7 @@ const QueryExec = function(qb, conn) { where = null; } - var sql = qb.update(table,set,where); + const sql = qb.update(table,set,where); qb.reset_query(sql); exec(sql,callback); }, @@ -139,13 +139,13 @@ const QueryExec = function(qb, conn) { where = null; } - var sqls = qb.update_batch(table,set,index,where); - var results = null; - var errors = []; + const sqls = qb.update_batch(table,set,index,where); + const results = null; + const errors = []; // Execute each batch of (at least) 100 (function next_batch() { - var sql = sqls.shift(); + const sql = sqls.shift(); qb.reset_query(sql); exec(sql, function(err, res) { @@ -185,20 +185,20 @@ const QueryExec = function(qb, conn) { throw new Error("delete(): No callback function has been provided!"); } - var sql = qb.delete(table, where); + const sql = qb.delete(table, where); qb.reset_query(sql); exec(sql,callback); }, empty_table: function(table, callback) { - var sql = qb.empty_table(table,callback); + const sql = qb.empty_table(table,callback); qb.reset_query(sql); exec(sql,callback); }, truncate: function(table, callback) { - var sql = qb.truncate(table,callback); + const sql = qb.truncate(table,callback); qb.reset_query(sql); exec(sql,callback); }, diff --git a/drivers/template/connect.js b/drivers/template/connect.js index 38fe981..01859f6 100755 --- a/drivers/template/connect.js +++ b/drivers/template/connect.js @@ -13,7 +13,7 @@ * **/ -var connect, Standard, Pool, PoolCluster; +let connect, Standard, Pool, PoolCluster; // **************************************************************************** // Sets up a standard one-time connection (no pooling). This one is used by the @@ -23,7 +23,7 @@ var connect, Standard, Pool, PoolCluster; // @return Object Connection handle // **************************************************************************** Standard = function(settings) { - + }; // **************************************************************************** @@ -33,7 +33,7 @@ Standard = function(settings) { // @return Object Connection handle // **************************************************************************** Pool = function(settings) { - + }; // **************************************************************************** @@ -44,7 +44,7 @@ Pool = function(settings) { // @return Object Connection handle // **************************************************************************** PoolCluster = function(settings) { - + }; // **************************************************************************** @@ -55,9 +55,9 @@ PoolCluster = function(settings) { // **************************************************************************** connect = function(settings,type) { type = type || 'single'; - - var connection = null; - + + let connection = null; + switch(type) { case 'single': connection = Standard(settings); @@ -72,7 +72,7 @@ connect = function(settings,type) { throw new Error("Invalid connection type specified!"); break; } - + if (connection === null) { throw new Error("A connection could not be established!"); } diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js index 756ef0b..fa13f32 100755 --- a/examples/connection_pool_example.js +++ b/examples/connection_pool_example.js @@ -1,11 +1,11 @@ -var settings = { +const settings = { host: 'localhost', database: 'mydatabase', user: 'myuser', password: 'MyP@ssw0rd' }; const nqb = require('node-querybuilder'); -var pool = new QueryBuilder(settings, 'mysql', 'pool'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(function(qb) { qb.select('name', 'position') diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js index 52a7fd5..e45cbd9 100755 --- a/examples/connection_single_example.js +++ b/examples/connection_single_example.js @@ -1,11 +1,11 @@ -var settings = { +const settings = { host: 'localhost', database: 'mydatabase', user: 'myuser', password: 'MyP@ssw0rd' }; const nqb = require('node-querybuilder'); -var qb = new QueryBuilder(settings, 'mysql', 'single'); +const qb = new QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) diff --git a/index.js b/index.js index 8f7a162..26afbd7 100755 --- a/index.js +++ b/index.js @@ -87,7 +87,7 @@ const QueryBuilder = function(settings,driver,type) { // Fail if specified driver is inactive if (qb.driver_info.active === false) { - var err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) + const err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) + " of the " + qb.driver + " driver you are attempting to load is not currently available!"; throw new Error(err); } diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index da2553d..49cea3c 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -12,7 +12,7 @@ const check = function(done, f) { }; const connection_released = function(qb) { - var connection = qb.connection(); + const connection = qb.connection(); expect(connection._pool._freeConnections).to.have.length(0); qb.release(); expect(connection._pool._freeConnections).to.have.length(1); @@ -23,20 +23,20 @@ describe('QueryBuilder() - MySQL Adapter', function() { if (err) { console.error("Not connected!"); return; } console.log("connected!"); }; - var driver = 'mysql'; - var settings = { + const driver = 'mysql'; + const settings = { host: '127.0.0.1', database: 'mock_db', user: 'travis', version: '2.5.4', port: 3306 }; - var bad_user = Object.assign({},settings); bad_user.user = 'foobar'; - var bad_host = Object.assign({},settings); bad_host.host = 'nonlocalhost'; - var bad_password = Object.assign({},settings); bad_password.password = 'password'; - var bad_database = Object.assign({},settings); bad_database.database = 'bad_mock_db'; - var bad_port = Object.assign({},settings); bad_port.port = 1; - var bad_version = Object.assign({},settings); bad_version.version = 12; + const bad_user = Object.assign({},settings); bad_user.user = 'foobar'; + const bad_host = Object.assign({},settings); bad_host.host = 'nonlocalhost'; + const bad_password = Object.assign({},settings); bad_password.password = 'password'; + const bad_database = Object.assign({},settings); bad_database.database = 'bad_mock_db'; + const bad_port = Object.assign({},settings); bad_port.port = 1; + const bad_version = Object.assign({},settings); bad_version.version = 12; it('should exist', function() { should.exist(nqb.QueryBuilder); @@ -45,22 +45,22 @@ describe('QueryBuilder() - MySQL Adapter', function() { nqb.QueryBuilder.should.be.a('function'); }); it('should have all the QueryBuilder methods', function() { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - var children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; expect(qb).to.include.keys(children); }); it('should have all the QueryExec methods', function() { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - var children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; expect(qb).to.include.keys(children); }); it('should have all the miscellaneous methods', function() { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - var children = ['last_query','escape','empty_table','truncate']; + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const children = ['last_query','escape','empty_table','truncate']; expect(qb).to.include.keys(children); }); it('should establish a single connection given valid connection credentials', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); expect(qb, 'should have connect property').to.have.property('connect'); qb.connect(function(err) { check(done, function() { @@ -69,13 +69,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to disconnect from MySQL', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { should.exist(qb.disconnect); qb.disconnect.should.be.a('function'); qb.disconnect(function(err) { - var connection = qb.connection(); + const connection = qb.connection(); check(done, function() { expect(err, 'should be diconnected').to.not.be.instanceof(Error); @@ -93,8 +93,11 @@ describe('QueryBuilder() - MySQL Adapter', function() { expect(function() { nqb.QueryBuilder({},driver); }).to.throw(Error); }); it('should fail to establish a single connection given connection credentials with bad user', function(done) { + + let qb; + try { - var qb = nqb.QueryBuilder(bad_user, driver); + qb = nqb.QueryBuilder(bad_user, driver); } catch(e) { expect(e, 'should not get a connection').to.be.instanceof(Error); return; @@ -107,8 +110,11 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should fail to establish a single connection given connection credentials with bad host', function(done) { + + let qb; + try { - var qb = nqb.QueryBuilder(bad_host, driver); + qb = nqb.QueryBuilder(bad_host, driver); } catch(e) { expect(e, 'should not get a connection').to.be.instanceof(Error); return; @@ -121,8 +127,11 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should fail to establish a single connection given connection credentials with bad database', function(done) { + + let qb; + try { - var qb = nqb.QueryBuilder(bad_database, driver); + qb = nqb.QueryBuilder(bad_database, driver); } catch(e) { expect(e, 'should not get a connection').to.be.instanceof(Error); return; @@ -135,8 +144,11 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should fail to establish a single connection given connection credentials with bad password', function(done) { + + let qb; + try { - var qb = nqb.QueryBuilder(bad_password, driver); + qb = nqb.QueryBuilder(bad_password, driver); } catch(e) { expect(e, 'should not get a connection').to.be.instanceof(Error); return; @@ -149,8 +161,9 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should fail to establish a single connection given connection credentials with bad port', function(done) { + let qb; try { - var qb = nqb.QueryBuilder(bad_port, driver); + qb = nqb.QueryBuilder(bad_port, driver); } catch(e) { expect(e, 'should not get a connection').to.be.instanceof(Error); return; @@ -163,26 +176,26 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should fail to establish connection if an invalid driver is specified', function() { - var qb; + let qb; expect(function() { nqb.QueryBuilder(settings); }, 'no driver specified').to.throw(Error); expect(function() { nqb.QueryBuilder(settings,'foobar'); }, 'invalid driver specified').to.throw(Error); }); it('should fail to establish connection if an invalid driver version is specified', function() { - var qb; + let qb; expect(function() { nqb.QueryBuilder( Object.assign({}, settings), driver); }, 'valid driver version').to.not.throw(Error); expect(function() { nqb.QueryBuilder(bad_version, driver); }, 'invalid driver version').to.throw(Error); }); it('should allow us to retrieve our connection settings for reference', function(done) { - var conn_settings = Object.assign({}, settings, {password: undefined}); + const conn_settings = Object.assign({}, settings, {password: undefined}); delete conn_settings.version; - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.connection_settings); qb.connection_settings.should.be.a('function'); - var settings = qb.connection_settings(); + const settings = qb.connection_settings(); expect(settings).to.be.instanceof(Object); expect(settings).to.be.eql(conn_settings); qb.disconnect(); @@ -190,7 +203,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to escape certain values', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.escape); @@ -205,7 +218,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to escape identifiers the MySQL way', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { should.exist(qb.escape_id); @@ -218,7 +231,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow us to execute a query', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", function(err, res) { check(done, function() { @@ -230,7 +243,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should not be able to release a non-pooled connection', function(done) { - var qb = nqb.QueryBuilder(Object.assign({}, settings), driver); + const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(function(err) { check(done, function() { expect(function() { qb.release(); }).to.throw(Error); @@ -238,7 +251,7 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should create a connection pool object if asked', function() { - var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); + const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); expect(pool).to.be.instanceof.object; expect(pool).to.include.keys(['pool','get_connection','disconnect']); pool.pool.should.be.a('function'); @@ -246,8 +259,8 @@ describe('QueryBuilder() - MySQL Adapter', function() { pool.disconnect.should.be.a('function'); }); it('should create a QueryBuilder adapter when getting a connection from the pool', function(done) { - var qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); - var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); + const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); + const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { check(done, function() { expect(qb).to.include.keys(Object.keys(qb2)); @@ -255,14 +268,14 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); it('should allow one to release a connection from the pool', function(done) { - var qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); - var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); + const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); + const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { check(done, function() { connection_released(qb); }); }); }); it('should allow one use the same connection pool connection for multiple queries', function(done) { - var pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); + const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(function(qb) { qb.query('select * from `cities` where `city` = "Gainesville"', function(err, res) { diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index 7d8f970..09c690a 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -21,7 +21,7 @@ describe('get_compiled_select()', function() { }); it('should return a SQL string', function() { qb.reset_query(); - var sql = qb.get_compiled_select('galaxies'); + const sql = qb.get_compiled_select('galaxies'); sql.should.eql('SELECT * FROM `galaxies`'); }); }); @@ -35,7 +35,7 @@ describe('get_compiled_insert()', function() { }); it('should return a SQL string', function() { qb.reset_query(); - var sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); + const sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` (`foo`) VALUES ('bar')"); }); }); @@ -49,7 +49,7 @@ describe('get_compiled_update()', function() { }); it('should return a SQL string', function() { qb.reset_query(); - var sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); + const sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); sql.should.eql("UPDATE (`galaxies`) SET `foo` = 'bar' WHERE `id` = 45"); }); }); @@ -63,7 +63,7 @@ describe('get_compiled_delete()', function() { }); it('should return a SQL string', function() { qb.reset_query(); - var sql = qb.where('id',45).get_compiled_delete('galaxies'); + const sql = qb.where('id',45).get_compiled_delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` WHERE `id` = 45"); }); }); diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js index 7db463e..044b845 100755 --- a/test/mysql/tests-count.js +++ b/test/mysql/tests-count.js @@ -17,39 +17,39 @@ describe('count()', function() { }); it('should add a table to from_array when a table is supplied', function() { qb.reset_query(); - var sql = qb.count('galaxies'); + const sql = qb.count('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); it('should return a string', function() { qb.reset_query(); - var sql = qb.count('galaxies'); + const sql = qb.count('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should create a properly-escaped SELECT query', function() { qb.reset_query(); - var sql = qb.count('galaxies'); + const sql = qb.count('galaxies'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); it('should include WHERE statements', function() { qb.reset_query(); - var sql = qb.where({type:'spiral'}).count('galaxies'); + const sql = qb.where({type:'spiral'}).count('galaxies'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 'spiral'"); }); it('should work when table/view/procedure is provided earlier in chain but not in count() method', function() { qb.reset_query(); - var sql = qb.from('galaxies').count(); + const sql = qb.from('galaxies').count(); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); it('should work with multiple tables/views/stored procedures', function() { qb.reset_query(); - var sql = qb.from(['planets','galaxies']).count(); + const sql = qb.from(['planets','galaxies']).count(); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `planets`, `galaxies`"); }); it('should include any joins that were added in the chain', function() { qb.reset_query(); - var sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); + const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); }); }); diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 7eabfd7..647f52b 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -37,7 +37,7 @@ describe('delete()', function() { }); it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { qb.reset_query(); - var sql = qb.from(['galaxies','star_systems','planets']).delete(); + const sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql("DELETE FROM `galaxies`"); }); it('should add where conditions to where_array when conditions are supplied', function() { @@ -47,44 +47,44 @@ describe('delete()', function() { }); it('should return a string', function() { qb.reset_query(); - var sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); + const sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', function() { qb.reset_query(); - var sql = qb.delete('galaxies'); + const sql = qb.delete('galaxies'); sql.should.eql('DELETE FROM `galaxies`'); }); it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', function() { qb.reset_query(); - var sql = qb.delete('galaxies', {class: 'M'}); + const sql = qb.delete('galaxies', {class: 'M'}); sql.should.eql("DELETE FROM `galaxies` WHERE `class` = 'M'"); }); it('should use ONLY the FIRST table added previously via the from() method', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.delete(); + let sql = qb.delete(); sql.should.eql('DELETE FROM `galaxies`'); qb.reset_query(); - var sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql('DELETE FROM `galaxies`'); }); it('should accept where conditions added previously via the where() method', function() { qb.reset_query(); - var sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); + const sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` WHERE `created` >= 4600000000 AND `class` = 'M'"); }); it('should accept a limit on the number of rows deleted', function() { qb.reset_query(); - var sql = qb.limit(20).delete('galaxies'); + const sql = qb.limit(20).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` LIMIT 20"); }); it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', function() { qb.reset_query(); - var sql = qb.limit(20,10).delete('galaxies'); + const sql = qb.limit(20,10).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` LIMIT 10, 20"); }); }); diff --git a/test/mysql/tests-empty_table.js b/test/mysql/tests-empty_table.js index f3c504e..46d9bcb 100755 --- a/test/mysql/tests-empty_table.js +++ b/test/mysql/tests-empty_table.js @@ -11,14 +11,14 @@ describe('emtpy_table()', function() { }); it('should return a string', function() { qb.reset_query(); - var sql = qb.empty_table('galaxies'); + const sql = qb.empty_table('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should build a proper DELETE statement', function() { qb.reset_query(); - var sql = qb.empty_table('galaxies'); + const sql = qb.empty_table('galaxies'); sql.should.eql('DELETE FROM `galaxies`'); }); it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { @@ -44,7 +44,7 @@ describe('emtpy_table()', function() { }); it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { qb.reset_query(); - var sql = qb.from(['galaxies','star_systems','planets']).empty_table(); + const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); sql.should.eql("DELETE FROM `galaxies`"); }); }); \ No newline at end of file diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index dfe00b7..1687756 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -21,113 +21,113 @@ describe('get()', function() { }); it('should return a string', function() { qb.reset_query(); - var sql = qb.get('galaxies'); + const sql = qb.get('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', function() { qb.reset_query(); - var sql = qb.get('galaxies'); + const sql = qb.get('galaxies'); sql.should.eql('SELECT * FROM `galaxies`'); }); it('should properly handle alias if provided in table string', function() { qb.reset_query(); - var sql = qb.get('galaxies g'); + const sql = qb.get('galaxies g'); sql.should.eql('SELECT * FROM `galaxies` `g`'); }); it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', function() { qb.reset_query(); - var sql = qb.select(['id','name']).get('galaxies'); + const sql = qb.select(['id','name']).get('galaxies'); sql.should.eql("SELECT `id`, `name` FROM `galaxies`"); }); it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', function() { qb.reset_query(); - var sql = qb.where('class','M').get('galaxies'); + const sql = qb.where('class','M').get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` WHERE `class` = 'M'"); }); it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', function() { qb.reset_query(); - var sql = qb.get(['galaxies','star_systems','planets']); + const sql = qb.get(['galaxies','star_systems','planets']); sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', function() { qb.reset_query(); - var sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); + const sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); sql.should.eql("SELECT * FROM `galaxies`, `star_systems`, `planets` WHERE `class` = 'M'"); }); it('should use tables added previously via the from() method', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.get(); + let sql = qb.get(); sql.should.eql('SELECT * FROM `galaxies`'); qb.reset_query(); - var sql = qb.from(['galaxies','star_systems','planets']).get(); + sql = qb.from(['galaxies','star_systems','planets']).get(); sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); it('should accept where conditions added previously via the where() method', function() { qb.reset_query(); - var sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); + const sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` WHERE `created` >= 4600000000 AND `classification` = 'M'"); }); it('should accept a limit on the number of rows selected', function() { qb.reset_query(); - var sql = qb.limit(20).get('galaxies'); + const sql = qb.limit(20).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` LIMIT 20"); }); it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', function() { qb.reset_query(); - var sql = qb.limit(20,10).get('galaxies'); + const sql = qb.limit(20,10).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` LIMIT 10, 20"); }); it('should include the DISTINCT keyword if the distinct() method is called', function() { qb.reset_query(); - var sql = qb.distinct().select(['id','name']).get('galaxies'); + const sql = qb.distinct().select(['id','name']).get('galaxies'); sql.should.eql("SELECT DISTINCT `id`, `name` FROM `galaxies`"); }); it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', function() { qb.reset_query(); // MIN - var sql = qb.select_min('size','min_size').get('galaxies'); + let sql = qb.select_min('size','min_size').get('galaxies'); sql.should.eql("SELECT MIN(`size`) AS min_size FROM `galaxies`"); qb.reset_query(); // MAX - var sql = qb.select_max('size','max_size').get('galaxies'); + sql = qb.select_max('size','max_size').get('galaxies'); sql.should.eql("SELECT MAX(`size`) AS max_size FROM `galaxies`"); qb.reset_query(); // AVG - var sql = qb.select_avg('size','avg_size').get('galaxies'); + sql = qb.select_avg('size','avg_size').get('galaxies'); sql.should.eql("SELECT AVG(`size`) AS avg_size FROM `galaxies`"); qb.reset_query(); // SUM - var sql = qb.select_sum('size','total_size').get('galaxies'); + sql = qb.select_sum('size','total_size').get('galaxies'); sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); }); it('should include any joins that were added in the chain', function() { qb.reset_query(); - var sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) + const sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) .join('galaxies g','g.id=s.galaxy_id','left') .get('star_systems s'); sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); }); it('should include any GROUP BY statements added using the group_by() method.', function() { qb.reset_query(); - var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); + const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); }); it('should add the ORDER BY clause of the order_by() method was called in the chain', function() { qb.reset_query(); - var sql = qb.order_by('size').get('galaxies'); + const sql = qb.order_by('size').get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` ORDER BY `size` ASC"); }); it('should include any HAVING clauses added using the having() method', function() { qb.reset_query(); - var sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); + const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); }); }); @@ -166,14 +166,14 @@ describe('get_where()', function() { }); it('should return a string', function() { qb.reset_query(); - var sql = qb.get('galaxies', {type: 'spiral'}); + const sql = qb.get('galaxies', {type: 'spiral'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should add table(s) to from_array and where items to where_array', function() { qb.reset_query(); - var sql = qb.get_where('galaxies', {type: 'spiral'}); + const sql = qb.get_where('galaxies', {type: 'spiral'}); qb.from_array.should.eql(['`galaxies`']); qb.where_array.should.eql(["`type` = 'spiral'"]); sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js index 952395f..50b9135 100755 --- a/test/mysql/tests-having.js +++ b/test/mysql/tests-having.js @@ -52,7 +52,7 @@ describe('having()', function() { }); it('should accept an object of conditions and prepend AND to each condition following the first one', function() { qb.reset_query(); - var object = {planet_class: 'M', sentient_life: 1}; + const object = {planet_class: 'M', sentient_life: 1}; object['planet_order <='] = 3; qb.having(object); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 48e1475..b68a839 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -2,8 +2,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -var test_data = {id:3, name:'Milky Way', type: 'spiral'}; -var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; +const test_data = {id:3, name:'Milky Way', type: 'spiral'}; +const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; // table, data, callback, ignore, suffix @@ -67,44 +67,44 @@ describe('insert()', function() { }); it('should allow for an empty data parameter', function() { qb.reset_query(); - var sql = qb.insert('galaxies'); + const sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.insert(); + const sql = qb.insert(); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing values set in in set_array', function() { qb.reset_query(); qb.set(test_data); - var sql = qb.insert('galaxies'); + const sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); - var sql = qb.insert(); + const sql = qb.insert(); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); - var sql = qb.insert('galaxies', test_data); + const sql = qb.insert('galaxies', test_data); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); - var sql = qb.insert('galaxies', test_data_set); - var sql_b = qb.insert_batch('galaxies', test_data_set); + const sql = qb.insert('galaxies', test_data_set); + const sql_b = qb.insert_batch('galaxies', test_data_set); sql.should.eql(sql_b); }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); const func = function() { console.log("foo"); }; - var regex = /foobar/; - var arr = [1,2,3]; - var obj = {foo: 'bar'}; + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; expect(function() { qb.insert('galaxies',{id: 1}); }, 'number in data').to.not.throw(Error); expect(function() { qb.insert('galaxies',{id: 'foo'}); }, 'string in data').to.not.throw(Error); @@ -130,23 +130,23 @@ describe('insert_ignore()', function() { }); it('should create an INSERT IGNORE statement', function() { qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data); + const sql = qb.insert_ignore('galaxies', test_data); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data); - var sql_b = qb.insert('galaxies', test_data, true); + const sql = qb.insert_ignore('galaxies', test_data); + const sql_b = qb.insert('galaxies', test_data, true); sql.should.eql(sql_b); }); it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data_set); + const sql = qb.insert_ignore('galaxies', test_data_set); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index 4394e29..fc305c8 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -2,8 +2,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -var test_where = {id:3}; -var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; +const test_where = {id:3}; +const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; describe('insert_batch()', function() { it('should exist', function() { @@ -41,7 +41,7 @@ describe('insert_batch()', function() { }); it('should build a proper batch INSERT string', function() { qb.reset_query(); - var sql = qb.insert_batch('galaxies', test_data); + const sql = qb.insert_batch('galaxies', test_data); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); it('should only accept an array as the second parameter', function() { @@ -71,21 +71,21 @@ describe('insert_batch()', function() { }); it('should allow for an empty data parameter', function() { qb.reset_query(); - var sql = qb.insert_batch('galaxies',[]); + const sql = qb.insert_batch('galaxies',[]); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.insert_batch(null,[]); + const sql = qb.insert_batch(null,[]); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { qb.reset_query(); const func = function() { console.log("foo"); }; - var regex = /foobar/; - var arr = [1,2,3]; - var obj = {foo: 'bar'}; + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; expect(function() { qb.insert_batch('galaxies',[{id: func}]); }, 'function in data').to.throw(Error); expect(function() { qb.insert_batch('galaxies',[{id: regex}]); }, 'regex in data').to.throw(Error); @@ -97,7 +97,7 @@ describe('insert_batch()', function() { }); it('should support insert ignore statements', function() { qb.reset_query(); - var sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); }); \ No newline at end of file diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index d681626..e655a48 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -16,7 +16,7 @@ describe('join()', function() { qb.join_array.should.be.empty; }); it('should require a string to be passed as first parameter', function() { - var invalid_match = /must provide a table/; + const invalid_match = /must provide a table/; expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); @@ -39,8 +39,8 @@ describe('join()', function() { qb.join_array.should.eql(['JOIN `universe` `u` ']); }); it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', function() { - var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; - var invalid_direction = /Invalid join direction provided as third parameter/; + const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; + const invalid_direction = /Invalid join direction provided as third parameter/; expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); @@ -56,7 +56,7 @@ describe('join()', function() { }); it('should allow valid join direction to be passed in third parameter', function() { // NOTE: A lot of this functionality was already tested when testing second param - var invalid_direction = /Invalid join direction provided as third parameter/; + const invalid_direction = /Invalid join direction provided as third parameter/; expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index 8428767..5659f7d 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -16,9 +16,9 @@ describe('select()', function() { qb.select_array.should.be.empty; }); it('should require an array or string to be passed as first parameter', function() { - var invalid_match = /requires a string or array/; - var empty_str_match = /string is empty/; - var empty_arr_match = /array is empty/; + const invalid_match = /requires a string or array/; + const empty_str_match = /string is empty/; + const empty_arr_match = /array is empty/; expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); @@ -165,9 +165,9 @@ describe('select()', function() { }); }); -var prefixes = ['min','max','avg','sum']; -for (var i in prefixes) { - var type = prefixes[i]; +const prefixes = ['min','max','avg','sum']; +for (const i in prefixes) { + const type = prefixes[i]; describe('select_' + type+'()', function() { it('should exist', function() { should.exist(qb['select_' + type]); diff --git a/test/mysql/tests-truncate.js b/test/mysql/tests-truncate.js index 72bc745..fe75e9a 100755 --- a/test/mysql/tests-truncate.js +++ b/test/mysql/tests-truncate.js @@ -11,14 +11,14 @@ describe('truncate()', function() { }); it('should return a string', function() { qb.reset_query(); - var sql = qb.truncate('galaxies'); + const sql = qb.truncate('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); it('should build a proper truncate statement', function() { qb.reset_query(); - var sql = qb.truncate('galaxies'); + const sql = qb.truncate('galaxies'); sql.should.eql('TRUNCATE `galaxies`'); }); }); \ No newline at end of file diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 8b32c42..ec3513e 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -2,9 +2,9 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -var test_where = {id:3}; -var test_data = {name:'Milky Way', type: 'spiral'}; -var test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; +const test_where = {id:3}; +const test_data = {name:'Milky Way', type: 'spiral'}; +const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; // table, data, callback, ignore, suffix @@ -74,39 +74,39 @@ describe('update()', function() { it('should utilize pre-existing tables set in from_array', function() { qb.reset_query(); qb.from('galaxies'); - var sql = qb.update(null, test_data, test_where); + const sql = qb.update(null, test_data, test_where); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); }); it('should utilize pre-existing value set in in set_array', function() { qb.reset_query(); qb.set(test_data); - var sql = qb.update('galaxies'); + const sql = qb.update('galaxies'); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { qb.reset_query(); qb.from('galaxies').set(test_data); - var sql = qb.update(); + const sql = qb.update(); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should accept a non-empty object for the data parameter', function() { qb.reset_query(); - var sql = qb.update('galaxies', test_data); + const sql = qb.update('galaxies', test_data); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { qb.reset_query(); - var sql = qb.update('galaxies', test_data_set); + const sql = qb.update('galaxies', test_data_set); qb.reset_query(); - var sql_b = qb.update_batch('galaxies', test_data_set, 'id'); + const sql_b = qb.update_batch('galaxies', test_data_set, 'id'); sql.should.eql(sql_b); }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); const func = function() { console.log("foo"); }; - var regex = /foobar/; - var arr = [1,2,3]; - var obj = {foo: 'bar'}; + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; qb.reset_query(); expect(function() { qb.update('galaxies',{id: func}); }, 'function in data').to.throw(Error); qb.reset_query(); expect(function() { qb.update('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); @@ -120,7 +120,7 @@ describe('update()', function() { /* it('should support insert ignore statements', function() { qb.reset_query(); - var sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); */ }); \ No newline at end of file diff --git a/test/mysql/tests-update_batch.js b/test/mysql/tests-update_batch.js index 9dd6845..7592623 100755 --- a/test/mysql/tests-update_batch.js +++ b/test/mysql/tests-update_batch.js @@ -2,8 +2,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -var test_where = {id:3}; -var test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; +const test_where = {id:3}; +const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; describe('update_batch()', function() { it('should exist', function() { @@ -14,7 +14,7 @@ describe('update_batch()', function() { }); it('should build a proper batch UPDATE string', function() { qb.reset_query(); - var sql = qb.update_batch('galaxies', test_data, 'id'); + const sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); }); }); \ No newline at end of file diff --git a/tests.md b/tests.md index 6b8e0a2..b247846 100755 --- a/tests.md +++ b/tests.md @@ -6,7 +6,7 @@ mocha --timeout 5000 --reporter markdown test/*.js - [select()](#select) - [where()](#where) - + # from() should exist. @@ -184,7 +184,7 @@ qb.fromArray.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); actually exists and can be initialized. ```js -var qb = new QueryBuilder(); +const qb = new QueryBuilder(); qb.should.be.instanceOf(Object); ``` @@ -217,7 +217,7 @@ qb.joinArray.should.be.empty; should require a string to be passed as first parameter. ```js -var invalid_match = /must provide a table/; +const invalid_match = /must provide a table/; expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); @@ -249,8 +249,8 @@ qb.joinArray.should.eql(['JOIN `universe` `u` ']); should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided. ```js -var invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; -var invalid_direction = /Invalid join direction provided as third parameter/; +const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; +const invalid_direction = /Invalid join direction provided as third parameter/; expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); @@ -269,7 +269,7 @@ should allow valid join direction to be passed in third parameter. ```js // NOTE: A lot of this functionality was already tested when testing second param -var invalid_direction = /Invalid join direction provided as third parameter/; +const invalid_direction = /Invalid join direction provided as third parameter/; expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); @@ -366,9 +366,9 @@ qb.selectArray.should.be.empty; should require an array or string to be passed as first parameter. ```js -var invalid_match = /requires a string or array/; -var empty_str_match = /string is empty/; -var empty_arr_match = /array is empty/; +const invalid_match = /requires a string or array/; +const empty_str_match = /string is empty/; +const empty_arr_match = /array is empty/; expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); @@ -551,11 +551,11 @@ should not allow subqueries or functions with commas in them without the second ```js qb.resetQuery(); -expect(function() { +expect(function() { qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); }).to.throw(Error); -expect(function() { +expect(function() { qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); }).to.not.throw(Error); ``` From db26335d32975338b98e6cfc4e9a96225cccbdae Mon Sep 17 00:00:00 2001 From: DovidM Date: Thu, 15 Jun 2017 08:10:26 -0400 Subject: [PATCH 111/200] Changed .match to .test since only looking for boolean --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 26afbd7..bf64706 100755 --- a/index.js +++ b/index.js @@ -69,7 +69,7 @@ const QueryBuilder = function(settings,driver,type) { } // Determine version of driver to use - if (qb.settings.hasOwnProperty('version') && (typeof qb.settings.version).match(/^(string|number)$/i)) { + if (qb.settings.hasOwnProperty('version') && /^(string|number)$/i.test(typeof qb.settings.version)) { qb.driver_version = qb.settings.version; delete qb.settings.version; } From c6a84518ba913ee891e7def895821dc391531281 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 14:18:42 -0400 Subject: [PATCH 112/200] Use built in method to check if variable is an array --- drivers/mysql/query_builder.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index efe4d9c..b9454d7 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -99,7 +99,7 @@ const QueryBuilder = function() { } } } - else if (Object.prototype.toString.call(a) === Object.prototype.toString.call([])) { + else if (Array.isArray(a)) { while (a.length > 0) { a.pop(); } @@ -501,7 +501,7 @@ const QueryBuilder = function() { escape = (typeof escape === 'boolean' ? escape : true); - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { return this._where_in(key, value, false, 'AND '); } return this._where(key, value, 'AND ', escape); @@ -510,7 +510,7 @@ const QueryBuilder = function() { or_where: function(key, value=null, escape) { escape = (typeof escape === 'boolean' ? escape : true); - if (typeof key === 'string' && typeof value === 'object' && Object.prototype.toString.call(value) === Object.prototype.toString.call([]) && value.length > 0) { + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { return this._where_in(key, value, false, 'OR '); } return this._where(key, value, 'OR ', escape); @@ -571,7 +571,7 @@ const QueryBuilder = function() { for (let k in key) { let v = key[k]; - if (typeof v === 'object' && Object.prototype.toString.call(v) === Object.prototype.toString.call([]) && v.length > 0) { + if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { return this._where_in(k,v,false,type,escape); } @@ -847,7 +847,7 @@ const QueryBuilder = function() { throw new Error("Your select string is empty!"); } } - else if (Object.prototype.toString.call(select) === Object.prototype.toString.call([])) { + else if (Array.isArray(select)) { if (select.length == 0) { throw new Error("Your select array is empty!"); } @@ -993,7 +993,7 @@ const QueryBuilder = function() { let m; let key_array = {}; const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - const key_is_array = Object.prototype.toString.call(key) === Object.prototype.toString.call([]); + const key_is_array = Array.isArray(key); if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... @@ -1216,7 +1216,7 @@ const QueryBuilder = function() { throw new Error("insert(): Invalid data provided to insert into database!"); } - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + if (Array.isArray(set)) { return this.insert_batch(table, set, ignore, suffix); } @@ -1398,7 +1398,7 @@ const QueryBuilder = function() { set = set || null; // Send to batch_update if the data param is an array - if (Object.prototype.toString.call(set) === Object.prototype.toString.call([])) { + if (Array.isArray(set)) { let index = null; if (set.length > 0) { if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { From 03d4c7b64fc2c01e94c593fd2e642aa88d9c2ef9 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 14:29:44 -0400 Subject: [PATCH 113/200] Added arrow functions --- drivers/mysql/query_builder.js | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index b9454d7..2fc5af1 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,7 +1,7 @@ const QueryBuilder = function() { // ------------------------------ GENERIC FUNCTIONS ------------------------------// - const array_values = function(item) { + const array_values = item => { const keys = Object.keys(item); const length = keys.length; const values = Array(length); @@ -11,7 +11,7 @@ const QueryBuilder = function() { return values; }; - const prepare_for_limit_and_offset = function(item, type = 'limit') { + const prepare_for_limit_and_offset = (item, type = 'limit') => { type = type.toLowerCase(); @@ -52,7 +52,7 @@ const QueryBuilder = function() { return item; } - const extract_having_parts = function(key,key_array) { + const extract_having_parts = (key,key_array) => { let m; key = key.trim().replace(/\s+/g,' '); const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting @@ -87,7 +87,7 @@ const QueryBuilder = function() { } // Simply setting all properties to [] causes reference issues in the parent class. - const clear_array = function(a,debug) { + const clear_array = (a,debug) => { if (debug === true) { console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); console.dir(a); @@ -111,7 +111,7 @@ const QueryBuilder = function() { }; // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - const track_aliases = function(qb,table) { + const track_aliases = (qb,table) => { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (const i in table) { const t = table[i]; @@ -141,7 +141,7 @@ const QueryBuilder = function() { } }; - const create_aliases_from_table = function(item) { + const create_aliases_from_table = item => { if (item.indexOf('.') !== -1) { return item.split('.').reverse()[0]; } @@ -149,7 +149,7 @@ const QueryBuilder = function() { return item; }; - const escape_identifiers = function(item = '*') { + const escape_identifiers = (item = '*') => { if (item === '*') { return item; } @@ -176,7 +176,7 @@ const QueryBuilder = function() { return str.replace(/[`]+/g,'`'); }; - const protect_identifiers = function(qb,item,protect_identifiers) { + const protect_identifiers = (qb,item,protect_identifiers) => { if (item === '') return item; protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); @@ -277,7 +277,7 @@ const QueryBuilder = function() { return true; }; - const qb_escape = function(qb,str) { + const qb_escape = (qb,str) => { const SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); const do_escape = SqlString.escape; @@ -294,7 +294,7 @@ const QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// - const build_where_clause = function(qb) { + const build_where_clause = qb => { let sql = ''; if(qb.where_array.length > 0) { sql += " WHERE "; @@ -303,7 +303,7 @@ const QueryBuilder = function() { return sql; }; - const build_from_clause = function(qb) { + const build_from_clause = qb => { let sql = ''; if(qb.from_array.length > 0) { sql += " FROM "; @@ -314,14 +314,14 @@ const QueryBuilder = function() { return sql; }; - const build_join_string = function(qb) { + const build_join_string = qb => { let sql = ''; sql += qb.join_array.join(' '); if(sql.length > 0) sql = ' ' + sql; return sql; }; - const build_group_by_clause = function(qb) { + const build_group_by_clause = qb => { if (qb.group_by_array.length <= 0) return ''; let sql = ' GROUP BY '; @@ -329,7 +329,7 @@ const QueryBuilder = function() { return sql; }; - const build_having_clause = function(qb) { + const build_having_clause = qb => { if (qb.having_array.length <= 0) return ''; let sql = ' HAVING '; @@ -337,7 +337,7 @@ const QueryBuilder = function() { return sql; }; - const build_order_by_clause = function(qb) { + const build_order_by_clause = qb => { if (qb.order_by_array.length <= 0) return ''; let sql = ' ORDER BY '; @@ -346,7 +346,7 @@ const QueryBuilder = function() { return sql; }; - const build_limit_clause = function(sql, limit, offset) { + const build_limit_clause = (sql, limit, offset) => { if (!limit) return sql; sql += ' '; @@ -360,7 +360,7 @@ const QueryBuilder = function() { return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - const compile_select = function(qb) { + const compile_select = qb => { const distinct_clause = qb.distinct_clause[0] || ''; let sql = 'SELECT ' + distinct_clause; if (qb.select_array.length === 0) { @@ -383,7 +383,7 @@ const QueryBuilder = function() { return sql; }; - const compile_delete = function(qb) { + const compile_delete = qb => { if (qb.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); return ''; @@ -398,7 +398,7 @@ const QueryBuilder = function() { return build_limit_clause(sql,limit_to,offset_val); }; - const compile_update = function(qb) { + const compile_update = qb => { const valstr = []; for (const i in qb.set_array) { const key = Object.keys(qb.set_array[i])[0]; @@ -426,7 +426,7 @@ const QueryBuilder = function() { return build_limit_clause(sql, limit_to, offset_val); }; - const compile_insert = function(qb, ignore, suffix='') { + const compile_insert = (qb, ignore, suffix='') => { const keys = []; const values = []; @@ -537,7 +537,7 @@ const QueryBuilder = function() { const filters = key.split(/\s+(AND|OR)\s+/i); if (filters.length > 1) { const that = this; - const parse_statement = function(statement,joiner) { + const parse_statement = (statement,joiner) => { const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); if (parsed.length >= 4) { const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); From 2790b24ea4cd28309ee89fb3919cb0dcedafc8ae Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 14:44:20 -0400 Subject: [PATCH 114/200] Replaced all functions that did not complain when I did it, with arrows --- README.md | 4 ++-- drivers/mysql/adapters.js | 14 +++++++------- drivers/mysql/query_exec.js | 4 ++-- drivers/template/connect.js | 8 ++++---- index.js | 8 ++++---- test/mysql/tests-adapter.js | 4 ++-- test/mysql/tests-insert.js | 2 +- test/mysql/tests-insert_batch.js | 2 +- test/mysql/tests-update.js | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index fb5cf22..2c12ec6 100755 --- a/README.md +++ b/README.md @@ -1277,8 +1277,8 @@ const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool' app.post('/update_account', function(req, res) { const user_id = req.session.user_id; - const sanitize_name = function(name) { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; - const sanitize_age = function(age) { return age.replace(/[^0-9]+$/,'').trim(); }; + const sanitize_name = name => { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; + const sanitize_age = age => { return age.replace(/[^0-9]+$/,'').trim(); }; const data = { first_name: sanitize_name(req.body.first_name), diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 8e4528d..4618e26 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,4 +1,4 @@ -const Adapters = function(nqb) { +const Adapters = nqb => { // Load MySQL Driver const mysql = require('mysql'); @@ -29,7 +29,7 @@ const Adapters = function(nqb) { // NOTE: MySQL connection settings names are the same as Node Querybuilder, // it's just good practice to go ahead and do this in case things change. // **************************************************************************** - const map_connection_settings = function() { + const map_connection_settings = () => { that.connection_settings = { host: nqb.settings.host, user: nqb.settings.user, @@ -62,7 +62,7 @@ const Adapters = function(nqb) { // @param Object qb The QueryBuilder object // @return Object QueryBuilder object // **************************************************************************** - const get_query_builder = function() { + const get_query_builder = () => { try { return require('./query_builder.js').QueryBuilder(); } catch(e) { @@ -78,7 +78,7 @@ const Adapters = function(nqb) { // @param Object conn The Connnection object // @return Object QueryExec Object // **************************************************************************** - const get_query_exec = function(qb, conn) { + const get_query_exec = (qb, conn) => { try { return require('./query_exec.js').QueryExec(qb, conn); } catch(e) { @@ -148,7 +148,7 @@ const Adapters = function(nqb) { // **************************************************************************** const Pool = function() { // Return Pool Object - const return_pool = function() { + const return_pool = () => { return { pool: function() { return nqb.pool; @@ -205,7 +205,7 @@ const Adapters = function(nqb) { // ----- // @return Object Adapter object // **************************************************************************** - const Cluster = function() { + const Cluster = () => { }; @@ -215,7 +215,7 @@ const Adapters = function(nqb) { // @param // @return // **************************************************************************** - const determine_adapter = function() { + const determine_adapter = () => { switch(nqb.connection_type) { case 'cluster': return new Cluster(); diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index a3d6d26..59f26ca 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -4,9 +4,9 @@ // @param Object qb The QueryBuilder object // @param Object adapter The connection adapter object // **************************************************************************** -const QueryExec = function(qb, conn) { +const QueryExec = function (qb, conn) { - const exec = function(sql, callback) { + const exec = (sql, callback) => { if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { conn.query(sql, function(err, results) { // Standardize some important properties diff --git a/drivers/template/connect.js b/drivers/template/connect.js index 01859f6..7523a38 100755 --- a/drivers/template/connect.js +++ b/drivers/template/connect.js @@ -22,7 +22,7 @@ let connect, Standard, Pool, PoolCluster; // @param Object settings Connection settings // @return Object Connection handle // **************************************************************************** -Standard = function(settings) { +Standard = settings => { }; @@ -32,7 +32,7 @@ Standard = function(settings) { // @param Object settings Connection settings // @return Object Connection handle // **************************************************************************** -Pool = function(settings) { +Pool = settings => { }; @@ -43,7 +43,7 @@ Pool = function(settings) { // @param Object settings Connection settings // @return Object Connection handle // **************************************************************************** -PoolCluster = function(settings) { +PoolCluster = settings => { }; @@ -53,7 +53,7 @@ PoolCluster = function(settings) { // @param Object settings Connection settings (including the type) // @return Object Connection handle // **************************************************************************** -connect = function(settings,type) { +connect = function (settings,type) { type = type || 'single'; let connection = null; diff --git a/index.js b/index.js index bf64706..6b9b731 100755 --- a/index.js +++ b/index.js @@ -39,7 +39,7 @@ * **/ const lo_assign = require('lodash.assign'); -const QueryBuilder = function(settings,driver,type) { +const QueryBuilder = (settings,driver,type) => { this.settings = (settings ? lo_assign({}, settings) : {}); this.driver = driver || 'mysql'; @@ -55,7 +55,7 @@ const QueryBuilder = function(settings,driver,type) { // @param Object qb The QueryBuilder object // @return Object Modified QueryBuilder object // **************************************************************************** - const get_driver_info = function(qb) { + const get_driver_info = qb => { // A driver must be specified if (typeof driver !== 'string') { throw new Error("No database driver specified!"); @@ -100,7 +100,7 @@ const QueryBuilder = function(settings,driver,type) { // @param Object qb The QueryBuilder object // @return Object Modified QueryBuilder object // **************************************************************************** - const get_connection_type = function(qb) { + const get_connection_type = qb => { if (Object.keys(qb.drivers[qb.driver].connection_types).indexOf(qb.connection_type) === -1) { throw new Error("You have specified a invalid database connection method: " + qb.connection_type); } @@ -116,7 +116,7 @@ const QueryBuilder = function(settings,driver,type) { // ----- // @return VOID This method responds asychronously via a callback // **************************************************************************** - const get_adapter = function(qb) { + const get_adapter = qb => { try { const adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); return adapter; diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 49cea3c..bde3669 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -11,7 +11,7 @@ const check = function(done, f) { } }; -const connection_released = function(qb) { +const connection_released = qb => { const connection = qb.connection(); expect(connection._pool._freeConnections).to.have.length(0); qb.release(); @@ -19,7 +19,7 @@ const connection_released = function(qb) { }; describe('QueryBuilder() - MySQL Adapter', function() { - const on_connect = function(err) { + const on_connect = err => { if (err) { console.error("Not connected!"); return; } console.log("connected!"); }; diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index b68a839..fcfdb6c 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -101,7 +101,7 @@ describe('insert()', function() { }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); - const func = function() { console.log("foo"); }; + const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index fc305c8..b02d90f 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -82,7 +82,7 @@ describe('insert_batch()', function() { }); it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { qb.reset_query(); - const func = function() { console.log("foo"); }; + const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index ec3513e..eaa1ebe 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -103,7 +103,7 @@ describe('update()', function() { }); it('should fail if any invalid values are passed in the data object.', function() { qb.reset_query(); - const func = function() { console.log("foo"); }; + const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; From f4fd1357f302d37129d471de619c5a937671f685 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 14:52:20 -0400 Subject: [PATCH 115/200] Traded some of that for this (literally:)) --- drivers/mysql/adapters.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 4618e26..7de9407 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,4 +1,4 @@ -const Adapters = nqb => { +const Adapters = function(nqb) { // Load MySQL Driver const mysql = require('mysql'); @@ -30,17 +30,17 @@ const Adapters = nqb => { // it's just good practice to go ahead and do this in case things change. // **************************************************************************** const map_connection_settings = () => { - that.connection_settings = { + this.connection_settings = { host: nqb.settings.host, user: nqb.settings.user, password: nqb.settings.password } if (nqb.settings.hasOwnProperty('database')) { - that.connection_settings.database = nqb.settings.database; + this.connection_settings.database = nqb.settings.database; delete nqb.settings.database } if (nqb.settings.hasOwnProperty('port')) { - that.connection_settings.port = nqb.settings.port; + this.connection_settings.port = nqb.settings.port; delete nqb.settings.port } @@ -50,7 +50,7 @@ const Adapters = nqb => { delete nqb.settings.password // Merge any driver-specific settings into connection settings - that.connection_settings = Object.assign(that.connection_settings, nqb.settings); + this.connection_settings = Object.assign(this.connection_settings, nqb.settings); } map_connection_settings(); @@ -219,14 +219,11 @@ const Adapters = nqb => { switch(nqb.connection_type) { case 'cluster': return new Cluster(); - break; case 'pool': return new Pool(); - break; case 'single': default: return new Adapter({}); - break; } } From 58e23b5cfbec8fa68eba3899effb60beb6e65c56 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 15:16:09 -0400 Subject: [PATCH 116/200] Switched tons of functions to arrows --- HISTORY.md | 4 +- README.md | 82 ++++++------- drivers/mysql/adapters.js | 4 +- drivers/mysql/query_builder.js | 2 +- examples/connection_pool_example.js | 2 +- test/mysql/tests-adapter.js | 130 ++++++++++---------- test/mysql/tests-compilation_methods.js | 36 +++--- test/mysql/tests-count.js | 28 ++--- test/mysql/tests-delete.js | 58 ++++----- test/mysql/tests-distinct.js | 8 +- test/mysql/tests-empty_table.js | 44 +++---- test/mysql/tests-from.js | 48 ++++---- test/mysql/tests-general.js | 4 +- test/mysql/tests-get.js | 104 ++++++++-------- test/mysql/tests-groupby.js | 42 +++---- test/mysql/tests-having.js | 62 +++++----- test/mysql/tests-insert.js | 132 ++++++++++---------- test/mysql/tests-insert_batch.js | 104 ++++++++-------- test/mysql/tests-join.js | 114 +++++++++--------- test/mysql/tests-like.js | 154 ++++++++++++------------ test/mysql/tests-limit.js | 80 ++++++------ test/mysql/tests-offset.js | 50 ++++---- test/mysql/tests-orderby.js | 92 +++++++------- test/mysql/tests-select.js | 96 +++++++-------- test/mysql/tests-set.js | 92 +++++++------- test/mysql/tests-truncate.js | 10 +- test/mysql/tests-update.js | 108 ++++++++--------- test/mysql/tests-update_batch.js | 8 +- test/mysql/tests-where.js | 82 ++++++------- test/mysql/tests-where_in.js | 132 ++++++++++---------- tests.md | 104 ++++++++-------- 31 files changed, 1008 insertions(+), 1008 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index a113a43..f2295a5 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -14,9 +14,9 @@ you spot any mistakes. * Added the ability to do `LIKE` statements directly (new methods: `like()`, `not_like()`, `or_like()`, `or_not_like()`) * Restored ability to do `WHERE IN(...)` statements by passing an array as the second param to `where()` * Added the ability to do `[OR] WHERE [NOT] IN(...)` statements directly (new methods: `where_in()`, `or_where_in()`, `where_not_in()`, `or_where_not_in()`) -* Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(function() { ... })) +* Added the ability to do `FROM` statements directly for `SELECT` and `DELETE` queries (new method: `from()`) (ex. db.from('foo').get(() => ...)) * Identifiers will now be properly escaped in `JOIN` statements. -* Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},function() { ... });`) +* Added the ability to call `get_where()` as a shorthand to `get()` and `where()` (ex. `db.get_where('table',{foo: 'bar'},() => ...);`) * Added the ability to call `select_min()`, `select_max()`, `select_avg()`, and `select_sum()`. * Significanly improved security, helping to prevent SQL injection attacks. * Added ability to do `OR HAVING` statements with `or_having()` method diff --git a/README.md b/README.md index 2c12ec6..fd37ce1 100755 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single' qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', function(err,response) { + .get('planets', (err,response) => { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 @@ -1036,7 +1036,7 @@ insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, ch #### Callback Example ```javascript -const callback = function(err, response) { +const callback = (err, response) => { qb.release(); if (err) { console.error(err); @@ -1050,7 +1050,7 @@ const callback = function(err, response) { } } }; -pool.get_connection(function(qb) { +pool.get_connection(qb => { qb.get('foo',callback); }); ``` @@ -1061,15 +1061,15 @@ pool.get_connection(function(qb) { const pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); const data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; -pool.get_connection(function(qb) { - qb.insert('employees', data, function(err, res) { +pool.get_connection(qb => { + qb.insert('employees', data, (err, res) => { if (err) { console.error(err); } else { if (res.affected_rows > 0) { const insert_id = res.insert_id; - qb.get_where('employees', {id: insert_id}, function(err, res) { + qb.get_where('employees', {id: insert_id}, (err, res) => { qb.release(); console.dir(res); }); @@ -1165,7 +1165,7 @@ qb.limit(10) .join('stars s', 's.galaxy_id=g.id', 'left') .group_by('g.id') .order_by('g.name', 'asc') - .get(function(err, response, conn) { + .get((err, response, conn) => { conn.release(); if (err) return console.error(err); @@ -1236,7 +1236,7 @@ Integer ```javascript // SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 3 const type = 3; -qb.where('type',type).count('galaxies', function(err, count) { +qb.where('type',type).count('galaxies', (err, count) => { if (err) return console.error(err); console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); }); @@ -1275,7 +1275,7 @@ const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -app.post('/update_account', function(req, res) { +app.post('/update_account', (req, res) => { const user_id = req.session.user_id; const sanitize_name = name => { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; const sanitize_age = age => { return age.replace(/[^0-9]+$/,'').trim(); }; @@ -1287,8 +1287,8 @@ app.post('/update_account', function(req, res) { bio: req.body.bio, }; - pool.get_connection(function(qb) { - qb.update('users', data, {id:user_id}, function(err, res) { + pool.get_connection(qb => { + qb.update('users', data, {id:user_id}, (err, res) => { qb.release(); if (err) return console.error(err); @@ -1308,7 +1308,7 @@ const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single' qb.where('id', 42) .from('users') .set('email', 'email@domain.net') - .update(null, null, null, function(err, res) { + .update(null, null, null, (err, res) => { if (err) return console.error(err); console.log("Updated: " + res.affected_rows + " rows"); }); @@ -1351,7 +1351,7 @@ const dataset = [ const where = {'last_updated <' : '2015-01-01'} -qb.update_batch('galaxies', dataset, key, where, function(err, res) { +qb.update_batch('galaxies', dataset, key, where, (err, res) => { if (err) return console.error(err); /* @@ -1408,7 +1408,7 @@ const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -app.post('/add_article', function(req, res) { +app.post('/add_article', (req, res) => { const user_id = req.session.user_id; const data = { @@ -1418,8 +1418,8 @@ app.post('/add_article', function(req, res) { publish_date: sanitize_age(req.body.last_name) }; - pool.get_connection(function(qb) { - qb.insert('articles', data, function(err, res) { + pool.get_connection(qb => { + qb.insert('articles', data, (err, res) => { qb.release(); if (err) return console.error(err); @@ -1463,7 +1463,7 @@ const data = [ {name: 'Postgres', version: '8.4'} ]; -qb.insert_batch('db_engines', data, function(err, res) { +qb.insert_batch('db_engines', data, (err, res) => { if (err) throw err; // INSERT INTO `db_engines` (`name`, `version`) @@ -1506,7 +1506,7 @@ Object containing information about the result of the query. const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); const data = {name: 'Postgres', version: '8.4'}; -qb.insert_ignore('db_engines', data, function(err, res) { +qb.insert_ignore('db_engines', data, (err, res) => { if (err) throw err; // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4'); @@ -1521,7 +1521,7 @@ This time we'll do it with an `on_dupe` string ```javascript const data = {name: 'Postgres', version: '8.4'}; -qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', function(err, res) { +qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NOW()', (err, res) => { if (err) throw err; // INSERT IGNORE INTO `db_engines` (`name`, `version`) VALUES ('Postgres', '8.4') ON DUPLICATE KEY UPDATE last_modified = NOW(); @@ -1572,15 +1572,15 @@ const app = express(); const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -app.post('/delete_comment/:id', function(req, res) { +app.post('/delete_comment/:id', (req, res) => { const comment_id = req.params.id; - pool.get_connection(function(qb) { - qb.get('comments', {id: id}, function(err, res) { + pool.get_connection(qb => { + qb.get('comments', {id: id}, (err, res) => { if (err) return console.error(err); const article_id = res.article_id; - qb.delete('comments', {id: id}, function(err, res) { + qb.delete('comments', {id: id}, (err, res) => { qb.release(); if (err) return console.error(err); @@ -1628,12 +1628,12 @@ const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool' * ]; */ -pool.get_connection(function(qb) { - qb.truncate('users', function(err, res) { +pool.get_connection(qb => { + qb.truncate('users', (err, res) => { if (err) throw err; - qb.insert('users', {name: 'Bob'}, function(err, res) { + qb.insert('users', {name: 'Bob'}, (err, res) => { if (err) throw err; - qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.get_where('users', {id: res.insert_id}, (err, res) => { qb.release(); if (err) throw err; // { id: 1, name: 'Bob' } (notice ID is 1) @@ -1676,12 +1676,12 @@ const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool' * ]; */ -pool.get_connection(function(qb) { - qb.empty_table('users', function(err, res) { +pool.get_connection(qb => { + qb.empty_table('users', (err, res) => { if (err) throw err; - qb.insert('users', {name: 'Bob'}, function(err, res) { + qb.insert('users', {name: 'Bob'}, (err, res) => { if (err) throw err; - qb.get_where('users', {id: res.insert_id}, function(err, res) { + qb.get_where('users', {id: res.insert_id}, (err, res) => { qb.release(); if (err) throw err; // { id: 4, name: 'Bob' } (notice ID is 4) @@ -1726,8 +1726,8 @@ Used to get a new connection from the connection pool or cluster pool. An instan const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -pool.get_connection(function(qb) { - qb.limit(10).get('users', function(err, res) { +pool.get_connection(qb => { + qb.limit(10).get('users', (err, res) => { qb.release(); // Do stuff with results or err }); @@ -1749,14 +1749,14 @@ Below is a contrived example (with no error handling--for brevity) that gets a l const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -pool.get_connection(function(qb) { - qb.like('username','|','right').get_where('users', {active: true}, function(err, res) { +pool.get_connection(qb => { + qb.like('username','|','right').get_where('users', {active: true}, (err, res) => { const users = users; (function update_user() { const user = users.shift(); user.username = user.username.replace(/\^|/,''); - qb.update('users', user, {id: user.id}, function(err, res) { + qb.update('users', user, {id: user.id}, (err, res) => { if (user.length > 0) { setTimeout(update_user,0); } else { @@ -1774,8 +1774,8 @@ Here's a simpler example so you can better see how it will most often be used const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -pool.get_connection(function(qb) { - qb.get_where('users', {username: 'foobar'}, function(err, res) { +pool.get_connection(qb => { + qb.get_where('users', {username: 'foobar'}, (err, res) => { qb.release(); if (err) throw err; console.dir(res); @@ -1797,9 +1797,9 @@ If you'd rather the engine not execute the query first, you can always use the a ```javascript const settings = require('db.json'); const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); -pool.get_connection(function(qb) { +pool.get_connection(qb => { const id = 4531; - qb.get('comments', {id: id}, function(err, res) { + qb.get('comments', {id: id}, (err, res) => { // SELECT * FROM `comments` WHERE `id` = 4531 console.log(qb.last_query()); qb.release(); @@ -1835,7 +1835,7 @@ Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); const sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; -qb.query(sql, function(err, res) { +qb.query(sql, (err, res) => { console.dir(res); }); ``` diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 7de9407..9fbb45f 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -160,7 +160,7 @@ const Adapters = function(nqb) { throw new Error(error_msg); } - nqb.pool.getConnection(function (err, connection) { + nqb.pool.getConnection((err, connection) => { if (err) throw err; const adapter = new Adapter({ pool: { @@ -185,7 +185,7 @@ const Adapters = function(nqb) { // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) if (that.debugging === true) { - nqb.pool.getConnection(function(err, connection) { + nqb.pool.getConnection((err, connection) => { connection.query('SELECT 1 + 1 AS solution', function(err) { connection.release(); if (err) { diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 2fc5af1..ae9c6f8 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1319,7 +1319,7 @@ const QueryBuilder = function() { } for (let i = 0; i < set.length; i++) { - (function(i,qb) { + ((i,qb) => { const row = []; for (const key in set[i]) { if (set[i].hasOwnProperty(key)) { diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js index fa13f32..3285b21 100755 --- a/examples/connection_pool_example.js +++ b/examples/connection_pool_example.js @@ -7,7 +7,7 @@ const settings = { const nqb = require('node-querybuilder'); const pool = new QueryBuilder(settings, 'mysql', 'pool'); -pool.get_connection(function(qb) { +pool.get_connection(qb => { qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', function(err,response) { diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index bde3669..0f7e27b 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -18,7 +18,7 @@ const connection_released = qb => { expect(connection._pool._freeConnections).to.have.length(1); }; -describe('QueryBuilder() - MySQL Adapter', function() { +describe('QueryBuilder() - MySQL Adapter', () => { const on_connect = err => { if (err) { console.error("Not connected!"); return; } console.log("connected!"); @@ -38,46 +38,46 @@ describe('QueryBuilder() - MySQL Adapter', function() { const bad_port = Object.assign({},settings); bad_port.port = 1; const bad_version = Object.assign({},settings); bad_version.version = 12; - it('should exist', function() { + it('should exist', () => { should.exist(nqb.QueryBuilder); }); - it('should be a function', function() { + it('should be a function', () => { nqb.QueryBuilder.should.be.a('function'); }); - it('should have all the QueryBuilder methods', function() { + it('should have all the QueryBuilder methods', () => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; expect(qb).to.include.keys(children); }); - it('should have all the QueryExec methods', function() { + it('should have all the QueryExec methods', () => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; expect(qb).to.include.keys(children); }); - it('should have all the miscellaneous methods', function() { + it('should have all the miscellaneous methods', () => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); const children = ['last_query','escape','empty_table','truncate']; expect(qb).to.include.keys(children); }); - it('should establish a single connection given valid connection credentials', function(done) { + it('should establish a single connection given valid connection credentials', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should be connected').to.not.be.instanceof(Error); }); }); }); - it('should allow us to disconnect from MySQL', function(done) { + it('should allow us to disconnect from MySQL', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { + qb.connect(err => { should.exist(qb.disconnect); qb.disconnect.should.be.a('function'); - qb.disconnect(function(err) { + qb.disconnect(err => { const connection = qb.connection(); - check(done, function() { + check(done, () => { expect(err, 'should be diconnected').to.not.be.instanceof(Error); expect(connection._protocol._ended).to.be.true; }); @@ -86,13 +86,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); - it('should fail to establish a single connection given no parameters', function() { - expect(function() { nqb.QueryBuilder(); }).to.throw(Error); + it('should fail to establish a single connection given no parameters', () => { + expect(() => nqb.QueryBuilder()).to.throw(Error); }); - it('should fail to establish a single connection given no connection credentials', function() { - expect(function() { nqb.QueryBuilder({},driver); }).to.throw(Error); + it('should fail to establish a single connection given no connection credentials', () => { + expect(() => nqb.QueryBuilder({},driver)).to.throw(Error); }); - it('should fail to establish a single connection given connection credentials with bad user', function(done) { + it('should fail to establish a single connection given connection credentials with bad user', done => { let qb; @@ -103,13 +103,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { return; } expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); }); }); }); - it('should fail to establish a single connection given connection credentials with bad host', function(done) { + it('should fail to establish a single connection given connection credentials with bad host', done => { let qb; @@ -120,13 +120,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { return; } expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); }); }); }); - it('should fail to establish a single connection given connection credentials with bad database', function(done) { + it('should fail to establish a single connection given connection credentials with bad database', done => { let qb; @@ -137,13 +137,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { return; } expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); }); }); }); - it('should fail to establish a single connection given connection credentials with bad password', function(done) { + it('should fail to establish a single connection given connection credentials with bad password', done => { let qb; @@ -154,13 +154,13 @@ describe('QueryBuilder() - MySQL Adapter', function() { return; } expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); }); }); }); - it('should fail to establish a single connection given connection credentials with bad port', function(done) { + it('should fail to establish a single connection given connection credentials with bad port', done => { let qb; try { qb = nqb.QueryBuilder(bad_port, driver); @@ -169,30 +169,30 @@ describe('QueryBuilder() - MySQL Adapter', function() { return; } expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); }); }); }); - it('should fail to establish connection if an invalid driver is specified', function() { + it('should fail to establish connection if an invalid driver is specified', () => { let qb; - expect(function() { nqb.QueryBuilder(settings); }, 'no driver specified').to.throw(Error); - expect(function() { nqb.QueryBuilder(settings,'foobar'); }, 'invalid driver specified').to.throw(Error); + expect(() => nqb.QueryBuilder(settings), 'no driver specified').to.throw(Error); + expect(() => nqb.QueryBuilder(settings,'foobar'), 'invalid driver specified').to.throw(Error); }); - it('should fail to establish connection if an invalid driver version is specified', function() { + it('should fail to establish connection if an invalid driver version is specified', () => { let qb; - expect(function() { nqb.QueryBuilder( Object.assign({}, settings), driver); }, 'valid driver version').to.not.throw(Error); - expect(function() { nqb.QueryBuilder(bad_version, driver); }, 'invalid driver version').to.throw(Error); + expect(() => nqb.QueryBuilder( Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); + expect(() => nqb.QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); }); - it('should allow us to retrieve our connection settings for reference', function(done) { + it('should allow us to retrieve our connection settings for reference', done => { const conn_settings = Object.assign({}, settings, {password: undefined}); delete conn_settings.version; const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { should.exist(qb.connection_settings); qb.connection_settings.should.be.a('function'); const settings = qb.connection_settings(); @@ -202,10 +202,10 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); }); - it('should allow us to escape certain values', function(done) { + it('should allow us to escape certain values', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { should.exist(qb.escape); qb.escape.should.be.a('function'); expect(qb.escape(null)).to.be.eql('NULL'); @@ -217,10 +217,10 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); }); - it('should allow us to escape identifiers the MySQL way', function(done) { + it('should allow us to escape identifiers the MySQL way', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { - check(done, function() { + qb.connect(err => { + check(done, () => { should.exist(qb.escape_id); qb.escape_id.should.be.a('function'); expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); @@ -230,11 +230,11 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); }); - it('should allow us to execute a query', function(done) { + it('should allow us to execute a query', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { + qb.connect(err => { qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", function(err, res) { - check(done, function() { + check(done, () => { expect(err).to.not.be.instanceof(Error); expect(res).to.not.be.empty; expect(res).to.have.length(3); @@ -242,15 +242,15 @@ describe('QueryBuilder() - MySQL Adapter', function() { }); }); }); - it('should not be able to release a non-pooled connection', function(done) { + it('should not be able to release a non-pooled connection', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(function(err) { - check(done, function() { - expect(function() { qb.release(); }).to.throw(Error); + qb.connect(err => { + check(done, () => { + expect(() => qb.release()).to.throw(Error); }); }); }); - it('should create a connection pool object if asked', function() { + it('should create a connection pool object if asked', () => { const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); expect(pool).to.be.instanceof.object; expect(pool).to.include.keys(['pool','get_connection','disconnect']); @@ -258,33 +258,33 @@ describe('QueryBuilder() - MySQL Adapter', function() { pool.get_connection.should.be.a('function'); pool.disconnect.should.be.a('function'); }); - it('should create a QueryBuilder adapter when getting a connection from the pool', function(done) { + it('should create a QueryBuilder adapter when getting a connection from the pool', done => { const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(function(qb) { - check(done, function() { + pool.get_connection(qb => { + check(done, () => { expect(qb).to.include.keys(Object.keys(qb2)); }); }); }); - it('should allow one to release a connection from the pool', function(done) { + it('should allow one to release a connection from the pool', done => { const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(function(qb) { - check(done, function() { connection_released(qb); }); + pool.get_connection(qb => { + check(done, () => connection_released(qb)); }); }); - it('should allow one use the same connection pool connection for multiple queries', function(done) { + it('should allow one use the same connection pool connection for multiple queries', done => { const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(function(qb) { + pool.get_connection(qb => { qb.query('select * from `cities` where `city` = "Gainesville"', function(err, res) { if (res.length > 0) { qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', function(err, res) { - check(done, function() { connection_released(qb); }); + check(done, () => connection_released(qb)); }); } else { - check(done, function() { connection_released(qb); }); + check(done, () => connection_released(qb)); } }); }); diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index 09c690a..2e4d949 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -2,66 +2,66 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('get_compiled_select()', function() { - it('should exist', function() { +describe('get_compiled_select()', () => { + it('should exist', () => { should.exist(qb.get_compiled_select); }); - it('should be a function', function() { + it('should be a function', () => { qb.get_compiled_select.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.get_compiled_select('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); - it('should add a set of tables to from_array when an array of tables is supplied', function() { + it('should add a set of tables to from_array when an array of tables is supplied', () => { qb.reset_query(); qb.get_compiled_select(['galaxies','star_systems','planets']); qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); }); - it('should return a SQL string', function() { + it('should return a SQL string', () => { qb.reset_query(); const sql = qb.get_compiled_select('galaxies'); sql.should.eql('SELECT * FROM `galaxies`'); }); }); -describe('get_compiled_insert()', function() { - it('should exist', function() { +describe('get_compiled_insert()', () => { + it('should exist', () => { should.exist(qb.get_compiled_insert); }); - it('should be a function', function() { + it('should be a function', () => { qb.get_compiled_insert.should.be.a('function'); }); - it('should return a SQL string', function() { + it('should return a SQL string', () => { qb.reset_query(); const sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` (`foo`) VALUES ('bar')"); }); }); -describe('get_compiled_update()', function() { - it('should exist', function() { +describe('get_compiled_update()', () => { + it('should exist', () => { should.exist(qb.get_compiled_update); }); - it('should be a function', function() { + it('should be a function', () => { qb.get_compiled_update.should.be.a('function'); }); - it('should return a SQL string', function() { + it('should return a SQL string', () => { qb.reset_query(); const sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); sql.should.eql("UPDATE (`galaxies`) SET `foo` = 'bar' WHERE `id` = 45"); }); }); -describe('get_compiled_delete()', function() { - it('should exist', function() { +describe('get_compiled_delete()', () => { + it('should exist', () => { should.exist(qb.get_compiled_delete); }); - it('should be a function', function() { + it('should be a function', () => { qb.get_compiled_delete.should.be.a('function'); }); - it('should return a SQL string', function() { + it('should return a SQL string', () => { qb.reset_query(); const sql = qb.where('id',45).get_compiled_delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` WHERE `id` = 45"); diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js index 044b845..d5d18a4 100755 --- a/test/mysql/tests-count.js +++ b/test/mysql/tests-count.js @@ -2,52 +2,52 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('count()', function() { - it('should exist', function() { +describe('count()', () => { + it('should exist', () => { should.exist(qb.count); }); - it('should be a function', function() { + it('should be a function', () => { qb.count.should.be.a('function'); }); - it('should require that an item already exists in the from_array if one is not provided as the first parameter', function() { + it('should require that an item already exists in the from_array if one is not provided as the first parameter', () => { qb.reset_query(); - expect(function() { qb.count(); }, 'no tables supplied in chain').to.throw(Error); - expect(function() { qb.from('galaxies').count(); }, 'table supplied by from()').to.not.throw(Error); - expect(function() { qb.count('galaxies'); }, 'table supplied as first parameter').to.not.throw(Error); + expect(() => qb.count(), 'no tables supplied in chain').to.throw(Error); + expect(() => qb.from('galaxies').count(), 'table supplied by from()').to.not.throw(Error); + expect(() => qb.count('galaxies'), 'table supplied as first parameter').to.not.throw(Error); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); const sql = qb.count('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); - it('should return a string', function() { + it('should return a string', () => { qb.reset_query(); const sql = qb.count('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should create a properly-escaped SELECT query', function() { + it('should create a properly-escaped SELECT query', () => { qb.reset_query(); const sql = qb.count('galaxies'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); - it('should include WHERE statements', function() { + it('should include WHERE statements', () => { qb.reset_query(); const sql = qb.where({type:'spiral'}).count('galaxies'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 'spiral'"); }); - it('should work when table/view/procedure is provided earlier in chain but not in count() method', function() { + it('should work when table/view/procedure is provided earlier in chain but not in count() method', () => { qb.reset_query(); const sql = qb.from('galaxies').count(); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); }); - it('should work with multiple tables/views/stored procedures', function() { + it('should work with multiple tables/views/stored procedures', () => { qb.reset_query(); const sql = qb.from(['planets','galaxies']).count(); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `planets`, `galaxies`"); }); - it('should include any joins that were added in the chain', function() { + it('should include any joins that were added in the chain', () => { qb.reset_query(); const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 647f52b..2339874 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -2,67 +2,67 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('delete()', function() { - it('should exist', function() { +describe('delete()', () => { + it('should exist', () => { should.exist(qb.delete); }); - it('should be a function', function() { + it('should be a function', () => { qb.delete.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.delete('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); - it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { qb.reset_query(); - expect(function() { qb.delete([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.delete({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.delete(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.delete(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.delete(true); }, 'true provided').to.throw(Error); - expect(function() { qb.delete(Infinity);}, 'Infinity provided').to.throw(Error); - expect(function() { qb.delete([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.delete(/foobar/);}, 'regex provided').to.throw(Error); - expect(function() { qb.delete(NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.delete(false); }, 'false provided').to.throw(Error); - expect(function() { qb.delete(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.delete(' '); }, 'string full of spaces provided').to.throw(Error); - expect(function() { qb.delete(null); }, 'null provided').to.throw(Error); + expect(() => qb.delete([]), 'empty array provided').to.throw(Error); + expect(() => qb.delete({}), 'empty object provided').to.throw(Error); + expect(() => qb.delete(3), 'integer provided').to.throw(Error); + expect(() => qb.delete(3.5), 'float provided').to.throw(Error); + expect(() => qb.delete(true), 'true provided').to.throw(Error); + expect(() => qb.delete(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.delete([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.delete(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.delete(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.delete(false), 'false provided').to.throw(Error); + expect(() => qb.delete(''), 'empty string provided').to.throw(Error); + expect(() => qb.delete(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.delete(null), 'null provided').to.throw(Error); // An undefined/nothing option will only work if a table has already been provided - qb.from('galaxies'); expect(function() { qb.delete(undefined); },'undefined provided').to.not.throw(Error); - qb.from('galaxies'); expect(function() { qb.delete(); },'nothing provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.delete(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.delete(),'nothing provided').to.not.throw(Error); }); - it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { qb.reset_query(); const sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql("DELETE FROM `galaxies`"); }); - it('should add where conditions to where_array when conditions are supplied', function() { + it('should add where conditions to where_array when conditions are supplied', () => { qb.reset_query(); qb.delete('planets', {continents: 7, star_system: 'Solar'}); qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); }); - it('should return a string', function() { + it('should return a string', () => { qb.reset_query(); const sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', function() { + it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', () => { qb.reset_query(); const sql = qb.delete('galaxies'); sql.should.eql('DELETE FROM `galaxies`'); }); - it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', function() { + it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', () => { qb.reset_query(); const sql = qb.delete('galaxies', {class: 'M'}); sql.should.eql("DELETE FROM `galaxies` WHERE `class` = 'M'"); }); - it('should use ONLY the FIRST table added previously via the from() method', function() { + it('should use ONLY the FIRST table added previously via the from() method', () => { qb.reset_query(); qb.from('galaxies'); let sql = qb.delete(); @@ -72,17 +72,17 @@ describe('delete()', function() { sql = qb.from(['galaxies','star_systems','planets']).delete(); sql.should.eql('DELETE FROM `galaxies`'); }); - it('should accept where conditions added previously via the where() method', function() { + it('should accept where conditions added previously via the where() method', () => { qb.reset_query(); const sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` WHERE `created` >= 4600000000 AND `class` = 'M'"); }); - it('should accept a limit on the number of rows deleted', function() { + it('should accept a limit on the number of rows deleted', () => { qb.reset_query(); const sql = qb.limit(20).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` LIMIT 20"); }); - it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', function() { + it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', () => { qb.reset_query(); const sql = qb.limit(20,10).delete('galaxies'); sql.should.eql("DELETE FROM `galaxies` LIMIT 10, 20"); diff --git a/test/mysql/tests-distinct.js b/test/mysql/tests-distinct.js index 630ee97..233d7a6 100755 --- a/test/mysql/tests-distinct.js +++ b/test/mysql/tests-distinct.js @@ -2,14 +2,14 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('distinct()', function() { - it('should exist', function() { +describe('distinct()', () => { + it('should exist', () => { should.exist(qb.distinct); }); - it('should be a function', function() { + it('should be a function', () => { qb.distinct.should.be.a('function'); }); - it('should override the default distinct_clause with the "DISTINCT " keyword', function() { + it('should override the default distinct_clause with the "DISTINCT " keyword', () => { qb.reset_query(); qb.distinct(); qb.distinct_clause.should.eql(['DISTINCT ']); diff --git a/test/mysql/tests-empty_table.js b/test/mysql/tests-empty_table.js index 46d9bcb..5634c32 100755 --- a/test/mysql/tests-empty_table.js +++ b/test/mysql/tests-empty_table.js @@ -2,47 +2,47 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('emtpy_table()', function() { - it('should exist', function() { +describe('emtpy_table()', () => { + it('should exist', () => { should.exist(qb.empty_table); }); - it('should be a function', function() { + it('should be a function', () => { qb.empty_table.should.be.a('function'); }); - it('should return a string', function() { + it('should return a string', () => { qb.reset_query(); const sql = qb.empty_table('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should build a proper DELETE statement', function() { + it('should build a proper DELETE statement', () => { qb.reset_query(); const sql = qb.empty_table('galaxies'); sql.should.eql('DELETE FROM `galaxies`'); }); - it('should only accept nothing or a non-empty-string for the table (first) parameter', function() { + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { qb.reset_query(); - expect(function() { qb.empty_table([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.empty_table({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.empty_table(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.empty_table(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.empty_table(true); }, 'true provided').to.throw(Error); - expect(function() { qb.empty_table(Infinity);}, 'Infinity provided').to.throw(Error); - expect(function() { qb.empty_table([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.empty_table(/foobar/);}, 'regex provided').to.throw(Error); - expect(function() { qb.empty_table(NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.empty_table(false); }, 'false provided').to.throw(Error); - expect(function() { qb.empty_table(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.empty_table(' '); }, 'string full of spaces provided').to.throw(Error); - expect(function() { qb.empty_table(null); }, 'null provided').to.throw(Error); + expect(() => qb.empty_table([]), 'empty array provided').to.throw(Error); + expect(() => qb.empty_table({}), 'empty object provided').to.throw(Error); + expect(() => qb.empty_table(3), 'integer provided').to.throw(Error); + expect(() => qb.empty_table(3.5), 'float provided').to.throw(Error); + expect(() => qb.empty_table(true), 'true provided').to.throw(Error); + expect(() => qb.empty_table(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.empty_table([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.empty_table(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.empty_table(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.empty_table(false), 'false provided').to.throw(Error); + expect(() => qb.empty_table(''), 'empty string provided').to.throw(Error); + expect(() => qb.empty_table(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.empty_table(null), 'null provided').to.throw(Error); // An undefined/nothing option will only work if a table has already been provided - qb.from('galaxies'); expect(function() { qb.empty_table(undefined); },'undefined provided').to.not.throw(Error); - qb.from('galaxies'); expect(function() { qb.empty_table(); },'nothing provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.empty_table(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.empty_table(),'nothing provided').to.not.throw(Error); }); - it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', function() { + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { qb.reset_query(); const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); sql.should.eql("DELETE FROM `galaxies`"); diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js index e6b4c3d..9e8ddce 100755 --- a/test/mysql/tests-from.js +++ b/test/mysql/tests-from.js @@ -1,93 +1,93 @@ const should = require('chai').should(); const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('from()', function() { - it('should exist', function() { +describe('from()', () => { + it('should exist', () => { should.exist(qb.from); }); - it('should be a function', function() { + it('should be a function', () => { qb.from.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('from_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.from_array.should.be.empty; }); - it('should add an item to an array and escape it properly', function() { + it('should add an item to an array and escape it properly', () => { qb.from('universe'); qb.from_array.should.eql(['`universe`']); }) - it('should accept a comma-delimited string of items and trim and escape each properly', function() { + it('should accept a comma-delimited string of items and trim and escape each properly', () => { qb.reset_query(); qb.from('universe,galaxy , star_system, planet'); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); - it('should have an empty array after resetting', function() { + it('should have an empty array after resetting', () => { qb.reset_query(); qb.from_array.should.be.empty; }); - it('should be allowed to be called multiple times to add multiple items to the from array', function() { + it('should be allowed to be called multiple times to add multiple items to the from array', () => { qb.reset_query(); qb.from('universe').from('galaxy').from('star_system').from('planet'); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); - it('should accept an array of items and add them individually to the from array', function() { + it('should accept an array of items and add them individually to the from array', () => { qb.reset_query(); qb.from(['universe','galaxy','star_system','planet']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); }); - it('should not double-escape an item', function() { + it('should not double-escape an item', () => { qb.reset_query(); qb.from('`do`'); qb.from_array.should.eql(['`do`']); }); - it('should not double-escape items when provided with an array of pre-escaped items', function() { + it('should not double-escape items when provided with an array of pre-escaped items', () => { qb.reset_query(); qb.from(['`universe`','`galaxy`','`star_system`']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); }); - it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', function() { + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { qb.reset_query(); qb.from(['`universe`','galaxy','`star_system`']); qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); }); - it('should allow for aliases and it should escape them properly', function() { + it('should allow for aliases and it should escape them properly', () => { qb.reset_query(); qb.from('universe u'); qb.from_array.should.eql(['`universe` `u`']); }); - it('should allow for the word AS to be used to alias an item', function() { + it('should allow for the word AS to be used to alias an item', () => { qb.reset_query(); qb.from('universe as u'); qb.from_array.should.eql(['`universe` as `u`']); }); - it('should allow for an array of item + aliases and it should escape them all properly', function() { + it('should allow for an array of item + aliases and it should escape them all properly', () => { qb.reset_query(); qb.from(['universe u', 'galaxy g']); qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); - it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', function() { + it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { qb.reset_query(); qb.from(['`universe` `u`', '`galaxy` `g`']); qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); - it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', function() { + it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { qb.reset_query(); qb.from(['`universe` u', 'galaxy `g`']); qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); - it('should add aliases to alias-tracking array', function() { + it('should add aliases to alias-tracking array', () => { qb.reset_query(); qb.from(['`universe` `u`', '`galaxy` `g`']); qb.aliased_tables.should.eql(['u','g']); }); - it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', function() { + it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { qb.reset_query(); qb.from(['universe u, galaxy g']); qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); }); - it('should allow for namespacing in field name (host.db.table)', function() { + it('should allow for namespacing in field name (host.db.table)', () => { qb.reset_query(); qb.from('star_system.planet'); qb.from_array.should.eql(['`star_system`.`planet`']); @@ -96,17 +96,17 @@ describe('from()', function() { qb.from('galaxy.star_system.planet'); qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); }); - it('should allow for namespacing in field name (host.db.table.column) + alias', function() { + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { qb.reset_query(); qb.from('universe.galaxy.star_system planet'); qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); }); - it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', function() { + it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { qb.reset_query(); qb.from('universe.galaxy.star_system as planet'); qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); }); - it('should accept but ignore empty strings and empty strings within arrays', function() { + it('should accept but ignore empty strings and empty strings within arrays', () => { qb.reset_query(); qb.from(''); qb.from_array.should.be.empty; diff --git a/test/mysql/tests-general.js b/test/mysql/tests-general.js index 35dd338..b27380d 100755 --- a/test/mysql/tests-general.js +++ b/test/mysql/tests-general.js @@ -1,7 +1,7 @@ const should = require('chai').should(); -describe('QueryBuilder', function() { - it('actually exists and can be initialized', function() { +describe('QueryBuilder', () => { + it('actually exists and can be initialized', () => { const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); qb.should.be.instanceOf(Object); }); diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index 1687756..478bbb6 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -2,61 +2,61 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('get()', function() { - it('should exist', function() { +describe('get()', () => { + it('should exist', () => { should.exist(qb.get); }); - it('should be a function', function() { + it('should be a function', () => { qb.get.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.get('galaxies'); qb.from_array.should.eql(['`galaxies`']); }); - it('should add a set of tables to from_array when an array of tables is supplied', function() { + it('should add a set of tables to from_array when an array of tables is supplied', () => { qb.reset_query(); qb.get(['galaxies','star_systems','planets']); qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); }); - it('should return a string', function() { + it('should return a string', () => { qb.reset_query(); const sql = qb.get('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', function() { + it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', () => { qb.reset_query(); const sql = qb.get('galaxies'); sql.should.eql('SELECT * FROM `galaxies`'); }); - it('should properly handle alias if provided in table string', function() { + it('should properly handle alias if provided in table string', () => { qb.reset_query(); const sql = qb.get('galaxies g'); sql.should.eql('SELECT * FROM `galaxies` `g`'); }); - it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', function() { + it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', () => { qb.reset_query(); const sql = qb.select(['id','name']).get('galaxies'); sql.should.eql("SELECT `id`, `name` FROM `galaxies`"); }); - it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', function() { + it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', () => { qb.reset_query(); const sql = qb.where('class','M').get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` WHERE `class` = 'M'"); }); - it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', function() { + it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', () => { qb.reset_query(); const sql = qb.get(['galaxies','star_systems','planets']); sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); - it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', function() { + it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', () => { qb.reset_query(); const sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); sql.should.eql("SELECT * FROM `galaxies`, `star_systems`, `planets` WHERE `class` = 'M'"); }); - it('should use tables added previously via the from() method', function() { + it('should use tables added previously via the from() method', () => { qb.reset_query(); qb.from('galaxies'); let sql = qb.get(); @@ -66,27 +66,27 @@ describe('get()', function() { sql = qb.from(['galaxies','star_systems','planets']).get(); sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); }); - it('should accept where conditions added previously via the where() method', function() { + it('should accept where conditions added previously via the where() method', () => { qb.reset_query(); const sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` WHERE `created` >= 4600000000 AND `classification` = 'M'"); }); - it('should accept a limit on the number of rows selected', function() { + it('should accept a limit on the number of rows selected', () => { qb.reset_query(); const sql = qb.limit(20).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` LIMIT 20"); }); - it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', function() { + it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', () => { qb.reset_query(); const sql = qb.limit(20,10).get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` LIMIT 10, 20"); }); - it('should include the DISTINCT keyword if the distinct() method is called', function() { + it('should include the DISTINCT keyword if the distinct() method is called', () => { qb.reset_query(); const sql = qb.distinct().select(['id','name']).get('galaxies'); sql.should.eql("SELECT DISTINCT `id`, `name` FROM `galaxies`"); }); - it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', function() { + it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', () => { qb.reset_query(); // MIN @@ -108,70 +108,70 @@ describe('get()', function() { sql = qb.select_sum('size','total_size').get('galaxies'); sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); }); - it('should include any joins that were added in the chain', function() { + it('should include any joins that were added in the chain', () => { qb.reset_query(); const sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) .join('galaxies g','g.id=s.galaxy_id','left') .get('star_systems s'); sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); }); - it('should include any GROUP BY statements added using the group_by() method.', function() { + it('should include any GROUP BY statements added using the group_by() method.', () => { qb.reset_query(); const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); }); - it('should add the ORDER BY clause of the order_by() method was called in the chain', function() { + it('should add the ORDER BY clause of the order_by() method was called in the chain', () => { qb.reset_query(); const sql = qb.order_by('size').get('galaxies'); sql.should.eql("SELECT * FROM `galaxies` ORDER BY `size` ASC"); }); - it('should include any HAVING clauses added using the having() method', function() { + it('should include any HAVING clauses added using the having() method', () => { qb.reset_query(); const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); }); }); -describe('get_where()', function() { - it('should exist', function() { +describe('get_where()', () => { + it('should exist', () => { should.exist(qb.get_where); }); - it('should be a function', function() { + it('should be a function', () => { qb.get_where.should.be.a('function'); }); - it('should require the first parameter to be a table in string format or tables array format', function() { - qb.reset_query(); - expect(function() { qb.get_where(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.get_where(''); }, 'empty string for table').to.throw(Error); - expect(function() { qb.get_where([]); }, 'empty array for tables').to.throw(Error); - expect(function() { qb.get_where(['']); }, 'array of empty strings for tables').to.throw(Error); - expect(function() { qb.get_where(1); }, 'integer for table').to.throw(Error); - expect(function() { qb.get_where(5.5); }, 'float for table').to.throw(Error); - expect(function() { qb.get_where(true); }, 'TRUE for table').to.throw(Error); - expect(function() { qb.get_where(false); }, 'FALSE for table').to.throw(Error); - expect(function() { qb.get_where(null); }, 'NULL for table').to.throw(Error); - expect(function() { qb.get_where({}); }, 'Standard object for table').to.throw(Error); - expect(function() { qb.get_where(Infinite); }, 'Infinite for table').to.throw(Error); - expect(function() { qb.get_where('galaxies'); }, 'valid table, no where').to.throw(Error); - expect(function() { qb.get_where('galaxies',{}); }, 'valid table, empty where').to.throw(Error); - expect(function() { qb.get_where('galaxies',[]); }, 'valid table, array for where').to.throw(Error); - expect(function() { qb.get_where('galaxies',3); }, 'valid table, integer for where').to.throw(Error); - expect(function() { qb.get_where('galaxies',33.3); }, 'valid table, float for where').to.throw(Error); - expect(function() { qb.get_where('galaxies','foo'); }, 'valid table, string for where').to.throw(Error); - expect(function() { qb.get_where('galaxies',true); }, 'valid table, TRUE for where').to.throw(Error); - expect(function() { qb.get_where('galaxies',false); }, 'valid table, FALSE for where').to.throw(Error); - expect(function() { qb.get_where('galaxies',Infinite); }, 'valid table, Infinite where').to.throw(Error); - expect(function() { qb.get_where('galaxies',null); }, 'valid table, NULL where').to.throw(Error); - expect(function() { qb.get_where('galaxies',{id: 3}); }, 'valid table, valid where').to.not.throw(Error); - }); - it('should return a string', function() { + it('should require the first parameter to be a table in string format or tables array format', () => { + qb.reset_query(); + expect(() => qb.get_where(), 'nothing provided').to.throw(Error); + expect(() => qb.get_where(''), 'empty string for table').to.throw(Error); + expect(() => qb.get_where([]), 'empty array for tables').to.throw(Error); + expect(() => qb.get_where(['']), 'array of empty strings for tables').to.throw(Error); + expect(() => qb.get_where(1), 'integer for table').to.throw(Error); + expect(() => qb.get_where(5.5), 'float for table').to.throw(Error); + expect(() => qb.get_where(true), 'TRUE for table').to.throw(Error); + expect(() => qb.get_where(false), 'FALSE for table').to.throw(Error); + expect(() => qb.get_where(null), 'NULL for table').to.throw(Error); + expect(() => qb.get_where({}), 'Standard object for table').to.throw(Error); + expect(() => qb.get_where(Infinite), 'Infinite for table').to.throw(Error); + expect(() => qb.get_where('galaxies'), 'valid table, no where').to.throw(Error); + expect(() => qb.get_where('galaxies',{}), 'valid table, empty where').to.throw(Error); + expect(() => qb.get_where('galaxies',[]), 'valid table, array for where').to.throw(Error); + expect(() => qb.get_where('galaxies',3), 'valid table, integer for where').to.throw(Error); + expect(() => qb.get_where('galaxies',33.3), 'valid table, float for where').to.throw(Error); + expect(() => qb.get_where('galaxies','foo'), 'valid table, string for where').to.throw(Error); + expect(() => qb.get_where('galaxies',true), 'valid table, TRUE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',false), 'valid table, FALSE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',Infinite), 'valid table, Infinite where').to.throw(Error); + expect(() => qb.get_where('galaxies',null), 'valid table, NULL where').to.throw(Error); + expect(() => qb.get_where('galaxies',{id: 3}), 'valid table, valid where').to.not.throw(Error); + }); + it('should return a string', () => { qb.reset_query(); const sql = qb.get('galaxies', {type: 'spiral'}); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should add table(s) to from_array and where items to where_array', function() { + it('should add table(s) to from_array and where items to where_array', () => { qb.reset_query(); const sql = qb.get_where('galaxies', {type: 'spiral'}); qb.from_array.should.eql(['`galaxies`']); diff --git a/test/mysql/tests-groupby.js b/test/mysql/tests-groupby.js index 0659478..51be939 100755 --- a/test/mysql/tests-groupby.js +++ b/test/mysql/tests-groupby.js @@ -2,50 +2,50 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('group_by()', function() { - it('should exist', function() { +describe('group_by()', () => { + it('should exist', () => { should.exist(qb.group_by); }); - it('should be a function', function() { + it('should be a function', () => { qb.group_by.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('group_by_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.group_by_array.should.be.empty; }); - it('should accept a single field in string form', function() { + it('should accept a single field in string form', () => { qb.reset_query(); qb.group_by('planet_type'); qb.group_by_array.should.eql(['`planet_type`']); }); - it('should accept a multiple fields delimited by commas', function() { + it('should accept a multiple fields delimited by commas', () => { qb.reset_query(); qb.group_by('planet_type, planet_position'); qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); }); - it('should accept an array of fields', function() { + it('should accept an array of fields', () => { qb.reset_query(); qb.group_by(['planet_type', 'planet_position']); qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); }); - it('should not accept anything but a string or an array of strings', function() { + it('should not accept anything but a string or an array of strings', () => { qb.reset_query(); - expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); - expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); - expect(function() { qb.group_by(true); }, 'true provided').to.throw(Error); - expect(function() { qb.group_by({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.group_by(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.group_by(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); // valid string - expect(function() { qb.group_by('planet_type'); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.group_by(['planet_type']); }, 'array of string(s) provided').to.not.throw(Error); + expect(() => qb.group_by('planet_type'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type']), 'array of string(s) provided').to.not.throw(Error); }); }); \ No newline at end of file diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js index 50b9135..eac8240 100755 --- a/test/mysql/tests-having.js +++ b/test/mysql/tests-having.js @@ -2,17 +2,17 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('having()', function() { - it('should exist', function() { +describe('having()', () => { + it('should exist', () => { should.exist(qb.having); }); - it('should be a function', function() { + it('should be a function', () => { qb.having.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('having_array'); }); - it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', function() { + it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', () => { qb.reset_query(); qb.having('planet_class > "M"'); qb.having_array.should.eql(["`planet_class` > 'M'"]); @@ -41,69 +41,69 @@ describe('having()', function() { qb.having('planet_class != "M"'); qb.having_array.should.eql(["`planet_class` != 'M'"]); }); - it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', function() { + it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', () => { qb.reset_query(); - expect(function() { qb.having('planet_class = "M", sentient_life = 1'); }, 'two conditions provided').to.throw(Error); + expect(() => qb.having('planet_class = "M", sentient_life = 1'), 'two conditions provided').to.throw(Error); }); - it('should accept an array of conditions and prepend AND to each condition following the first one', function() { + it('should accept an array of conditions and prepend AND to each condition following the first one', () => { qb.reset_query(); qb.having(["planet_class = 'M'", 'sentient_life = 1']); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); }); - it('should accept an object of conditions and prepend AND to each condition following the first one', function() { + it('should accept an object of conditions and prepend AND to each condition following the first one', () => { qb.reset_query(); const object = {planet_class: 'M', sentient_life: 1}; object['planet_order <='] = 3; qb.having(object); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); }); - it('should not accept anything but a non-empty array, object, or string', function() { - qb.reset_query(); - expect(function() { qb.group_by(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.group_by(null); }, 'null provided').to.throw(Error); - expect(function() { qb.group_by(false); }, 'false provided').to.throw(Error); - expect(function() { qb.group_by(true); }, 'true provided').to.throw(Error); - expect(function() { qb.group_by({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.group_by(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.group_by(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.group_by([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.group_by([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.group_by(''); }, 'empty string provided').to.throw(Error); + it('should not accept anything but a non-empty array, object, or string', () => { + qb.reset_query(); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); // valid string - expect(function() { qb.group_by('planet_type = "M"'); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.group_by(['planet_type = "M"']); }, 'array of string(s) provided').to.not.throw(Error); + expect(() => qb.group_by('planet_type = "M"'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type = "M"']), 'array of string(s) provided').to.not.throw(Error); }); - it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', function() { + it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', () => { qb.reset_query(); qb.having('planet_class','M'); qb.having_array.should.eql(["`planet_class` = 'M'"]); }); - it('should not escape conditions if asked not to', function() { + it('should not escape conditions if asked not to', () => { qb.reset_query(); qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); qb.having_array.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); }); }); -describe('or_having()', function() { - it('should exist', function() { +describe('or_having()', () => { + it('should exist', () => { should.exist(qb.or_having); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_having.should.be.a('function'); }); - it('should accept an array of conditions and prepend OR to each condition following the first one', function() { + it('should accept an array of conditions and prepend OR to each condition following the first one', () => { qb.reset_query(); qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); qb.having_array.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); }); - it('should be chainable with normal having', function() { + it('should be chainable with normal having', () => { qb.reset_query(); qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index fcfdb6c..1db5003 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -7,144 +7,144 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('insert()', function() { - it('should exist', function() { +describe('insert()', () => { + it('should exist', () => { should.exist(qb.insert); }); - it('should be a function', function() { + it('should be a function', () => { qb.insert.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.insert('galaxies', test_data); qb.from_array.should.eql(['`galaxies`']); }); - it('should only accept nothing or a string for the table (first) parameter', function() { + it('should only accept nothing or a string for the table (first) parameter', () => { qb.reset_query(); // Doing these to prevent other errors qb.from('galaxies'); - expect(function() { qb.insert([], test_data); }, 'empty array provided').to.throw(Error); - expect(function() { qb.insert({}, test_data); }, 'empty object provided').to.throw(Error); - expect(function() { qb.insert(3, test_data); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert(3.5, test_data); }, 'float provided').to.throw(Error); - expect(function() { qb.insert(true, test_data); }, 'true provided').to.throw(Error); - expect(function() { qb.insert(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert(/foobar/, test_data); }, 'regex provided').to.throw(Error); + expect(() => qb.insert([], test_data), 'empty array provided').to.throw(Error); + expect(() => qb.insert({}, test_data), 'empty object provided').to.throw(Error); + expect(() => qb.insert(3, test_data), 'integer provided').to.throw(Error); + expect(() => qb.insert(3.5, test_data), 'float provided').to.throw(Error); + expect(() => qb.insert(true, test_data), 'true provided').to.throw(Error); + expect(() => qb.insert(Infinity, test_data), 'Infinity provided').to.throw(Error); + expect(() => qb.insert([1,2], test_data), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert(/foobar/, test_data), 'regex provided').to.throw(Error); - expect(function() { qb.insert(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - expect(function() { qb.insert(false, test_data); }, 'false provided').to.not.throw(Error); - expect(function() { qb.insert('', test_data); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.insert(null, test_data); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert(undefined, test_data);},'undefined provided').to.not.throw(Error); - }); - it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { + expect(() => qb.insert(NaN, test_data), 'NaN provided').to.not.throw(Error); + expect(() => qb.insert(false, test_data), 'false provided').to.not.throw(Error); + expect(() => qb.insert('', test_data), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.insert(null, test_data), 'null provided').to.not.throw(Error); + expect(() => qb.insert(undefined, test_data),'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { qb.reset_query(); - expect(function() { qb.insert('galaxies',test_data); }, 'non-empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[]); }, 'empty array provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',[test_data,test_data]);}, 'array of non-empty standard objects provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{}); }, 'empty object provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',undefined); }, 'undefined provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies'); }, 'nothing provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',[]), 'empty array provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',[test_data,test_data]), 'array of non-empty standard objects provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',{}), 'empty object provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',null), 'null provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',undefined), 'undefined provided').to.not.throw(Error); + expect(() => qb.insert('galaxies'), 'nothing provided').to.not.throw(Error); - expect(function() { qb.insert('galaxies',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert('galaxies',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.insert('galaxies',true); }, 'true provided').to.throw(Error); - expect(function() { qb.insert('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - expect(function() { qb.insert('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert('galaxies',['abc',2,{foo:'bar'}]);}, 'array of mixed values provided').to.throw(Error); - expect(function() { qb.insert('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert('galaxies',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.insert('galaxies',false); }, 'false provided').to.throw(Error); - expect(function() { qb.insert('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); - }); - it('should allow for an empty data parameter', function() { + expect(() => qb.insert('galaxies',3), 'integer provided').to.throw(Error); + expect(() => qb.insert('galaxies',3.5), 'float provided').to.throw(Error); + expect(() => qb.insert('galaxies',true), 'true provided').to.throw(Error); + expect(() => qb.insert('galaxies',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert('galaxies',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert('galaxies',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert('galaxies',false), 'false provided').to.throw(Error); + expect(() => qb.insert('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', () => { qb.reset_query(); const sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); - it('should utilize pre-existing tables set in from_array', function() { + it('should utilize pre-existing tables set in from_array', () => { qb.reset_query(); qb.from('galaxies'); const sql = qb.insert(); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); - it('should utilize pre-existing values set in in set_array', function() { + it('should utilize pre-existing values set in in set_array', () => { qb.reset_query(); qb.set(test_data); const sql = qb.insert('galaxies'); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); - it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { qb.reset_query(); qb.from('galaxies').set(test_data); const sql = qb.insert(); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); - it('should accept a non-empty object for the data parameter', function() { + it('should accept a non-empty object for the data parameter', () => { qb.reset_query(); const sql = qb.insert('galaxies', test_data); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); - it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', () => { qb.reset_query(); const sql = qb.insert('galaxies', test_data_set); const sql_b = qb.insert_batch('galaxies', test_data_set); sql.should.eql(sql_b); }); - it('should fail if any invalid values are passed in the data object.', function() { + it('should fail if any invalid values are passed in the data object.', () => { qb.reset_query(); const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; - expect(function() { qb.insert('galaxies',{id: 1}); }, 'number in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: 'foo'}); }, 'string in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: false}); }, 'boolean in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: null}); }, 'null in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: undefined}); }, 'undefined in data').to.not.throw(Error); - expect(function() { qb.insert('galaxies',{id: func}); }, 'function in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: arr}); }, 'array in data').to.throw(Error); - expect(function() { qb.insert('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: 1}), 'number in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: 'foo'}), 'string in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: false}), 'boolean in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: null}), 'null in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: undefined}), 'undefined in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: func}), 'function in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: regex}), 'regex in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: arr}), 'array in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: obj}), 'object in data').to.throw(Error); }); }); -describe('insert_ignore()', function() { - it('should exist', function() { +describe('insert_ignore()', () => { + it('should exist', () => { should.exist(qb.insert_ignore); }); - it('should be a function', function() { + it('should be a function', () => { qb.insert_ignore.should.be.a('function'); }); - it('should create an INSERT IGNORE statement', function() { + it('should create an INSERT IGNORE statement', () => { qb.reset_query(); const sql = qb.insert_ignore('galaxies', test_data); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral')"); }); - it('should be just a wrapper of insert() that passes true to the 3rd parameter', function() { + it('should be just a wrapper of insert() that passes true to the 3rd parameter', () => { qb.reset_query(); const sql = qb.insert_ignore('galaxies', test_data); const sql_b = qb.insert('galaxies', test_data, true); sql.should.eql(sql_b); }); - it('should convert to insert_batch() if an array of data is supplied to second parameter', function() { + it('should convert to insert_batch() if an array of data is supplied to second parameter', () => { qb.reset_query(); const sql = qb.insert_ignore('galaxies', test_data_set); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); - it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', function() { + it('should support the "on_dupe" suffix parameter... effectively appending to the query anything supplied in this parameter', () => { qb.reset_query(); const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index b02d90f..ee25478 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -5,97 +5,97 @@ const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('insert_batch()', function() { - it('should exist', function() { +describe('insert_batch()', () => { + it('should exist', () => { should.exist(qb.insert_batch); }); - it('should be a function', function() { + it('should be a function', () => { qb.insert_batch.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.insert_batch('galaxies', test_data); qb.from_array.should.eql(['`galaxies`']); }); - it('should only accept nothing or a string for the table (first) parameter', function() { + it('should only accept nothing or a string for the table (first) parameter', () => { qb.reset_query(); // Doing these to prevent other errors qb.from('galaxies'); - expect(function() { qb.insert_batch([], test_data); }, 'empty array provided').to.throw(Error); - expect(function() { qb.insert_batch({}, test_data); }, 'empty object provided').to.throw(Error); - expect(function() { qb.insert_batch(3, test_data); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert_batch(3.5, test_data); }, 'float provided').to.throw(Error); - expect(function() { qb.insert_batch(true, test_data); }, 'true provided').to.throw(Error); - expect(function() { qb.insert_batch(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert_batch([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert_batch(/foobar/, test_data); }, 'regex provided').to.throw(Error); + expect(() => qb.insert_batch([], test_data), 'empty array provided').to.throw(Error); + expect(() => qb.insert_batch({}, test_data), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch(3, test_data), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch(3.5, test_data), 'float provided').to.throw(Error); + expect(() => qb.insert_batch(true, test_data), 'true provided').to.throw(Error); + expect(() => qb.insert_batch(Infinity, test_data), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch([1,2], test_data), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch(/foobar/, test_data), 'regex provided').to.throw(Error); - expect(function() { qb.insert_batch(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - expect(function() { qb.insert_batch(false, test_data); }, 'false provided').to.not.throw(Error); - expect(function() { qb.insert_batch('', test_data); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.insert_batch(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.insert_batch(null, test_data); }, 'null provided').to.not.throw(Error); - expect(function() { qb.insert_batch(undefined, test_data); }, 'undefined provided').to.not.throw(Error); + expect(() => qb.insert_batch(NaN, test_data), 'NaN provided').to.not.throw(Error); + expect(() => qb.insert_batch(false, test_data), 'false provided').to.not.throw(Error); + expect(() => qb.insert_batch('', test_data), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert_batch(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.insert_batch(null, test_data), 'null provided').to.not.throw(Error); + expect(() => qb.insert_batch(undefined, test_data), 'undefined provided').to.not.throw(Error); }); - it('should build a proper batch INSERT string', function() { + it('should build a proper batch INSERT string', () => { qb.reset_query(); const sql = qb.insert_batch('galaxies', test_data); sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); }); - it('should only accept an array as the second parameter', function() { + it('should only accept an array as the second parameter', () => { qb.reset_query(); - expect(function() { qb.insert_batch('galaxies',test_data); }, 'array of objects provided').to.not.throw(Error); - expect(function() { qb.insert_batch('galaxies',[]); }, 'empty array provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies',test_data), 'array of objects provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies',[]), 'empty array provided').to.not.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[test_data,test_data]); }, 'array of arrays provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',null); }, 'null provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',undefined); }, 'undefined provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies'); }, 'nothing provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',true); }, 'true provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[Date, /foobar/, null]); }, 'array of non-standard objects provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',false); }, 'false provided').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[test_data,test_data]), 'array of arrays provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',{}), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',''), 'empty string provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',null), 'null provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',undefined), 'undefined provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies'), 'nothing provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3.5), 'float provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',true), 'true provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',false), 'false provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',' '), 'string full of spaces provided').to.throw(Error); }); - it('should allow for an empty data parameter', function() { + it('should allow for an empty data parameter', () => { qb.reset_query(); const sql = qb.insert_batch('galaxies',[]); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); - it('should utilize pre-existing tables set in from_array', function() { + it('should utilize pre-existing tables set in from_array', () => { qb.reset_query(); qb.from('galaxies'); const sql = qb.insert_batch(null,[]); sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); }); - it('should fail if any invalid values are passed into one of the data objects in the dataset', function() { + it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { qb.reset_query(); const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; - expect(function() { qb.insert_batch('galaxies',[{id: func}]); }, 'function in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: regex}]); }, 'regex in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: Infinity}]); }, 'Infinity in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: undefined}]); }, 'undefined in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: NaN}]); }, 'NaN in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: arr}]); }, 'array in data').to.throw(Error); - expect(function() { qb.insert_batch('galaxies',[{id: obj}]); }, 'object in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: func}]), 'function in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: regex}]), 'regex in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: Infinity}]), 'Infinity in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: undefined}]), 'undefined in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: NaN}]), 'NaN in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: arr}]), 'array in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: obj}]), 'object in data').to.throw(Error); }); - it('should support insert ignore statements', function() { + it('should support insert ignore statements', () => { qb.reset_query(); const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index e655a48..f532bc5 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -2,110 +2,110 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('join()', function() { - it('should exist', function() { +describe('join()', () => { + it('should exist', () => { should.exist(qb.join); }); - it('should be a function', function() { + it('should be a function', () => { qb.join.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('join_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.join_array.should.be.empty; }); - it('should require a string to be passed as first parameter', function() { + it('should require a string to be passed as first parameter', () => { const invalid_match = /must provide a table/; - expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); - expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); - expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); - expect(function() { qb.join(false); }, 'false provided').to.throw(Error, invalid_match); - expect(function() { qb.join({}); }, 'object provided').to.throw(Error, invalid_match); - expect(function() { qb.join([]); }, 'empty array provided').to.throw(Error, invalid_match); - expect(function() { qb.join(''); }, 'empty string provided').to.throw(Error, invalid_match); - expect(function() { qb.join(' '); }, 'string of spaces provided').to.throw(Error, invalid_match); - expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); - }); - it('should except single item and add it to join array as basic join and escape item', function() { + expect(() => qb.join(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.join(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.join(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.join(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.join({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.join([]), 'empty array provided').to.throw(Error, invalid_match); + expect(() => qb.join(''), 'empty string provided').to.throw(Error, invalid_match); + expect(() => qb.join(' '), 'string of spaces provided').to.throw(Error, invalid_match); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + }); + it('should except single item and add it to join array as basic join and escape item', () => { qb.reset_query(); qb.join('universe'); qb.join_array.should.eql(['JOIN `universe` ']); }); - it('should except single item with alias and add it to join array as basic join and escape each part', function() { + it('should except single item with alias and add it to join array as basic join and escape each part', () => { qb.reset_query(); qb.join('universe u'); qb.join_array.should.eql(['JOIN `universe` `u` ']); }); - it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', function() { + it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', () => { const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; const invalid_direction = /Invalid join direction provided as third parameter/; - expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe','','left'); }, 'empty string 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',' ','left'); }, 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',5,'left'); }, 'integer 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',5.6,'left'); }, 'float 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',[],'left'); }, 'array 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe',{},'left'); }, 'object 2nd param').to.throw(Error,invalid_2nd_param); - expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); - expect(function() { qb.join('universe','foo = bar'); }, 'no 3rd param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar','left'); }, '3 valid params').to.not.throw(Error); - }); - it('should allow valid join direction to be passed in third parameter', function() { + expect(() => qb.join('universe',null,'left'), 'null 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',false,'left'), 'false 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','','left'), 'empty string 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',' ','left'), 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5,'left'), 'integer 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5.6,'left'), 'float 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',[],'left'), 'array 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',{},'left'), 'object 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe','foo = bar'), 'no 3rd param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), '3 valid params').to.not.throw(Error); + }); + it('should allow valid join direction to be passed in third parameter', () => { // NOTE: A lot of this functionality was already tested when testing second param const invalid_direction = /Invalid join direction provided as third parameter/; - expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); - expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',''); }, 'empty third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',' '); }, 'just spaces').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',null); }, 'null third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',false); }, 'false third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',5); }, 'integer third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',5.5); }, 'float third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',{}); }, 'object third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',[]); }, 'array third param').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar','left '); }, 'trailing space').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',' left '); }, 'leading and trailing space').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar',' left'); }, 'leading space').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar','left'); }, 'lowercase direction').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar','LEFT'); }, 'uppercase direction').to.not.throw(Error); - expect(function() { qb.join('universe','foo = bar','LEFT OUTER'); }, 'two word direction').to.not.throw(Error); - }); - it('should except a valid second parameter as a join condition and escape it properly', function() { + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe',null,null), 'invalid 2nd and 3rd params').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',''), 'empty third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' '), 'just spaces').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',null), 'null third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',false), 'false third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5), 'integer third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5.5), 'float third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',{}), 'object third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',[]), 'array third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left '), 'trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left '), 'leading and trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left'), 'leading space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), 'lowercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT'), 'uppercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT OUTER'), 'two word direction').to.not.throw(Error); + }); + it('should except a valid second parameter as a join condition and escape it properly', () => { qb.reset_query(); qb.join('universe u','u.type_id = ut.id'); qb.join_array.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); }); - it('should escape compound objects properly', function() { + it('should escape compound objects properly', () => { qb.reset_query(); qb.join('universe.galaxy.star_system s','s.type_id = st.id'); qb.join_array.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); }); - it('should add aliases to alias-tracking array', function() { + it('should add aliases to alias-tracking array', () => { qb.reset_query(); qb.join('universe.galaxy.star_system s'); qb.aliased_tables.should.eql(['s']); }); - it('should properly place join direction into join clause', function() { + it('should properly place join direction into join clause', () => { qb.reset_query(); qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); qb.join_array.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); }); - it('should be chainable to allow for multiple join clauses', function() { + it('should be chainable to allow for multiple join clauses', () => { qb.reset_query(); qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); qb.join_array.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); }); - it('should escape complex join conditions', function() { + it('should escape complex join conditions', () => { qb.reset_query(); qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); }); - it('should NOT escape any part of join query when asked not to', function() { + it('should NOT escape any part of join query when asked not to', () => { qb.reset_query(); qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left', false); qb.join_array.should.eql(["LEFT JOIN star_system s ON s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'"]); diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js index 957870f..7aea272 100755 --- a/test/mysql/tests-like.js +++ b/test/mysql/tests-like.js @@ -2,67 +2,67 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('like()', function() { - it('should exist', function() { +describe('like()', () => { + it('should exist', () => { should.exist(qb.like); }); - it('should be a function', function() { + it('should be a function', () => { qb.like.should.be.a('function'); }); - it('should require first parameter to be a valid string or object with key value pairs', function() { - expect(function() { qb.like(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.like(null); }, 'null provided').to.throw(Error); - expect(function() { qb.like(false); }, 'false provided').to.throw(Error); - expect(function() { qb.like(true); }, 'true provided').to.throw(Error); - expect(function() { qb.like({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.like(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.like(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.like([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like(''); }, 'empty string provided').to.throw(Error); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); - expect(function() { qb.like('planet_name','ear','after'); }, 'valid string').to.not.throw(Error); - expect(function() { qb.like({planet_name: 'ear'}); }, 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', function() { - expect(function() { qb.like('planet_name'); }, 'no second param provided').to.throw(Error); - expect(function() { qb.like('planet_name','ear'); }, 'valid second param provided').to.not.throw(Error); - expect(function() { qb.like({planet_name: 'ear'}); }, 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', function() { - expect(function() { qb.like('planet_name',null); }, 'null provided').to.throw(Error); - expect(function() { qb.like('planet_name',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.like('planet_name',[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like('planet_name',NaN); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like('planet_name',Infinity);}, 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - expect(function() { qb.like('planet_name',false); }, 'false provided').to.not.throw(Error); - expect(function() { qb.like('planet_name',true); }, 'true provided').to.not.throw(Error); - expect(function() { qb.like('planet_name',3); }, 'integer provided').to.not.throw(Error); - expect(function() { qb.like('planet_name',3.5); }, 'float provided').to.not.throw(Error); - expect(function() { qb.like('planet_name',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.like('planet_name','ear'); }, 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', function() { - expect(function() { qb.like('galaxy_name','milk',null); }, 'null provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',NaN); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',Infinity); }, 'empty array provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',false); }, 'false provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',true); }, 'true provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk',''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk','foo'); }, 'non-empty string provided').to.throw(Error); + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - expect(function() { qb.like('galaxy_name','milk'); }, 'no third param provided').to.not.throw(Error); - expect(function() { qb.like('galaxy_name','milk','right'); }, 'right as third param').to.not.throw(Error); - expect(function() { qb.like('galaxy_name','milk','left'); }, 'left as third param').to.not.throw(Error); - expect(function() { qb.like('galaxy_name','milk','both'); }, 'both as third param').to.not.throw(Error); - expect(function() { qb.like('galaxy_name','milk','before'); }, 'before as third param').to.not.throw(Error); - expect(function() { qb.like('galaxy_name','milk','after'); }, 'after as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', function() { + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { qb.reset_query(); qb.like('galaxy_name', 'milky', 'after'); qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); @@ -71,7 +71,7 @@ describe('like()', function() { qb.like('galaxy_name', 'milky', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', function() { + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { qb.reset_query(); qb.like('galaxy_name', 'milky', 'before'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); @@ -80,7 +80,7 @@ describe('like()', function() { qb.like('galaxy_name', 'milky', 'left'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', function() { + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { qb.reset_query(); qb.like('galaxy_name', 'milky'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); @@ -89,98 +89,98 @@ describe('like()', function() { qb.like('galaxy_name', 'milky', 'both'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); }); - it('should put AND in between multiple LIKE clauses', function() { + it('should put AND in between multiple LIKE clauses', () => { qb.reset_query(); qb.like('galaxy_name', 'milky'); qb.like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); }); }); -describe('or_like()', function() { - it('should exist', function() { +describe('or_like()', () => { + it('should exist', () => { should.exist(qb.or_like); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_like.should.be.a('function'); }); - it('should put OR in between multiple OR LIKE clauses', function() { + it('should put OR in between multiple OR LIKE clauses', () => { qb.reset_query(); qb.or_like('galaxy_name', 'milky'); qb.or_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); }); - it('should be chainable with regular like clauses', function() { + it('should be chainable with regular like clauses', () => { qb.reset_query(); qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); }); }); -describe('not_like()', function() { - it('should exist', function() { +describe('not_like()', () => { + it('should exist', () => { should.exist(qb.not_like); }); - it('should be a function', function() { + it('should be a function', () => { qb.not_like.should.be.a('function'); }); - it('should put NOT before LIKE', function() { + it('should put NOT before LIKE', () => { qb.reset_query(); qb.not_like('galaxy_name', 'milky'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); }); - it('should put AND in between multiple NOT LIKE clauses', function() { + it('should put AND in between multiple NOT LIKE clauses', () => { qb.reset_query(); qb.not_like('galaxy_name', 'milky'); qb.not_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); - it('should be chainable with regular like clauses', function() { + it('should be chainable with regular like clauses', () => { qb.reset_query(); qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); }); }); -describe('or_not_like()', function() { - it('should exist', function() { +describe('or_not_like()', () => { + it('should exist', () => { should.exist(qb.or_not_like); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_not_like.should.be.a('function'); }); - it('should put NOT before LIKE', function() { + it('should put NOT before LIKE', () => { qb.reset_query(); qb.or_not_like('galaxy_name', 'milky'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); }); - it('should put OR in between multiple NOT LIKE clauses', function() { + it('should put OR in between multiple NOT LIKE clauses', () => { qb.reset_query(); qb.or_not_like('galaxy_name', 'milky'); qb.or_not_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); }); - it('should be chainable with regular like clauses', function() { + it('should be chainable with regular like clauses', () => { qb.reset_query(); qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); diff --git a/test/mysql/tests-limit.js b/test/mysql/tests-limit.js index f7ebef7..cf7ac1c 100755 --- a/test/mysql/tests-limit.js +++ b/test/mysql/tests-limit.js @@ -2,82 +2,82 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('limit()', function() { - it('should exist', function() { +describe('limit()', () => { + it('should exist', () => { should.exist(qb.limit); }); - it('should be a function', function() { + it('should be a function', () => { qb.limit.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('limit_to'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.limit_to.should.be.empty; }); - it('should require an integer (or integer in string form) in first parameter', function() { + it('should require an integer (or integer in string form) in first parameter', () => { qb.reset_query(); - expect(function() { qb.limit(5); }, 'integer provided').to.not.throw(Error); - expect(function() { qb.limit('5'); }, '5 in string form provided').to.not.throw(Error); - expect(function() { qb.limit(5.7); }, 'float provided').to.throw(Error); - expect(function() { qb.limit('5.7');}, 'float provided').to.throw(Error); - expect(function() { qb.limit('abc');}, 'alpha provided').to.throw(Error); - expect(function() { qb.limit('abc7');}, 'alpha numerics provided').to.throw(Error); - expect(function() { qb.limit(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.limit(null); }, 'null provided').to.throw(Error); - expect(function() { qb.limit(true); }, 'true provided').to.throw(Error); - expect(function() { qb.limit(false);}, 'false provided').to.throw(Error); - expect(function() { qb.limit(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.limit({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.limit([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.limit([5]); }, 'array with integer in it provided').to.throw(Error); + expect(() => qb.limit(5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(5.7), 'float provided').to.throw(Error); + expect(() => qb.limit('5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.limit(), 'nothing provided').to.throw(Error); + expect(() => qb.limit(null), 'null provided').to.throw(Error); + expect(() => qb.limit(true), 'true provided').to.throw(Error); + expect(() => qb.limit(false), 'false provided').to.throw(Error); + expect(() => qb.limit(''), 'empty string provided').to.throw(Error); + expect(() => qb.limit({}), 'empty object provided').to.throw(Error); + expect(() => qb.limit([]), 'empty array provided').to.throw(Error); + expect(() => qb.limit([5]), 'array with integer in it provided').to.throw(Error); }); - it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', function() { + it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', () => { qb.reset_query(); - expect(function() { qb.limit(10,5); }, 'integer provided').to.not.throw(Error); - expect(function() { qb.limit(10,'5'); }, '5 in string form provided').to.not.throw(Error); - expect(function() { qb.limit(10,5.7); }, 'float provided').to.throw(Error); - expect(function() { qb.limit(10,'5.7'); }, 'float provided').to.throw(Error); - expect(function() { qb.limit(10,'abc'); }, 'alpha provided').to.throw(Error); - expect(function() { qb.limit(10,'abc7');}, 'alphanumerics provided').to.throw(Error); - expect(function() { qb.limit(10,null); }, 'null provided').to.throw(Error); - expect(function() { qb.limit(10,true); }, 'true provided').to.throw(Error); - expect(function() { qb.limit(10,false); }, 'false provided').to.throw(Error); - expect(function() { qb.limit(10,''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.limit(10,{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.limit(10,[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.limit(10,[5]); }, 'array with integer in it provided').to.throw(Error); + expect(() => qb.limit(10,5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit(10,'5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(10,5.7), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit(10,'abc7'), 'alphanumerics provided').to.throw(Error); + expect(() => qb.limit(10,null), 'null provided').to.throw(Error); + expect(() => qb.limit(10,true), 'true provided').to.throw(Error); + expect(() => qb.limit(10,false), 'false provided').to.throw(Error); + expect(() => qb.limit(10,''), 'empty string provided').to.throw(Error); + expect(() => qb.limit(10,{}), 'empty object provided').to.throw(Error); + expect(() => qb.limit(10,[]), 'empty array provided').to.throw(Error); + expect(() => qb.limit(10,[5]), 'array with integer in it provided').to.throw(Error); }); - it('should override the default limit_to value when a limit is provided', function() { + it('should override the default limit_to value when a limit is provided', () => { qb.reset_query(); qb.limit(10); qb.limit_to.should.eql([10]); }); - it('should override the default limit_to and offset_val values when a limit and an offset are provided', function() { + it('should override the default limit_to and offset_val values when a limit and an offset are provided', () => { qb.reset_query(); qb.limit(10,20); qb.limit_to.should.eql([10]); qb.offset_val.should.eql([20]); }); - it('should trim string values that are provided', function() { + it('should trim string values that are provided', () => { qb.reset_query(); qb.limit('10 '); qb.limit_to.should.eql([10]); }); - it('should trim string values that are provided', function() { + it('should trim string values that are provided', () => { qb.reset_query(); qb.limit(' 10 ',' 12'); qb.limit_to.should.eql([10]); qb.offset_val.should.eql([12]); }); - it('should override values set by any previous calls to itself', function() { + it('should override values set by any previous calls to itself', () => { qb.reset_query(); qb.limit(10); qb.limit_to.should.eql([10]); qb.limit(20); qb.limit_to.should.eql([20]); }); - it('should be chainable whereby the last call to the method will contain the value(s) used', function() { + it('should be chainable whereby the last call to the method will contain the value(s) used', () => { qb.reset_query(); qb.limit(10,5).limit(20).limit(100,30); qb.limit_to.should.eql([100]); diff --git a/test/mysql/tests-offset.js b/test/mysql/tests-offset.js index 185a847..4ca29d4 100755 --- a/test/mysql/tests-offset.js +++ b/test/mysql/tests-offset.js @@ -2,56 +2,56 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('offset()', function() { - it('should exist', function() { +describe('offset()', () => { + it('should exist', () => { should.exist(qb.offset); }); - it('should be a function', function() { + it('should be a function', () => { qb.offset.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('offset_val'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.offset_val.should.be.empty; }); - it('should require an integer (or integer in string form) in first parameter', function() { + it('should require an integer (or integer in string form) in first parameter', () => { qb.reset_query(); - expect(function() { qb.offset(5); }, 'integer provided').to.not.throw(Error); - expect(function() { qb.offset('5'); }, '5 in string form provided').to.not.throw(Error); - expect(function() { qb.offset(5.7); }, 'float provided').to.throw(Error); - expect(function() { qb.offset('5.7'); }, 'float provided').to.throw(Error); - expect(function() { qb.offset('abc') }, 'alpha provided').to.throw(Error); - expect(function() { qb.offset('abc7'); }, 'alpha numerics provided').to.throw(Error); - expect(function() { qb.offset(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.offset(null); }, 'null provided').to.throw(Error); - expect(function() { qb.offset(true); }, 'true provided').to.throw(Error); - expect(function() { qb.offset(false); }, 'false provided').to.throw(Error); - expect(function() { qb.offset(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.offset({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.offset([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.offset([5]); }, 'array with integer in it provided').to.throw(Error); - }); - it('should override the default offset_val value when a offset is provided', function() { + expect(() => qb.offset(5), 'integer provided').to.not.throw(Error); + expect(() => qb.offset('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.offset(5.7), 'float provided').to.throw(Error); + expect(() => qb.offset('5.7'), 'float provided').to.throw(Error); + expect(() => qb.offset('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.offset('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.offset(), 'nothing provided').to.throw(Error); + expect(() => qb.offset(null), 'null provided').to.throw(Error); + expect(() => qb.offset(true), 'true provided').to.throw(Error); + expect(() => qb.offset(false), 'false provided').to.throw(Error); + expect(() => qb.offset(''), 'empty string provided').to.throw(Error); + expect(() => qb.offset({}), 'empty object provided').to.throw(Error); + expect(() => qb.offset([]), 'empty array provided').to.throw(Error); + expect(() => qb.offset([5]), 'array with integer in it provided').to.throw(Error); + }); + it('should override the default offset_val value when a offset is provided', () => { qb.reset_query(); qb.offset(10); qb.offset_val.should.eql([10]); }); - it('should trim string values that are provided', function() { + it('should trim string values that are provided', () => { qb.reset_query(); qb.offset('10 '); qb.offset(' 10'); qb.offset(' 10 '); qb.offset_val.should.eql([10]); }); - it('should override values set by any previous calls to itself', function() { + it('should override values set by any previous calls to itself', () => { qb.reset_query(); qb.offset(10); qb.offset_val.should.eql([10]); qb.offset(20); qb.offset_val.should.eql([20]); }); - it('should be chainable whereby the last call to the method will contain the value used', function() { + it('should be chainable whereby the last call to the method will contain the value used', () => { qb.reset_query(); qb.offset(10).offset(20).offset(100); qb.offset_val.should.eql([100]); diff --git a/test/mysql/tests-orderby.js b/test/mysql/tests-orderby.js index 9a29a7f..8ce91ef 100755 --- a/test/mysql/tests-orderby.js +++ b/test/mysql/tests-orderby.js @@ -2,91 +2,91 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('order_by()', function() { - it('should exist', function() { +describe('order_by()', () => { + it('should exist', () => { should.exist(qb.order_by); }); - it('should be a function', function() { + it('should be a function', () => { qb.order_by.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('order_by_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.order_by_array.should.be.empty; }); - it('should require non-empty string or array as first param unless random is provided as second parameter', function() { - expect(function() { qb.order_by(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.order_by(null); }, 'null provided').to.throw(Error); - expect(function() { qb.order_by(false); }, 'false provided').to.throw(Error); - expect(function() { qb.order_by(true); }, 'true provided').to.throw(Error); - expect(function() { qb.order_by({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.order_by(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.order_by(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.order_by([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.order_by(''); }, 'empty string provided').to.throw(Error); + it('should require non-empty string or array as first param unless random is provided as second parameter', () => { + expect(() => qb.order_by(), 'nothing provided').to.throw(Error); + expect(() => qb.order_by(null), 'null provided').to.throw(Error); + expect(() => qb.order_by(false), 'false provided').to.throw(Error); + expect(() => qb.order_by(true), 'true provided').to.throw(Error); + expect(() => qb.order_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.order_by(3), 'integer provided').to.throw(Error); + expect(() => qb.order_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.order_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.order_by(''), 'empty string provided').to.throw(Error); // If random - expect(function() { qb.order_by('','rand'); }, 'empty string and random direction provided').to.not.throw(Error); - expect(function() { qb.order_by(undefined,'rand'); }, 'undefined and random direction provided').to.not.throw(Error); - expect(function() { qb.order_by(null,'rand'); }, 'null and random direction provided').to.not.throw(Error); - expect(function() { qb.order_by(false,'rand'); }, 'false and random direction provided').to.not.throw(Error); - expect(function() { qb.order_by([],'rand'); }, 'empty array and random direction provided').to.not.throw(Error); + expect(() => qb.order_by('','rand'), 'empty string and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(undefined,'rand'), 'undefined and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(null,'rand'), 'null and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(false,'rand'), 'false and random direction provided').to.not.throw(Error); + expect(() => qb.order_by([],'rand'), 'empty array and random direction provided').to.not.throw(Error); }); - it('should accept a field and direction separated by a space as first parameter and escape the field', function() { + it('should accept a field and direction separated by a space as first parameter and escape the field', () => { qb.reset_query(); qb.order_by('planet_position asc'); qb.order_by_array.should.eql(['`planet_position` ASC']); }); - it('should accept a field and direction as seperate parameters and escape the field', function() { + it('should accept a field and direction as seperate parameters and escape the field', () => { qb.reset_query(); qb.order_by('planet_position', 'asc'); qb.order_by_array.should.eql(['`planet_position` ASC']); }); - it('should add additional order_by calls to teh order by array', function() { + it('should add additional order_by calls to teh order by array', () => { qb.reset_query(); qb.order_by('planet_position', 'asc'); qb.order_by('planet_size', 'desc'); qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); }); - it('should assume ASC when no direction is provided', function() { + it('should assume ASC when no direction is provided', () => { qb.reset_query(); qb.order_by('planet_position'); qb.order_by_array.should.eql(['`planet_position` ASC']); }); - it('should only accept valid ordering directions (ASC, DESC, random)', function() { - qb.reset_query(); - expect(function() { qb.order_by('planet_position'); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position','ASC'); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position','DESC'); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position','random'); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',null); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',undefined); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',false); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',3); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',true); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',[]); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',{}); }).to.not.throw(Error); - expect(function() { qb.order_by('planet_position',''); }).to.not.throw(Error); + it('should only accept valid ordering directions (ASC, DESC, random)', () => { + qb.reset_query(); + expect(() => qb.order_by('planet_position')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','ASC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','DESC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','random')).to.not.throw(Error); + expect(() => qb.order_by('planet_position',null)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',undefined)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',false)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',3)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',true)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',[])).to.not.throw(Error); + expect(() => qb.order_by('planet_position',{})).to.not.throw(Error); + expect(() => qb.order_by('planet_position','')).to.not.throw(Error); // Only an invalid string will throw an error - expect(function() { qb.order_by('planet_position','FAKE'); }).to.throw(Error); + expect(() => qb.order_by('planet_position','FAKE')).to.throw(Error); }); - it('should accept a comma-separated list of fields to order by with a single direction at the end', function() { + it('should accept a comma-separated list of fields to order by with a single direction at the end', () => { qb.reset_query(); qb.order_by('planet_position, planet_size asc'); qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); }); - it('should accept a comma-separated list of field + direction pairs', function() { + it('should accept a comma-separated list of field + direction pairs', () => { qb.reset_query(); qb.order_by('planet_position desc, planet_size asc'); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); }); - it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', function() { + it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', () => { qb.reset_query(); qb.order_by('planet_position', 'random'); qb.order_by_array.should.eql(['RAND()']); @@ -107,12 +107,12 @@ describe('order_by()', function() { qb.order_by('', 'rand'); qb.order_by_array.should.eql(['RAND()']); }); - it('should accept an array of field + direction pairs', function() { + it('should accept an array of field + direction pairs', () => { qb.reset_query(); qb.order_by(['planet_position DESC', 'planet_size ASC']); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); }); - it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', function() { + it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', () => { qb.reset_query(); qb.order_by(['planet_position', 'planet_size'], 'desc'); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); @@ -121,7 +121,7 @@ describe('order_by()', function() { qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); }); - it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', function() { + it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', () => { qb.reset_query(); qb.order_by(['planet_position', 'planet_size']); qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index 5659f7d..6376d23 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -2,123 +2,123 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('select()', function() { - it('should exist', function() { +describe('select()', () => { + it('should exist', () => { should.exist(qb.select); }); - it('should be a function', function() { + it('should be a function', () => { qb.select.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('select_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.select_array.should.be.empty; }); - it('should require an array or string to be passed as first parameter', function() { + it('should require an array or string to be passed as first parameter', () => { const invalid_match = /requires a string or array/; const empty_str_match = /string is empty/; const empty_arr_match = /array is empty/; - expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); - expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); - expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); - expect(function() { qb.select(false); }, 'false provided').to.throw(Error, invalid_match); - expect(function() { qb.select({}); }, 'object provided').to.throw(Error, invalid_match); - expect(function() { qb.select([]); }, 'empty array provided').to.throw(Error, empty_arr_match); - expect(function() { qb.select(''); }, 'empty string provided').to.throw(Error, empty_str_match); - expect(function() { qb.select(' '); }, 'string of spaces provided').to.throw(Error, empty_str_match); - expect(function() { qb.select('blah'); }, 'valid string provided').to.not.throw(Error); - }); - it('should add field to array and escape it properly', function() { + expect(() => qb.select(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.select(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.select(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.select(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.select({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.select([]), 'empty array provided').to.throw(Error, empty_arr_match); + expect(() => qb.select(''), 'empty string provided').to.throw(Error, empty_str_match); + expect(() => qb.select(' '), 'string of spaces provided').to.throw(Error, empty_str_match); + expect(() => qb.select('blah'), 'valid string provided').to.not.throw(Error); + }); + it('should add field to array and escape it properly', () => { qb.reset_query(); qb.select('notes'); qb.select_array.should.eql(['`notes`']); }); - it('should trim fields properly before placing them into the select array', function() { + it('should trim fields properly before placing them into the select array', () => { qb.reset_query(); qb.select(' notes '); qb.select_array.should.eql(['`notes`']); }); - it('should have an empty array after resetting', function() { + it('should have an empty array after resetting', () => { qb.reset_query(); qb.select_array.should.be.empty; }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.select('foo',false); qb.select_array.should.eql(['foo']); }); - it('should accept a comma-delimited string of field names and trim and escape each properly', function() { + it('should accept a comma-delimited string of field names and trim and escape each properly', () => { qb.reset_query(); qb.select('do,re , mi, fa'); qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); }); - it('should be allowed to be called multiple times to add multiple fields to the select array', function() { + it('should be allowed to be called multiple times to add multiple fields to the select array', () => { qb.reset_query(); qb.select('do').select('re').select('mi').select('fa'); qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); }); - it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', function() { + it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', () => { qb.reset_query(); qb.select('do').select('re',false).select('mi',false).select('fa'); qb.select_array.should.eql(['`do`','re','mi','`fa`']); }); - it('should accept an array of fields and add them individually to the select array', function() { + it('should accept an array of fields and add them individually to the select array', () => { qb.reset_query(); qb.select(['sol','la','ti','do']); qb.select_array.should.eql(['`sol`','`la`','`ti`','`do`']); }); - it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', function() { + it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', () => { qb.reset_query(); qb.select(['sol','la','ti','do'],false); qb.select_array.should.eql(['sol','la','ti','do']); }); - it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', function() { + it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', () => { qb.reset_query(); qb.select(['`sol`','la','ti','`do`'],false); qb.select_array.should.eql(['`sol`','la','ti','`do`']); }); - it('should not double-escape a field', function() { + it('should not double-escape a field', () => { qb.reset_query(); qb.select('`do`'); qb.select_array.should.eql(['`do`']); }); - it('should not double-escape fields when provided with an array of pre-escaped fields', function() { + it('should not double-escape fields when provided with an array of pre-escaped fields', () => { qb.reset_query(); qb.select(['`do`','`re`','`mi`']); qb.select_array.should.eql(['`do`','`re`','`mi`']); }); - it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', function() { + it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', () => { qb.reset_query(); qb.select(['`do`','re','`mi`']); qb.select_array.should.eql(['`do`','`re`','`mi`']); }); - it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', function() { + it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', () => { qb.reset_query(); qb.select('foo as bar'); qb.select_array.should.eql(['`foo` as `bar`']); }); - it('should not double-escape aliases', function() { + it('should not double-escape aliases', () => { qb.reset_query(); qb.select(['foo as `bar`']); qb.select_array.should.eql(['`foo` as `bar`']); }); - it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', function() { + it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', () => { qb.reset_query(); qb.select(['foo as bar','bar as foo']); qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`']); }); - it('should allow for field aliases with spaces in them', function() { + it('should allow for field aliases with spaces in them', () => { qb.reset_query(); qb.select('notes as The Notes'); qb.select_array.should.eql(['`notes` as `The Notes`']); }); - it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', function() { + it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', () => { qb.reset_query(); qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); }); - it('should allow for namespacing in field name (host.db.table.field)', function() { + it('should allow for namespacing in field name (host.db.table.field)', () => { qb.reset_query(); qb.select('star_system.planet'); qb.select_array.should.eql(['`star_system`.`planet`']); @@ -131,22 +131,22 @@ describe('select()', function() { qb.select('universe.galaxy.star_system.planet'); qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); }); - it('should allow for namespacing in field name (host.db.table.column) + alias', function() { + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { qb.reset_query(); qb.select('universe.galaxy.star_system.planet as planet'); qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); }); - it('should not allow subqueries or functions with commas in them without the second parameter being false', function() { + it('should not allow subqueries or functions with commas in them without the second parameter being false', () => { qb.reset_query(); - expect(function() { - qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); - }).to.throw(Error); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') + ).to.throw(Error); - expect(function() { - qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); - }).to.not.throw(Error); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false) + ).to.not.throw(Error); }); - it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', function() { + it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', () => { qb.reset_query(); qb.select('count(*) as count', false); qb.select_array.should.eql(['count(*) AS `count`']); @@ -168,14 +168,14 @@ describe('select()', function() { const prefixes = ['min','max','avg','sum']; for (const i in prefixes) { const type = prefixes[i]; - describe('select_' + type+'()', function() { - it('should exist', function() { + describe('select_' + type+'()', () => { + it('should exist', () => { should.exist(qb['select_' + type]); }); - it('should be a function', function() { + it('should be a function', () => { qb['select_' + type].should.be.a('function'); }); - it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', function() { + it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', () => { qb.reset_query(); qb['select_' + type]('s.star_systems'); qb.select_array.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index aa5a300..a6d8e6e 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -2,65 +2,65 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('set()', function() { - it('should exist', function() { +describe('set()', () => { + it('should exist', () => { should.exist(qb.set); }); - it('should be a function', function() { + it('should be a function', () => { qb.set.should.be.a('function'); }); - it('should have an object to put fields into', function() { + it('should have an object to put fields into', () => { qb.should.have.property('set_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.set_array.should.be.empty; }); - it('should not accept anything but a non-empty string or a non-empty object as first param', function() { + it('should not accept anything but a non-empty string or a non-empty object as first param', () => { qb.reset_query(); - expect(function() { qb.set(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.set(null); }, 'null provided').to.throw(Error); - expect(function() { qb.set(false); }, 'false provided').to.throw(Error); - expect(function() { qb.set(true); }, 'true provided').to.throw(Error); - expect(function() { qb.set({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.set(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.set(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.set(NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.set(Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.set([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.set([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.set(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.set(' '); }, 'string full of spaces provided').to.throw(Error); - expect(function() { qb.set(/foobar/); }, 'regex provided').to.throw(Error); + expect(() => qb.set(), 'nothing provided').to.throw(Error); + expect(() => qb.set(null), 'null provided').to.throw(Error); + expect(() => qb.set(false), 'false provided').to.throw(Error); + expect(() => qb.set(true), 'true provided').to.throw(Error); + expect(() => qb.set({}), 'empty object provided').to.throw(Error); + expect(() => qb.set(3), 'integer provided').to.throw(Error); + expect(() => qb.set(3.5), 'float provided').to.throw(Error); + expect(() => qb.set(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.set(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.set([]), 'empty array provided').to.throw(Error); + expect(() => qb.set([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.set(''), 'empty string provided').to.throw(Error); + expect(() => qb.set(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.set(/foobar/), 'regex provided').to.throw(Error); - expect(function() { qb.set('planet_position',3); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.set({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); + expect(() => qb.set('planet_position',3), 'valid string provided').to.not.throw(Error); + expect(() => qb.set({planet_position: 3}), 'valid object provided').to.not.throw(Error); }); - it('should not accept anything but a string, number, date, null, or boolean as second param if first param is a string.', function() { + it('should not accept anything but a string, number, date, null, or boolean as second param if first param is a string.', () => { qb.reset_query(); - expect(function() { qb.set('planet_position'); }, 'nothing provided').to.throw(Error); - expect(function() { qb.set('planet_position',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.set('planet_position',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.set('planet_position',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.set('planet_position',[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.set('planet_position',[1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.set('planet_position',/foobar/); }, 'regex provided').to.throw(Error); + expect(() => qb.set('planet_position'), 'nothing provided').to.throw(Error); + expect(() => qb.set('planet_position',{}), 'empty object provided').to.throw(Error); + expect(() => qb.set('planet_position',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.set('planet_position',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.set('planet_position',[]), 'empty array provided').to.throw(Error); + expect(() => qb.set('planet_position',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.set('planet_position',/foobar/), 'regex provided').to.throw(Error); - expect(function() { qb.set('planet_position',new Date()); }, 'date provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',null); }, 'null provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',3); }, 'Integer provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',3.5); }, 'float provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',false); }, 'false provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',true); }, 'true provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',''); }, 'empty string provided').to.not.throw(Error); - expect(function() { qb.set('planet_position',' '); }, 'string full of spaces provided').to.not.throw(Error); - expect(function() { qb.set('planet_position','Three'); }, 'non-empty string provided').to.not.throw(Error); + expect(() => qb.set('planet_position',new Date()), 'date provided').to.not.throw(Error); + expect(() => qb.set('planet_position',null), 'null provided').to.not.throw(Error); + expect(() => qb.set('planet_position',3), 'Integer provided').to.not.throw(Error); + expect(() => qb.set('planet_position',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.set('planet_position',false), 'false provided').to.not.throw(Error); + expect(() => qb.set('planet_position',true), 'true provided').to.not.throw(Error); + expect(() => qb.set('planet_position',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.set('planet_position',' '), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.set('planet_position','Three'), 'non-empty string provided').to.not.throw(Error); }); - it('should add first param (key) and second param (value) to hash and escape them properly', function() { + it('should add first param (key) and second param (value) to hash and escape them properly', () => { qb.reset_query(); qb.set('galaxy_name','Milky Way'); qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); }); - it('should merge passed object into set_array and escape items properly', function() { + it('should merge passed object into set_array and escape items properly', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}); qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}]); @@ -69,31 +69,31 @@ describe('set()', function() { qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); qb.set_array.should.eql([{"`galaxy_name`": "'Milky Way'"}, {"`galaxy_class`": "'C'"}]); }); - it('should not escape items if asked not to', function() { + it('should not escape items if asked not to', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set_array.should.eql([{galaxy_name: 'Milky Way'}]); }); - it('should append more items to set_array as set() is called', function() { + it('should append more items to set_array as set() is called', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}, null, false); qb.set('galaxy_size','D'); qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"`galaxy_size`": "'D'"}]); }); - it('should overwrite values of keys that have been set already', function() { + it('should overwrite values of keys that have been set already', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); qb.set('galaxy_class','D'); qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"`galaxy_class`": "'D'"}]); }); - it('should NOT overwrite values of keys that are the same but have different escape flags', function() { + it('should NOT overwrite values of keys that are the same but have different escape flags', () => { qb.reset_query(); qb.set({galaxy_name: 'Milky Way'}, null, false); qb.set({galaxy_class: 'C'}); diff --git a/test/mysql/tests-truncate.js b/test/mysql/tests-truncate.js index fe75e9a..02082aa 100755 --- a/test/mysql/tests-truncate.js +++ b/test/mysql/tests-truncate.js @@ -2,21 +2,21 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('truncate()', function() { - it('should exist', function() { +describe('truncate()', () => { + it('should exist', () => { should.exist(qb.truncate); }); - it('should be a function', function() { + it('should be a function', () => { qb.truncate.should.be.a('function'); }); - it('should return a string', function() { + it('should return a string', () => { qb.reset_query(); const sql = qb.truncate('galaxies'); expect(sql).to.be.a('string'); expect(sql).to.exist; expect(sql).to.not.eql(''); }); - it('should build a proper truncate statement', function() { + it('should build a proper truncate statement', () => { qb.reset_query(); const sql = qb.truncate('galaxies'); sql.should.eql('TRUNCATE `galaxies`'); diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index eaa1ebe..aa0d98f 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -8,117 +8,117 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('update()', function() { - it('should exist', function() { +describe('update()', () => { + it('should exist', () => { should.exist(qb.update); }); - it('should be a function', function() { + it('should be a function', () => { qb.update.should.be.a('function'); }); - it('should add a table to from_array when a table is supplied', function() { + it('should add a table to from_array when a table is supplied', () => { qb.reset_query(); qb.update('galaxies', test_data, test_where); qb.from_array.should.eql(['`galaxies`']); }); - it('should accept a string or falsy value for the table (first) parameter', function() { + it('should accept a string or falsy value for the table (first) parameter', () => { qb.reset_query(); // One could choose to pass a falsy value to the first param because they have or will // supply it with the from() method instead. - qb.reset_query(); expect(function() { qb.from('galaxies').update([], test_data); }, 'empty array provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update({}, test_data); }, 'empty object provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(3, test_data); }, 'integer provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(3.5, test_data); }, 'float provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(true, test_data); }, 'true provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(Infinity, test_data); }, 'Infinity provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update([1,2], test_data); }, 'array of numbers provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(/foobar/, test_data); }, 'regex provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update([], test_data), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update({}, test_data), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3, test_data), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3.5, test_data), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(true, test_data), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(Infinity, test_data), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update([1,2], test_data), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(/foobar/, test_data), 'regex provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(NaN, test_data); }, 'NaN provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(false, test_data); }, 'false provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update('', test_data); }, 'empty string provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(' ', test_data); }, 'string full of spaces provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(null, test_data); }, 'null provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.from('galaxies').update(undefined, test_data); },'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(NaN, test_data), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(false, test_data), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update('', test_data), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(null, test_data), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(undefined, test_data),'undefined provided').to.not.throw(Error); }); - it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', function() { + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { // One could choose to pass a falsy value to the second param because they have or will // supply data with the set() method instead. - qb.reset_query(); expect(function() { qb.update('galaxies',test_data); }, 'non-empty array provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',test_data_set); }, 'array of non-empty standard objects provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',test_data_set), 'array of non-empty standard objects provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',NaN); }, 'NaN provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',false); }, 'false provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',''); }, 'empty string provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',null); }, 'null provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',undefined); }, 'undefined provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies'); }, 'nothing provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',NaN), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',false), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',''), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',null), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',undefined), 'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies'), 'nothing provided').to.not.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3); }, 'integer provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',3.5); }, 'float provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',true); }, 'true provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',Infinity); }, 'Infinity provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('foobar',{}); }, 'empty object provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[{},{}]); }, 'array of empty objects provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[]); }, 'empty array provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',[1,2]); }, 'array of numbers provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]); }, 'array of mixed values provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',/foobar/); }, 'regex provided').to.throw(Error); - qb.reset_query(); expect(function() { qb.set({id:2}).update('galaxies',' '); }, 'string full of spaces provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3.5), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',true), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',Infinity), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('foobar',{}), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[]), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',/foobar/), 'regex provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',' '), 'string full of spaces provided').to.throw(Error); }); - it('should require that there is at least something being updated', function() { + it('should require that there is at least something being updated', () => { // @todo }); - it('should utilize pre-existing tables set in from_array', function() { + it('should utilize pre-existing tables set in from_array', () => { qb.reset_query(); qb.from('galaxies'); const sql = qb.update(null, test_data, test_where); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); }); - it('should utilize pre-existing value set in in set_array', function() { + it('should utilize pre-existing value set in in set_array', () => { qb.reset_query(); qb.set(test_data); const sql = qb.update('galaxies'); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); - it('should utilize pre-existing tables and values from from_aray and set_array, respectively', function() { + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { qb.reset_query(); qb.from('galaxies').set(test_data); const sql = qb.update(); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); - it('should accept a non-empty object for the data parameter', function() { + it('should accept a non-empty object for the data parameter', () => { qb.reset_query(); const sql = qb.update('galaxies', test_data); sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); }); - it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', function() { + it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', () => { qb.reset_query(); const sql = qb.update('galaxies', test_data_set); qb.reset_query(); const sql_b = qb.update_batch('galaxies', test_data_set, 'id'); sql.should.eql(sql_b); }); - it('should fail if any invalid values are passed in the data object.', function() { + it('should fail if any invalid values are passed in the data object.', () => { qb.reset_query(); const func = () => { console.log("foo"); }; const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; - qb.reset_query(); expect(function() { qb.update('galaxies',{id: func}); }, 'function in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: regex}); }, 'regex in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: Infinity}); }, 'Infinity in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: undefined}); }, 'undefined in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: NaN}); }, 'NaN in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: arr}); }, 'array in data').to.throw(Error); - qb.reset_query(); expect(function() { qb.update('galaxies',{id: obj}); }, 'object in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: func}), 'function in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: regex}), 'regex in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: undefined}), 'undefined in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: arr}), 'array in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: obj}), 'object in data').to.throw(Error); }); /* - it('should support insert ignore statements', function() { + it('should support insert ignore statements', () => { qb.reset_query(); const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); diff --git a/test/mysql/tests-update_batch.js b/test/mysql/tests-update_batch.js index 7592623..217aff9 100755 --- a/test/mysql/tests-update_batch.js +++ b/test/mysql/tests-update_batch.js @@ -5,14 +5,14 @@ const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('update_batch()', function() { - it('should exist', function() { +describe('update_batch()', () => { + it('should exist', () => { should.exist(qb.update_batch); }); - it('should be a function', function() { + it('should be a function', () => { qb.update_batch.should.be.a('function'); }); - it('should build a proper batch UPDATE string', function() { + it('should build a proper batch UPDATE string', () => { qb.reset_query(); const sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js index 4ac74be..41f5d11 100755 --- a/test/mysql/tests-where.js +++ b/test/mysql/tests-where.js @@ -2,52 +2,52 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('where()', function() { - it('should exist', function() { +describe('where()', () => { + it('should exist', () => { should.exist(qb.where); }); - it('should be a function', function() { + it('should be a function', () => { qb.where.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('where_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.where_array.should.be.empty; }); - it('should not accept anything but a non-empty string or a non-empty object', function() { - qb.reset_query(); - expect(function() { qb.where(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.where(null); }, 'null provided').to.throw(Error); - expect(function() { qb.where(false); }, 'false provided').to.throw(Error); - expect(function() { qb.where(true); }, 'true provided').to.throw(Error); - expect(function() { qb.where({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.where(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.where(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.where([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.where([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.where(''); }, 'empty string provided').to.throw(Error); + it('should not accept anything but a non-empty string or a non-empty object', () => { + qb.reset_query(); + expect(() => qb.where(), 'nothing provided').to.throw(Error); + expect(() => qb.where(null), 'null provided').to.throw(Error); + expect(() => qb.where(false), 'false provided').to.throw(Error); + expect(() => qb.where(true), 'true provided').to.throw(Error); + expect(() => qb.where({}), 'empty object provided').to.throw(Error); + expect(() => qb.where(3), 'integer provided').to.throw(Error); + expect(() => qb.where(3.5), 'float provided').to.throw(Error); + expect(() => qb.where([]), 'empty array provided').to.throw(Error); + expect(() => qb.where([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where(''), 'empty string provided').to.throw(Error); - expect(function() { qb.where('planet_position',3); }, 'valid string provided').to.not.throw(Error); - expect(function() { qb.where({planet_position: 3}); }, 'valid object provided').to.not.throw(Error); + expect(() => qb.where('planet_position',3), 'valid string provided').to.not.throw(Error); + expect(() => qb.where({planet_position: 3}), 'valid object provided').to.not.throw(Error); }); - it('should accept a field name in the form of a string as the first parameter', function() { + it('should accept a field name in the form of a string as the first parameter', () => { qb.reset_query(); qb.where('planet'); qb.where_array.should.eql(['`planet` IS NULL']); }); - it('should assume second param is NULL if not provided', function() { + it('should assume second param is NULL if not provided', () => { qb.reset_query(); qb.where('planet'); qb.where_array.should.eql(['`planet` IS NULL']); }); - it('should accept NULL as second parameter and assume IS NULL', function() { + it('should accept NULL as second parameter and assume IS NULL', () => { qb.reset_query(); qb.where('planet',null); qb.where_array.should.eql(['`planet` IS NULL']); }); - it('should accept boolean values and will transform them properly', function() { + it('should accept boolean values and will transform them properly', () => { qb.reset_query(); qb.where('planet',true); qb.where_array.should.eql(['`planet` = 1']); @@ -56,7 +56,7 @@ describe('where()', function() { qb.where('planet',false); qb.where_array.should.eql(['`planet` = 0']); }); - it('should accept integer and float values', function() { + it('should accept integer and float values', () => { qb.reset_query(); qb.where('planet',5); qb.where_array.should.eql(['`planet` = 5']); @@ -65,7 +65,7 @@ describe('where()', function() { qb.where('planet',123.456); qb.where_array.should.eql(['`planet` = 123.456']); }); - it('should accept string values', function() { + it('should accept string values', () => { qb.reset_query(); qb.where('planet','Earth'); qb.where_array.should.eql(["`planet` = 'Earth'"]); @@ -74,33 +74,33 @@ describe('where()', function() { qb.where('galaxy','Milky Way'); qb.where_array.should.eql(["`galaxy` = 'Milky Way'"]); }); - it('should accept arrays of values and assume a WHERE IN clause', function() { + it('should accept arrays of values and assume a WHERE IN clause', () => { qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); }); - it('should concatenate multiple where clauses with AND by default', function() { + it('should concatenate multiple where clauses with AND by default', () => { qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']); qb.where('galaxy','Milky Way'); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); }); - it('should accept an object of key/value pairs (field: value)', function() { + it('should accept an object of key/value pairs (field: value)', () => { qb.reset_query(); qb.where({planet:'Earth',star_system:'Solar'}); qb.where_array.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); }); - it('should accept an object of key/value pairs (field: value) where values can be arrays', function() { + it('should accept an object of key/value pairs (field: value) where values can be arrays', () => { qb.reset_query(); qb.where({star_system:'Solar',planet:['Earth','Mars']}); qb.where_array.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); }); - it('should accept an operators in the first parameter', function() { + it('should accept an operators in the first parameter', () => { qb.reset_query(); qb.where('position >',3); qb.where_array.should.eql(["`position` > 3"]); @@ -125,47 +125,47 @@ describe('where()', function() { qb.where('position !=',3); qb.where_array.should.eql(["`position` != 3"]); }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.where({star_system:'Solar',planet:['Earth','Mars']},false); qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); - it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", function() { + it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { qb.reset_query(); qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); }); - it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", function() { + it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { qb.reset_query(); qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); }); - it("should escape (quote) functions and subqueries as strings when provided as second parameter", function() { + it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { qb.reset_query(); qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); }); }); -describe('or_where()', function() { - it('should exist', function() { +describe('or_where()', () => { + it('should exist', () => { should.exist(qb.or_where); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_where.should.be.a('function'); }); - it('should prepend tertiary WHERE clauses with "OR"', function() { + it('should prepend tertiary WHERE clauses with "OR"', () => { qb.reset_query(); qb.or_where('planet','Mars'); qb.or_where('planet','Venus'); qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.or_where('planet','Mars').or_where('planet','Venus'); qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); }); - it('should be chainable with normal where', function() { + it('should be chainable with normal where', () => { qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); diff --git a/test/mysql/tests-where_in.js b/test/mysql/tests-where_in.js index 970a0c6..527dce7 100755 --- a/test/mysql/tests-where_in.js +++ b/test/mysql/tests-where_in.js @@ -2,173 +2,173 @@ const should = require('chai').should(); const expect = require('chai').expect; const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); -describe('where_in()', function() { - it('should exist', function() { +describe('where_in()', () => { + it('should exist', () => { should.exist(qb.where_in); }); - it('should be a function', function() { + it('should be a function', () => { qb.where_in.should.be.a('function'); }); - it('should have an array to put fields into', function() { + it('should have an array to put fields into', () => { qb.should.have.property('where_in_array'); }); - it('should have an empty array to put fields into at the beginning', function() { + it('should have an empty array to put fields into at the beginning', () => { qb.where_in_array.should.be.empty; }); - it('should not accept anything but a non-empty string as first parameter', function() { - qb.reset_query(); - expect(function() { qb.where_in(); }, 'nothing provided').to.throw(Error); - expect(function() { qb.where_in(null); }, 'null provided').to.throw(Error); - expect(function() { qb.where_in(false); }, 'false provided').to.throw(Error); - expect(function() { qb.where_in(true); }, 'true provided').to.throw(Error); - expect(function() { qb.where_in({}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.where_in({foo:'bar'}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.where_in(3); }, 'integer provided').to.throw(Error); - expect(function() { qb.where_in(3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.where_in(NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.where_in(Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.where_in([]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.where_in([1,2]); }, 'array of numbers provided').to.throw(Error); - expect(function() { qb.where_in(''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.where_in(/foobar/); }, 'regex provided').to.throw(Error); + it('should not accept anything but a non-empty string as first parameter', () => { + qb.reset_query(); + expect(() => qb.where_in(), 'nothing provided').to.throw(Error); + expect(() => qb.where_in(null), 'null provided').to.throw(Error); + expect(() => qb.where_in(false), 'false provided').to.throw(Error); + expect(() => qb.where_in(true), 'true provided').to.throw(Error); + expect(() => qb.where_in({}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in({foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in(3), 'integer provided').to.throw(Error); + expect(() => qb.where_in(3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in([]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where_in(''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in(/foobar/), 'regex provided').to.throw(Error); - expect(function() { qb.where_in('planet_position',[1,2,3]); }, 'valid string provided').to.not.throw(Error); - }); - it('should not accept anything but a non-empty array of values as second parameter', function() { - qb.reset_query(); - expect(function() { qb.where_in('planet'); }, 'nothing provided').to.throw(Error); - expect(function() { qb.where_in('planet',null); }, 'null provided').to.throw(Error); - expect(function() { qb.where_in('planet',false); }, 'false provided').to.throw(Error); - expect(function() { qb.where_in('planet',true); }, 'true provided').to.throw(Error); - expect(function() { qb.where_in('planet',{}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.where_in('planet',{foo:'bar'}); }, 'empty object provided').to.throw(Error); - expect(function() { qb.where_in('planet',3); }, 'integer provided').to.throw(Error); - expect(function() { qb.where_in('planet',3.5); }, 'float provided').to.throw(Error); - expect(function() { qb.where_in('planet',NaN); }, 'NaN provided').to.throw(Error); - expect(function() { qb.where_in('planet',Infinity); }, 'Infinity provided').to.throw(Error); - expect(function() { qb.where_in('planet',[]); }, 'empty array provided').to.throw(Error); - expect(function() { qb.where_in('planet',''); }, 'empty string provided').to.throw(Error); - expect(function() { qb.where_in('planet',/foobar/); }, 'regex provided').to.throw(Error); + expect(() => qb.where_in('planet_position',[1,2,3]), 'valid string provided').to.not.throw(Error); + }); + it('should not accept anything but a non-empty array of values as second parameter', () => { + qb.reset_query(); + expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); + expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); + expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); + expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); + expect(() => qb.where_in('planet',{}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',{foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',3), 'integer provided').to.throw(Error); + expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); - expect(function() { qb.where_in('planet',['Mars','Earth','Venus','Mercury']); }, 'non-empty array provided').to.not.throw(Error); + expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); }); - it('should require both a field name an array of values as first and second parameters, respectively', function() { + it('should require both a field name an array of values as first and second parameters, respectively', () => { qb.reset_query(); qb.where_in('planet_position',[1,2,3]); qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)']); }); - it('should concatenate multiple WHERE IN clauses with AND ', function() { + it('should concatenate multiple WHERE IN clauses with AND ', () => { qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']); qb.where_in('galaxy_id',[123,456,789,0110]); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.where_in('planet_position',[1,2,3],false); qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); -describe('where_not_in()', function() { - it('should exist', function() { +describe('where_not_in()', () => { + it('should exist', () => { should.exist(qb.where_not_in); }); - it('should be a function', function() { + it('should be a function', () => { qb.where_not_in.should.be.a('function'); }); - it('should prepend "NOT " to "IN"', function() { + it('should prepend "NOT " to "IN"', () => { qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); }); - it('should prepend tertiary WHERE clauses with "AND"', function() { + it('should prepend tertiary WHERE clauses with "AND"', () => { qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]); qb.where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); }); - it('should be chainable with normal where', function() { + it('should be chainable with normal where', () => { qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.where_not_in('planet_position',[1,2,3],false); qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); }); }); -describe('or_where_in()', function() { - it('should exist', function() { +describe('or_where_in()', () => { + it('should exist', () => { should.exist(qb.or_where_in); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_where_in.should.be.a('function'); }); - it('should prepend tertiary WHERE clauses with "OR"', function() { + it('should prepend tertiary WHERE clauses with "OR"', () => { qb.reset_query(); qb.or_where_in('planet_position',[1,2,3]); qb.or_where_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); }); - it('should be chainable with normal where', function() { + it('should be chainable with normal where', () => { qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.or_where_in('planet_position',[1,2,3],false); qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); -describe('or_where_not_in()', function() { - it('should exist', function() { +describe('or_where_not_in()', () => { + it('should exist', () => { should.exist(qb.or_where_in); }); - it('should be a function', function() { + it('should be a function', () => { qb.or_where_in.should.be.a('function'); }); - it('should prepend "NOT " to "IN"', function() { + it('should prepend "NOT " to "IN"', () => { qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); }); - it('should prepend tertiary WHERE clauses with "OR"', function() { + it('should prepend tertiary WHERE clauses with "OR"', () => { qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]); qb.or_where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); }); - it('should be chainable', function() { + it('should be chainable', () => { qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); }); - it('should be chainable with normal where', function() { + it('should be chainable with normal where', () => { qb.reset_query(); qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); }); - it('should not escape fields if asked not to', function() { + it('should not escape fields if asked not to', () => { qb.reset_query(); qb.or_where_not_in('planet_position',[1,2,3],false); qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); diff --git a/tests.md b/tests.md index b247846..e792625 100755 --- a/tests.md +++ b/tests.md @@ -218,16 +218,16 @@ should require a string to be passed as first parameter. ```js const invalid_match = /must provide a table/; -expect(function() { qb.join(); }, 'nothing provided').to.throw(Error, invalid_match); -expect(function() { qb.join(true); }, 'true provided').to.throw(Error, invalid_match); -expect(function() { qb.join(null); }, 'null provided').to.throw(Error, invalid_match); -expect(function() { qb.join(false); }, 'false provided').to.throw(Error, invalid_match); -expect(function() { qb.join({}); }, 'object provided').to.throw(Error, invalid_match); -expect(function() { qb.join([]); }, 'empty array provided').to.throw(Error, invalid_match); -expect(function() { qb.join(''); }, 'empty string provided').to.throw(Error, invalid_match); -expect(function() { qb.join(' '); }, 'string of spaces provided').to.throw(Error, invalid_match); -expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); -expect(function() { qb.join('foo'); }, 'valid string provided').to.not.throw(Error); +expect(() => qb.join(), 'nothing provided').to.throw(Error, invalid_match); +expect(() => qb.join(true), 'true provided').to.throw(Error, invalid_match); +expect(() => qb.join(null), 'null provided').to.throw(Error, invalid_match); +expect(() => qb.join(false), 'false provided').to.throw(Error, invalid_match); +expect(() => qb.join({}), 'object provided').to.throw(Error, invalid_match); +expect(() => qb.join([]), 'empty array provided').to.throw(Error, invalid_match); +expect(() => qb.join(''), 'empty string provided').to.throw(Error, invalid_match); +expect(() => qb.join(' '), 'string of spaces provided').to.throw(Error, invalid_match); +expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); +expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); ``` should except single item and add it to join array as basic join and escape item. @@ -252,17 +252,17 @@ should allow a string (and only a string) to be passed as second parameter but o const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; const invalid_direction = /Invalid join direction provided as third parameter/; -expect(function() { qb.join('universe',null,'left'); }, 'null 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',false,'left'); }, 'false 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe','','left'); }, 'empty string 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',' ','left'); }, 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',5,'left'); }, 'integer 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',5.6,'left'); }, 'float 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',[],'left'); }, 'array 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe',{},'left'); }, 'object 2nd param').to.throw(Error,invalid_2nd_param); -expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); -expect(function() { qb.join('universe','foo = bar'); }, 'no 3rd param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar','left'); }, '3 valid params').to.not.throw(Error); +expect(() => qb.join('universe',null,'left'), 'null 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',false,'left'), 'false 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe','','left'), 'empty string 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',' ','left'), 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',5,'left'), 'integer 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',5.6,'left'), 'float 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',[],'left'), 'array 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe',{},'left'), 'object 2nd param').to.throw(Error,invalid_2nd_param); +expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); +expect(() => qb.join('universe','foo = bar'), 'no 3rd param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','left'), '3 valid params').to.not.throw(Error); ``` should allow valid join direction to be passed in third parameter. @@ -271,22 +271,22 @@ should allow valid join direction to be passed in third parameter. // NOTE: A lot of this functionality was already tested when testing second param const invalid_direction = /Invalid join direction provided as third parameter/; -expect(function() { qb.join('universe','foo = bar','fake'); }, 'invalid 3rd param').to.throw(Error,invalid_direction); -expect(function() { qb.join('universe',null,null); }, 'invalid 2nd and 3rd params').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',''); }, 'empty third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',' '); }, 'just spaces').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',null); }, 'null third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',false); }, 'false third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',5); }, 'integer third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',5.5); }, 'float third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',{}); }, 'object third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',[]); }, 'array third param').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar','left '); }, 'trailing space').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',' left '); }, 'leading and trailing space').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar',' left'); }, 'leading space').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar','left'); }, 'lowercase direction').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar','LEFT'); }, 'uppercase direction').to.not.throw(Error); -expect(function() { qb.join('universe','foo = bar','LEFT OUTER'); }, 'two word direction').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); +expect(() => qb.join('universe',null,null), 'invalid 2nd and 3rd params').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',''), 'empty third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',' '), 'just spaces').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',null), 'null third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',false), 'false third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',5), 'integer third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',5.5), 'float third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',{}), 'object third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',[]), 'array third param').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','left '), 'trailing space').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',' left '), 'leading and trailing space').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar',' left'), 'leading space').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','left'), 'lowercase direction').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','LEFT'), 'uppercase direction').to.not.throw(Error); +expect(() => qb.join('universe','foo = bar','LEFT OUTER'), 'two word direction').to.not.throw(Error); ``` should except a valid second parameter as a join condition and escape it properly. @@ -369,15 +369,15 @@ should require an array or string to be passed as first parameter. const invalid_match = /requires a string or array/; const empty_str_match = /string is empty/; const empty_arr_match = /array is empty/; -expect(function() { qb.select(); }, 'nothing provided').to.throw(Error, invalid_match); -expect(function() { qb.select(true); }, 'true provided').to.throw(Error, invalid_match); -expect(function() { qb.select(null); }, 'null provided').to.throw(Error, invalid_match); -expect(function() { qb.select(false); }, 'false provided').to.throw(Error, invalid_match); -expect(function() { qb.select({}); }, 'object provided').to.throw(Error, invalid_match); -expect(function() { qb.select([]); }, 'empty array provided').to.throw(Error, empty_arr_match); -expect(function() { qb.select(''); }, 'empty string provided').to.throw(Error, empty_str_match); -expect(function() { qb.select(' '); }, 'string of spaces provided').to.throw(Error, empty_str_match); -expect(function() { qb.select('blah'); }, 'valid string provided').to.not.throw(Error); +expect(() => qb.select(), 'nothing provided').to.throw(Error, invalid_match); +expect(() => qb.select(true), 'true provided').to.throw(Error, invalid_match); +expect(() => qb.select(null), 'null provided').to.throw(Error, invalid_match); +expect(() => qb.select(false), 'false provided').to.throw(Error, invalid_match); +expect(() => qb.select({}), 'object provided').to.throw(Error, invalid_match); +expect(() => qb.select([]), 'empty array provided').to.throw(Error, empty_arr_match); +expect(() => qb.select(''), 'empty string provided').to.throw(Error, empty_str_match); +expect(() => qb.select(' '), 'string of spaces provided').to.throw(Error, empty_str_match); +expect(() => qb.select('blah'), 'valid string provided').to.not.throw(Error); ``` should add field to array and escape it properly. @@ -551,13 +551,13 @@ should not allow subqueries or functions with commas in them without the second ```js qb.resetQuery(); -expect(function() { - qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets'); -}).to.throw(Error); +expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') +).to.throw(Error); -expect(function() { - qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false); -}).to.not.throw(Error); +expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false) +).to.not.throw(Error); ``` should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped). From 1727c049d8e96063eced4171cbdece76cc60fa89 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 15:20:10 -0400 Subject: [PATCH 117/200] Made checking if var is array easier --- drivers/mysql/query_builder.js | 12 ++++++------ drivers/mysql/query_exec.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index ae9c6f8..d591b9d 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -635,7 +635,7 @@ const QueryBuilder = function() { } // Values must be an array... - if (Object.prototype.toString.call(values) !== Object.prototype.toString.call([])) { + if (!Array.isArray(values)) { throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); } else { @@ -733,7 +733,7 @@ const QueryBuilder = function() { }, from: function(from) { - if(Object.prototype.toString.call(from) !== Object.prototype.toString.call([])) { + if(!Array.isArray(from)) { from = [from]; } for (const i in from) { @@ -956,7 +956,7 @@ const QueryBuilder = function() { by = by.split(','); } - if (Object.prototype.toString.call(by) !== Object.prototype.toString.call([])) { + if (!Array.isArray(by)) { throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); } @@ -1069,7 +1069,7 @@ const QueryBuilder = function() { } // normalize orderby to be an array of items - if (Object.prototype.toString.call(orderby) !== Object.prototype.toString.call([])) { + if (!Array.isArray(orderby)) { if (typeof orderby === 'string') { orderby = orderby.trim(); if (orderby.length == 0) { @@ -1279,7 +1279,7 @@ const QueryBuilder = function() { this.from(table); } - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + if (!Array.isArray(set)) { throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); } @@ -1471,7 +1471,7 @@ const QueryBuilder = function() { } // Check to make sure we have a dataset - if (Object.prototype.toString.call(set) !== Object.prototype.toString.call([])) { + if (!Array.isArray(set)) { throw new Error("update_batch(): Array of object expected and non-array received."); } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 59f26ca..a15a3ec 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -74,7 +74,7 @@ const QueryExec = function (qb, conn) { }, get_where: function(table,where,callback) { - if (typeof table !== 'string' && Object.prototype.toString.call(table) !== Object.prototype.toString.call([])) { + if (typeof table !== 'string' && !Array.isArray(table)) { throw new Error("First parameter of get_where() must be a string or an array of strings."); } if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { From 62940d708f32c19490a9526ac2526a72fdad9890 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 15:31:11 -0400 Subject: [PATCH 118/200] Got the last of the functions that didn't throw errors (depending on how well my regex worked) converted to arrows --- drivers/mysql/adapters.js | 2 +- drivers/mysql/query_exec.js | 6 +++--- drivers/template/connect.js | 2 +- examples/connection_pool_example.js | 2 +- examples/connection_single_example.js | 2 +- test/mysql/tests-adapter.js | 8 ++++---- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 9fbb45f..026a3a2 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -186,7 +186,7 @@ const Adapters = function(nqb) { // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) if (that.debugging === true) { nqb.pool.getConnection((err, connection) => { - connection.query('SELECT 1 + 1 AS solution', function(err) { + connection.query('SELECT 1 + 1 AS solution', (err) => { connection.release(); if (err) { console.error(err); diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index a15a3ec..452661a 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -8,7 +8,7 @@ const QueryExec = function (qb, conn) { const exec = (sql, callback) => { if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - conn.query(sql, function(err, results) { + conn.query(sql, (err, results) => { // Standardize some important properties if (!err && results.length > 0) { @@ -48,7 +48,7 @@ const QueryExec = function (qb, conn) { const sql = qb.count(table); qb.reset_query(sql); - exec(sql, function(err, row) { + exec(sql, (err, row) => { if (!err) { //console.dir(row[0].numrows); callback(err, row[0].numrows); @@ -148,7 +148,7 @@ const QueryExec = function (qb, conn) { const sql = sqls.shift(); qb.reset_query(sql); - exec(sql, function(err, res) { + exec(sql, (err, res) => { if (!err) { if (null === results) { results = res; diff --git a/drivers/template/connect.js b/drivers/template/connect.js index 7523a38..2052a9c 100755 --- a/drivers/template/connect.js +++ b/drivers/template/connect.js @@ -53,7 +53,7 @@ PoolCluster = settings => { // @param Object settings Connection settings (including the type) // @return Object Connection handle // **************************************************************************** -connect = function (settings,type) { +connect = (settings,type) => { type = type || 'single'; let connection = null; diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js index 3285b21..b0896d1 100755 --- a/examples/connection_pool_example.js +++ b/examples/connection_pool_example.js @@ -10,7 +10,7 @@ const pool = new QueryBuilder(settings, 'mysql', 'pool'); pool.get_connection(qb => { qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', function(err,response) { + .get('planets', (err,response) => { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js index e45cbd9..792305c 100755 --- a/examples/connection_single_example.js +++ b/examples/connection_single_example.js @@ -9,7 +9,7 @@ const qb = new QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', function(err,response) { + .get('planets', (err,response) => { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 0f7e27b..0e8dde1 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -2,7 +2,7 @@ const should = require('chai').should(); const expect = require('chai').expect; const nqb = require('../../index.js'); -const check = function(done, f) { +const check = (done, f) => { try { f(); done(); @@ -233,7 +233,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { it('should allow us to execute a query', done => { const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { - qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", function(err, res) { + qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { check(done, () => { expect(err).to.not.be.instanceof(Error); expect(res).to.not.be.empty; @@ -278,9 +278,9 @@ describe('QueryBuilder() - MySQL Adapter', () => { const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(qb => { - qb.query('select * from `cities` where `city` = "Gainesville"', function(err, res) { + qb.query('select * from `cities` where `city` = "Gainesville"', (err, res) => { if (res.length > 0) { - qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', function(err, res) { + qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', (err, res) => { check(done, () => connection_released(qb)); }); } else { From 1a507390e49954f894c18413f64189e896a577a6 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 15:40:39 -0400 Subject: [PATCH 119/200] Removed self --- drivers/mysql/query_builder.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d591b9d..4f1bdb8 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1253,7 +1253,7 @@ const QueryBuilder = function() { }, insert_batch: function(table,set=null,ignore,suffix) { - const self = this; + const orig_table = table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); @@ -1319,18 +1319,18 @@ const QueryBuilder = function() { } for (let i = 0; i < set.length; i++) { - ((i,qb) => { + (i => { const row = []; for (const key in set[i]) { if (set[i].hasOwnProperty(key)) { - row.push(qb_escape(qb,set[i][key])); + row.push(qb_escape(this,set[i][key])); } } if (row.length != columns.length) { throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); } map.push('(' + row.join(', ') + ')'); - })(i,self); + })(i); } const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); From 501db0d8c89eec7d4035a448f725d7c540771431 Mon Sep 17 00:00:00 2001 From: DovidM Date: Tue, 11 Jul 2017 16:36:19 -0400 Subject: [PATCH 120/200] Removed return from arrow functions with 1 statement --- README.md | 4 ++-- test/mysql/tests-insert.js | 2 +- test/mysql/tests-insert_batch.js | 2 +- test/mysql/tests-update.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fd37ce1..40e65ab 100755 --- a/README.md +++ b/README.md @@ -1277,8 +1277,8 @@ const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool' app.post('/update_account', (req, res) => { const user_id = req.session.user_id; - const sanitize_name = name => { return name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); }; - const sanitize_age = age => { return age.replace(/[^0-9]+$/,'').trim(); }; + const sanitize_name = name => name.replace(/[^A-Za-z0-9\s'-]+$/,'').trim(); + const sanitize_age = age => age.replace(/[^0-9]+$/,'').trim(); const data = { first_name: sanitize_name(req.body.first_name), diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index 1db5003..fce7afa 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -101,7 +101,7 @@ describe('insert()', () => { }); it('should fail if any invalid values are passed in the data object.', () => { qb.reset_query(); - const func = () => { console.log("foo"); }; + const func = () => console.log("foo"); const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index ee25478..841c234 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -82,7 +82,7 @@ describe('insert_batch()', () => { }); it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { qb.reset_query(); - const func = () => { console.log("foo"); }; + const func = () => console.log("foo"); const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index aa0d98f..053547c 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -103,7 +103,7 @@ describe('update()', () => { }); it('should fail if any invalid values are passed in the data object.', () => { qb.reset_query(); - const func = () => { console.log("foo"); }; + const func = () => console.log("foo"); const regex = /foobar/; const arr = [1,2,3]; const obj = {foo: 'bar'}; From 4f424f153b3cab99a0a22c9ee5f059ff8dcab746 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 26 Jul 2017 10:00:33 -0400 Subject: [PATCH 121/200] 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74877cc..05c16c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "0.15.0", + "version": "1.0.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From ee64fe0b6140dee15e674a19ab22a9d75f9b2a06 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 10 Aug 2017 11:27:09 -0400 Subject: [PATCH 122/200] Fixed a bug where non-strings or non-array-of-strings could be passed to the method and cause issues. --- drivers/mysql/query_builder.js | 110 ++++++++++++++++----------------- test/mysql/tests-get.js | 15 +++++ 2 files changed, 70 insertions(+), 55 deletions(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 4f1bdb8..d977300 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -5,7 +5,7 @@ const QueryBuilder = function() { const keys = Object.keys(item); const length = keys.length; const values = Array(length); - for (const i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { values[i] = item[keys[i]]; } return values; @@ -93,7 +93,7 @@ const QueryBuilder = function() { console.dir(a); } if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (const key in a) { + for (let key in a) { if (a.hasOwnProperty(key)) { delete a[key]; } @@ -113,7 +113,7 @@ const QueryBuilder = function() { // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // const track_aliases = (qb,table) => { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (const i in table) { + for (let i in table) { const t = table[i]; track_aliases(qb,t); } @@ -135,7 +135,7 @@ const QueryBuilder = function() { const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist - if(qb.aliased_tables.indexOf(alias) == -1) { + if (qb.aliased_tables.indexOf(alias) == -1) { qb.aliased_tables.push(alias); } } @@ -155,7 +155,7 @@ const QueryBuilder = function() { } if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (const i in item) { + for (let i in item) { item[i] = escape_identifiers(item[i]); } return item; @@ -242,7 +242,7 @@ const QueryBuilder = function() { // we have nothing more to do other than escape the item if (qb.aliased_tables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { - for (const key in parts) { + for (let key in parts) { const val = parts[key]; if (val !== '*') { parts[key] = escape_identifiers(val); @@ -268,9 +268,9 @@ const QueryBuilder = function() { }; const has_operator = function (str) { - if(typeof str === 'string' && str.length > 0) { + if (typeof str === 'string' && str.length > 0) { const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); - if(!match) { + if (!match) { return false; } } @@ -296,7 +296,7 @@ const QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// const build_where_clause = qb => { let sql = ''; - if(qb.where_array.length > 0) { + if (qb.where_array.length > 0) { sql += " WHERE "; } sql += qb.where_array.join(" "); @@ -305,7 +305,7 @@ const QueryBuilder = function() { const build_from_clause = qb => { let sql = ''; - if(qb.from_array.length > 0) { + if (qb.from_array.length > 0) { sql += " FROM "; } else { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); @@ -317,7 +317,7 @@ const QueryBuilder = function() { const build_join_string = qb => { let sql = ''; sql += qb.join_array.join(' '); - if(sql.length > 0) sql = ' ' + sql; + if (sql.length > 0) sql = ' ' + sql; return sql; }; @@ -400,7 +400,7 @@ const QueryBuilder = function() { const compile_update = qb => { const valstr = []; - for (const i in qb.set_array) { + for (let i in qb.set_array) { const key = Object.keys(qb.set_array[i])[0]; const val = qb.set_array[i][key]; valstr.push(key + ' = ' + val); @@ -430,7 +430,7 @@ const QueryBuilder = function() { const keys = []; const values = []; - for (const i in qb.set_array) { + for (let i in qb.set_array) { const key = Object.keys(qb.set_array[i])[0]; const val = qb.set_array[i][key]; @@ -644,7 +644,7 @@ const QueryBuilder = function() { } } - for (const i in values) { + for (let i in values) { this.where_in_array.push(qb_escape(this,values[i])); } @@ -686,7 +686,7 @@ const QueryBuilder = function() { throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); } - if(Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { if (match === null) { throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } @@ -732,18 +732,18 @@ const QueryBuilder = function() { return this; }, - from: function(from) { - if(!Array.isArray(from)) { - from = [from]; + from: function(from_param) { + if (!Array.isArray(from_param)) { + from_param = [from_param]; } - for (const i in from) { - let val = from[i]; + for (let i in from_param) { + let val = from_param[i]; - if (val.trim() === '') continue; + if (typeof val !== 'string' || val.trim() === '') continue; if (val.indexOf(',') !== -1) { const objects = val.split(','); - for (const j in objects) { + for (let j in objects) { const v = objects[j].trim(); track_aliases(this,v); @@ -883,10 +883,10 @@ const QueryBuilder = function() { } } - for (const i in select) { + for (let i in select) { const val = select[i].trim(); - if(val !== '') { + if (val !== '') { this.select_array.push(protect_identifiers(this,val,escape)); } } @@ -964,7 +964,7 @@ const QueryBuilder = function() { throw new Error("You haven't provided any fields to group by!"); } - for (const key in by) { + for (let key in by) { if (typeof by[key] !== 'string') { throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); } @@ -1015,7 +1015,7 @@ const QueryBuilder = function() { } else if (key_is_array === true) { //console.log("Key is NOT a string"); - for (const i in key) { + for (let i in key) { if (typeof key[i] !== 'string') { throw new Error("having(): You've provided an unparseable format to the having() method.."); } @@ -1089,7 +1089,7 @@ const QueryBuilder = function() { throw new Error("You haven't provided any fields to order by!"); } - for (const i in orderby) { + for (let i in orderby) { orderby[i] = orderby[i].replace(/\s+/g, ' '); if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { @@ -1161,7 +1161,7 @@ const QueryBuilder = function() { // Add each key:value pair to the set_array - for (const i in key) { + for (let i in key) { let v = key[i]; if (typeof v === 'undefined') continue; @@ -1187,7 +1187,7 @@ const QueryBuilder = function() { // Determine if this key has already been set let found_index = null; - for (const j in this.set_array) { + for (let j in this.set_array) { if (this.set_array[j].hasOwnProperty(escaped_key)) { found_index = j; break; @@ -1283,13 +1283,13 @@ const QueryBuilder = function() { throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); } - for (const key in set) { + for (let key in set) { const row = set[key]; const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); if (!is_object || (is_object && Object.keys(row).length === 0)) { throw new Error('insert_batch(): An invalid item was found in the data array!'); } else { - for (const i in row) { + for (let i in row) { const v = row[i]; if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { @@ -1310,7 +1310,7 @@ const QueryBuilder = function() { const columns = []; // Obtain all the column names - for (const key in set[0]) { + for (let key in set[0]) { if (set[0].hasOwnProperty(key)) { if (columns.indexOf(key) == -1) { columns.push(protect_identifiers(this,key)); @@ -1321,7 +1321,7 @@ const QueryBuilder = function() { for (let i = 0; i < set.length; i++) { (i => { const row = []; - for (const key in set[i]) { + for (let key in set[i]) { if (set[i].hasOwnProperty(key)) { row.push(qb_escape(this,set[i][key])); } @@ -1338,7 +1338,7 @@ const QueryBuilder = function() { }, get: function(table) { - if (typeof table !== 'undefined') { + if (typeof table === 'string' || Array.isArray(table)) { this.from(table); } else { @@ -1351,25 +1351,25 @@ const QueryBuilder = function() { get_where: function(table=null, where=null) { - // Check if table is either a string or array + // Check if table is either a string or array if (typeof table !== 'string' && !Array.isArray(table)) throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - // If table is a string, make sure it's not empty - if (typeof table === 'string' && table.trim().length <= 0) - throw new Error("Invalid table string specified!"); + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); - // If table is array, make sure there are only strings in there and that they are non-empty strings - if (Array.isArray(table)) { - for (const v in table) { - if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { - throw new Error("Invalid table string specified in array of tables!"); - break; - } - } - } + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (let v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; + } + } + } - this.from(table); + this.from(table); if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); @@ -1482,13 +1482,13 @@ const QueryBuilder = function() { // Make sure each item in the dataset has the specified index and then add data to set_array //console.dir(set); - for (const i in set) { + for (let i in set) { const clean = {}; const row = set[i]; if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { const keys = Object.keys(row); if (keys.indexOf(index) !== -1) { - for (const j in row) { + for (let j in row) { clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); } this.set_array.push(clean); @@ -1553,11 +1553,11 @@ const QueryBuilder = function() { // Escape the index index = protect_identifiers(this, index); - for (const j in chunk) { + for (let j in chunk) { ids.push(chunk[j][index]); const keys = Object.keys(chunk[j]); - for (const k in keys) { + for (let k in keys) { if (keys[k] != index) { if (!when_then.hasOwnProperty(keys[k])) { when_then[keys[k]] = []; @@ -1571,10 +1571,10 @@ const QueryBuilder = function() { let sql = 'UPDATE (' + table + ') SET '; let cases = ''; - for (const l in when_then) { + for (let l in when_then) { cases += l + ' = CASE '; - for (const m in when_then[l]) { + for (let m in when_then[l]) { cases += when_then[l][m]; } @@ -1615,7 +1615,7 @@ const QueryBuilder = function() { this.from(table); } else { - if (from_array.length == 0) { + if (this.from_array.length == 0) { throw new Error('You have not specified any tables to build a select statement with!'); return this; } diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index 478bbb6..7493ec0 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -19,6 +19,21 @@ describe('get()', () => { qb.get(['galaxies','star_systems','planets']); qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); }); + it('should not accept anything but a non-empty string or an array of non-empty strings', () => { + qb.reset_query(); + expect(() => qb.get(), 'nothing provided').to.throw(Error); + expect(() => qb.get({foo: 'bar'}), 'object provided').to.throw(Error); + expect(() => qb.get(true), 'boolean provided').to.throw(Error); + expect(() => qb.get(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.get(null), 'NULL provided').to.throw(Error); + expect(() => qb.get(1), 'Integer provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get([]), 'Empty array provided').to.throw(Error); + expect(() => qb.get(''), 'Empty string provided').to.throw(Error); + expect(() => qb.get('galaxies'), 'Valid string provided').to.not.throw(Error); + expect(() => qb.get(['galaxies','planets']), 'Array of non-empty strings provided').to.not.throw(Error); + }); it('should return a string', () => { qb.reset_query(); const sql = qb.get('galaxies'); From 57b7d80c26939dc07a7d259a39cd0351475011e9 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 10 Aug 2017 11:27:31 -0400 Subject: [PATCH 123/200] 1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05c16c4..34fa40d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.0.0", + "version": "1.0.1", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 8162e0bff1d26636d279f3fde3810295aeeed708 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 22 Aug 2017 14:19:18 -0400 Subject: [PATCH 124/200] Fixed a bug dealing with the default 'driver' param value in the QueryBuilder contructor. --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 6b9b731..381acfc 100755 --- a/index.js +++ b/index.js @@ -57,11 +57,11 @@ const QueryBuilder = (settings,driver,type) => { // **************************************************************************** const get_driver_info = qb => { // A driver must be specified - if (typeof driver !== 'string') { + if (typeof this.driver !== 'string') { throw new Error("No database driver specified!"); } - qb.driver = driver.toLowerCase(); + qb.driver = qb.driver.toLowerCase(); // Verify that the driver is one we fundamentally support if (Object.keys(qb.drivers).indexOf(qb.driver) === -1) { @@ -101,7 +101,7 @@ const QueryBuilder = (settings,driver,type) => { // @return Object Modified QueryBuilder object // **************************************************************************** const get_connection_type = qb => { - if (Object.keys(qb.drivers[qb.driver].connection_types).indexOf(qb.connection_type) === -1) { + if (Object.keys(qb.drivers[qb.driver].connection_types).includes(qb.connection_type)) { throw new Error("You have specified a invalid database connection method: " + qb.connection_type); } if (qb.drivers[qb.driver].connection_types[qb.connection_type] !== true) { From 3bec22f8f44d194de49fc61dc12f17503188e810 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 22 Aug 2017 14:19:30 -0400 Subject: [PATCH 125/200] 1.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34fa40d..2361a39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.0.1", + "version": "1.0.2", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From f9d7e94f417d7b0843615138776ba630cdf2fa5a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 22 Aug 2017 14:58:45 -0400 Subject: [PATCH 126/200] Fixed a bug in how it's checking for valid connection types. Also updated tests to allow for an empty driver string in QueryBuilder constructor (defaults to 'mysql'). --- index.js | 2 +- test/mysql/tests-adapter.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 381acfc..7a7793c 100755 --- a/index.js +++ b/index.js @@ -101,7 +101,7 @@ const QueryBuilder = (settings,driver,type) => { // @return Object Modified QueryBuilder object // **************************************************************************** const get_connection_type = qb => { - if (Object.keys(qb.drivers[qb.driver].connection_types).includes(qb.connection_type)) { + if (!Object.keys(qb.drivers[qb.driver].connection_types).includes(qb.connection_type)) { throw new Error("You have specified a invalid database connection method: " + qb.connection_type); } if (qb.drivers[qb.driver].connection_types[qb.connection_type] !== true) { diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 0e8dde1..609ee62 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -177,8 +177,8 @@ describe('QueryBuilder() - MySQL Adapter', () => { }); it('should fail to establish connection if an invalid driver is specified', () => { let qb; - expect(() => nqb.QueryBuilder(settings), 'no driver specified').to.throw(Error); - expect(() => nqb.QueryBuilder(settings,'foobar'), 'invalid driver specified').to.throw(Error); + expect(() => nqb.QueryBuilder(settings), 'no driver specified').to.not.throw(Error); + expect(() => nqb.QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); }); it('should fail to establish connection if an invalid driver version is specified', () => { let qb; From 5d194d28a24c8bb5a30f1d043c4799d51932c95d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 22 Aug 2017 14:58:57 -0400 Subject: [PATCH 127/200] 1.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2361a39..ad3fbf5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.0.2", + "version": "1.0.3", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 8119ab75a8237b0c7d82f1b9d6ebcb99b64be966 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 22 Aug 2017 15:00:29 -0400 Subject: [PATCH 128/200] Updated travis to always use dist: precise. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 35f2183..3a88a3e 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ before_script: ./test/create_mock_dbs.sh language: node_js +dist: precise node_js: - "6.10.2" services: mysql From 10e7af79a45c49e78a574576ea8de77ec8322ca8 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 20 Feb 2018 11:40:06 -0500 Subject: [PATCH 129/200] Fixes #20 Fixes #20 --- drivers/mysql/query_builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index d977300..c0ed43a 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -283,7 +283,7 @@ const QueryBuilder = function() { if (typeof str === 'boolean') { str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str))) { + } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { str *= 1; } else { str = do_escape(str); From 52e817144c25d754d84ba8d41b5620a1d4dbcb39 Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Thu, 17 May 2018 15:33:46 -0400 Subject: [PATCH 130/200] Update mysql and test on more travis --- .travis.yml | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) mode change 100644 => 100755 package.json diff --git a/.travis.yml b/.travis.yml index 3a88a3e..ee59b79 100755 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,11 @@ before_script: ./test/create_mock_dbs.sh language: node_js dist: precise node_js: - - "6.10.2" + - "10.1.0" + - "9.11.1" + - "8.11.2" + - "7.10.1" + - "6.14.2" services: mysql test: adapter: mysql2 diff --git a/package.json b/package.json old mode 100644 new mode 100755 index ad3fbf5..7ef6ee8 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ ], "dependencies": { "lodash.assign": "^4.0.7", - "mysql": "2.10.*" + "mysql": "^2.10.1" }, "devDependencies": { "chai": "~1.4.2", From cf65b2e361a33758130ce8ef48e70a138a919cf4 Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Thu, 17 May 2018 16:32:58 -0400 Subject: [PATCH 131/200] update the history log --- HISTORY.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f2295a5..cfdd205 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,34 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. +## v1.1.1 (2018-05-25) + +* Updated package.json to always use the latest version of mysql 2.x + +## v1.1.0 (2018-02-20) + +* Fixed a bug where you could not insert with leading zeros. #20 + +## v1.0.3 (2017-08-22) + +* Fixed a bug in how it's checking for valid connection types. Also updated tests to allow for an empty driver string in QueryBuilder constructor (defaults to 'mysql'). + +## v1.0.2 (2017-08-22) + +* Fixed a bug dealing with the default 'driver' param value in the QueryBuilder contructor + +## v1.0.1 (2017-08-10) + +* Fixed a bug where non-strings or non-array-of-strings could be passed to the method and cause issues + +## v1.0.0 (2017-07-26) + +* Updated codebase to ES6 + +## v0.15.0 (2017-04-27) + +* Fixed and documented the escape property of the `join` method. + ## v0.9.0 (2015-02-05) * Added this history file @@ -36,7 +64,3 @@ you spot any mistakes. * Added public `escape()` method * Added funtional `update_batch()` method. * Added `truncate()` and `empty_table()` methods. - -## v0.15.0 (2017-04-27) - -* Fixed and documented the escape property of the `join` method. From 89135637295c80f1ea73e7b6a4235d1167deda56 Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Thu, 17 May 2018 16:34:09 -0400 Subject: [PATCH 132/200] 1.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ef6ee8..13b35cf 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.0.3", + "version": "1.1.1", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From d8648aa08bc9482f379df0c1cee52114d2581eac Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Fri, 18 May 2018 16:59:50 -0400 Subject: [PATCH 133/200] Fix a mysql escape string bug --- HISTORY.md | 7 ++++++- drivers/mysql/query_builder.js | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index cfdd205..8a342bb 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,12 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v1.1.1 (2018-05-25) +## v1.1.2 (2018-05-18) + +* retroactive fix of this change log +* updated the mysql escape string to use the proper mysql method. + +## v1.1.1 (2018-05-17) * Updated package.json to always use the latest version of mysql 2.x diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index c0ed43a..f74dc0b 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -278,15 +278,14 @@ const QueryBuilder = function() { }; const qb_escape = (qb,str) => { - const SqlString = require('../../node_modules/mysql/lib/protocol/SqlString.js'); - const do_escape = SqlString.escape; + const mysql = require('mysql'); if (typeof str === 'boolean') { str = (str === false ? 0 : 1); } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { str *= 1; } else { - str = do_escape(str); + str = mysql.escape(str); } return str; From e23d6cebc67faad55a2cfbd69cc678f78c4b224c Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Fri, 18 May 2018 17:01:08 -0400 Subject: [PATCH 134/200] 1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13b35cf..80d2161 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.1.1", + "version": "1.2.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 554acdc9d359aaf364c80e82380904fba8750aeb Mon Sep 17 00:00:00 2001 From: Chase Whitener Date: Fri, 18 May 2018 17:01:51 -0400 Subject: [PATCH 135/200] bump minor version --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 8a342bb..2e4ceef 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,7 +4,7 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. -## v1.1.2 (2018-05-18) +## v1.2.0 (2018-05-18) * retroactive fix of this change log * updated the mysql escape string to use the proper mysql method. From ed66131733e6e422770209057eea1d678aff0ed3 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Jun 2018 16:52:01 -0400 Subject: [PATCH 136/200] Updated documentation. Added a new test to check for functions in select statements. Added an additional check in the code to notify users that they must supply to the second parameter of if they expect SQL functions to work. Fixes #25. --- README.md | 254 +++++++++++++++++---------------- drivers/mysql/query_builder.js | 6 +- test/mysql/tests-select.js | 28 +++- 3 files changed, 160 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 40e65ab..af5810e 100755 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The primary benefits of this module (currently) are: - Is fully unit tested - **Very thoroughly documented** - Allows for greater flexibility and more control over a full ORM -- Ligher-weight than an ORM +- Lighter-weight than an ORM - Allows you to drop down to the native methods of your driver if you choose to - Allows for different drivers for different versions (SQLite 2 vs SQLite 3) - The order in which you call the methods is irrelevant except for the execution methods (get, insert, update, delete) which must be called last. @@ -74,14 +74,14 @@ const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single' qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', (err,response) => { + .get('planets', (err, response) => { if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 console.log("Query Ran: " + qb.last_query()); // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] - console.dir(response); + console.log("Results:", response); } ); ``` @@ -224,9 +224,9 @@ Parameter | Type | Default | Description fields | String/Array | Required | The fields in which to grab from the database escape | Boolean | true | TRUE: auto-escape fields; FALSE: don't escape -The fields provided to this method will be automatically escaped by the database driver. The `fields` paramter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): +The fields provided to this method will be automatically escaped by the database driver. The `fields` parameter can be passed in 1 of 2 ways (field names will be trimmed in either scenario): -**_NOTE:_** If the select method is never called before an execution method is ran, 'SELECT *' will be assumed. +**_NOTE:_** If the select method is never called before an execution method is ran, `SELECT *` will be assumed. - String with fields seperated by a comma: - `.select('foo, bar, baz')` @@ -237,10 +237,10 @@ The fields provided to this method will be automatically escaped by the database ```javascript // SELECT * FROM galaxies -qb.select('*').get('foo',callback); +qb.select('*').get('foo', callback); // Easier and same result: -qb.get('foo',callback); +qb.get('foo', callback); ``` An array of field names: @@ -254,7 +254,7 @@ You can chain the method together using different patterns if you want: ```javascript // SELECT `foo`, `bar`, `baz`, `this`, `that`, `the_other` -qb.select(['foo', 'bar', 'baz']).select('this,that,the_other'); +qb.select(['foo', 'bar', 'baz']).select('this, that, the_other'); ``` You can alias your field names and they will be escaped properly as well: @@ -268,7 +268,15 @@ You can optionally choose not to have the driver auto-escape the fieldnames (dan ```javascript // SELECT CONCAT(first_name,' ',last_name) AS `full_name` -qb.select('CONCAT(first_name,' ',last_name) AS `full_name`',false); +qb.select('CONCAT(first_name,' ',last_name) AS `full_name`', false); +``` + +In order to successfully use subqueries in your select statements, you *must* supply `false` to the second parameter. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! + +```javascript +// (SELECT `name` FROM `planets` WHERE `id`=8675309) AS `planet_name` +qb.select('(SELECT `name` FROM `planets` WHERE `id`=8675309) AS `planet_name`', false); + ``` **_NOTE:_** If you use this technique to add driver-specific functions, it may (and probably will) cause unexpected outcomes with other database drivers! @@ -285,7 +293,7 @@ This SQL command is used to prevent duplicate rows from being returned in the re ```javascript // SELECT DISTINCT `id`, `name`, `description` FROM `users` -qb.distinct().select('id,name,description').get('users',callback); +qb.distinct().select('id, name, description').get('users', callback); ``` -------------------------------------------------------------------------------- @@ -303,14 +311,14 @@ alias | String | NULL | Optional alias to rename field ```javascript // SELECT MIN(`age`) FROM `users` -qb.select_min('age').get('users',callback); +qb.select_min('age').get('users', callback); ``` You can optionally include a second parameter to rename the resulting field ```javascript // SELECT MIN(`age`) AS `min_age` FROM `users` -qb.select_min('age', 'min_age').get('users',callback); +qb.select_min('age', 'min_age').get('users', callback); ``` -------------------------------------------------------------------------------- @@ -328,14 +336,14 @@ alias | String | NULL | Optional alias to rename field ```javascript // SELECT MAX(`age`) FROM `users` -qb.select_max('age').get('users',callback); +qb.select_max('age').get('users', callback); ``` You can optionally include a second parameter to rename the resulting field ```javascript // SELECT MAX(`age`) AS `max_age` FROM `users` -qb.select_max('age', 'max_age').get('users',callback); +qb.select_max('age', 'max_age').get('users', callback); ``` -------------------------------------------------------------------------------- @@ -353,14 +361,14 @@ alias | String | NULL | Optional alias to rename field ```javascript // SELECT AVG(`age`) FROM `users` -qb.select_avg('age').get('users',callback); +qb.select_avg('age').get('users', callback); ``` You can optionally include a second parameter to rename the resulting field ```javascript // SELECT AVG(`age`) AS `avg_age` FROM `users` -qb.select_avg('age', 'avg_age').get('users',callback); +qb.select_avg('age', 'avg_age').get('users', callback); ``` -------------------------------------------------------------------------------- @@ -378,14 +386,14 @@ alias | String | NULL | Optional alias to rename field ```javascript // SELECT SUM(`age`) FROM `users` -qb.select_sum('age').get('users',callback); +qb.select_sum('age').get('users', callback); ``` You can optionally include a second parameter to rename the resulting field ```javascript // SELECT SUM(`age`) AS `sum_age` FROM `users` -qb.select_sum('age', 'sum_age').get('users',callback); +qb.select_sum('age', 'sum_age').get('users', callback); ``` -------------------------------------------------------------------------------- @@ -398,7 +406,7 @@ Parameter | Type | Default | Description :-------- | :----------- | :------- | :------------------------------------------ tables | String/Array | Required | Table(s), view(s), etc... to grab data from -You can provide tables, views, or any other valid source of data in a comma-seperated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). +You can provide tables, views, or any other valid source of data in a comma-separated list (string) or an array. When more than one data-source is provided when connected to a traditional RDMS, the tables will joined using a basic join. You can also `.from()` multiple times to get the same effect (the order in which they are called does not matter). Aliases can be provided and they will be escaped properly. @@ -410,7 +418,7 @@ Aliases can be provided and they will be escaped properly. ```javascript // SELECT `id`, `name`, `description` FROM `users` -qb.select('id,name,description').from('users').get(callback); +qb.select('id, name, description').from('users').get(callback); ``` **_Comma-Seperated_** @@ -418,7 +426,7 @@ qb.select('id,name,description').from('users').get(callback); ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) -qb.select('u.id,u.name,u,description,g.name as group_name') +qb.select('u.id, u.name, u, description, g.name as group_name') .from('users u, groups g') .get(callback); ``` @@ -428,7 +436,7 @@ qb.select('u.id,u.name,u,description,g.name as group_name') ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) -qb.select('u.id,u.name,u,description,g.name as group_name') +qb.select('u.id, u.name, u, description, g.name as group_name') .from(['users u', 'groups g']) .get(callback); ``` @@ -438,7 +446,7 @@ qb.select('u.id,u.name,u,description,g.name as group_name') ```javascript // SELECT `u`.`id`, `u`.`name`, `u`.`description`, `g`.`name` AS `group_name` // FROM (`users` `u`, `groups` `g`) -qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') +qb.from('groups g').select('u.id, u.name, u, description, g.name as group_name') .from('users u') .get(callback); ``` @@ -446,7 +454,7 @@ qb.from('groups g').select('u.id,u.name,u,description,g.name as group_name') -------------------------------------------------------------------------------- ### JOIN -#### .join(table,relation[,direction]) +#### .join(table, relation[,direction]) This SQL command is used query multiple tables related and connected by keys and get a single resultset. Parameter | Type | Default | Description @@ -464,7 +472,7 @@ escape | Boolean | true | TRUE: Escape table name and conditions; FALSE: - left outer - right outer -The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (althought it is recommened to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. +The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (although it is recommended to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. **Examples** @@ -474,7 +482,7 @@ If no direction is specified, "left" will be used: // SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` // FROM `users` `u` // LEFT JOIN `types` `t` ON `t`.`id`=`u`.`type_id` -qb.select('u.id,u.name,t.name as type_name').from('users u') +qb.select('u.id, u.name, t.name as type_name').from('users u') .join('types t', 't.id=u.type_id') .get(callback); ``` @@ -485,7 +493,7 @@ You may specify a direction: // SELECT `u`.`id`, `u`.`name`, `t`.`name` AS `type_name` // FROM `users` `u` // RIGHT OUTER JOIN `types` `t` ON `t`.`id`=`u`.`type_id` -qb.select('u.id,u.name,t.name as type_name').from('users u') +qb.select('u.id, u.name, t.name as type_name').from('users u') .join('types t', 't.id=u.type_id', 'right outer') .get(callback); ``` @@ -543,28 +551,28 @@ If you just want to pass a single filter at a time: ```javascript // SELECT `galaxy` FROM `universe` WHERE `planet_name` = 'Earth' -qb.select('galaxy').where('planet_name', 'Earth').get('universe',callback); +qb.select('galaxy').where('planet_name', 'Earth').get('universe', callback); ``` If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 -qb.select('planet').where('order <=',3).get('planets',callback); +qb.select('planet').where('order <=', 3).get('planets', callback); ``` You can conveniently pass an object of key:value pairs (which can also contain custom operators): ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' -qb.select('planet').where({'order <=':3, class:'M'}).get('planets',callback); +qb.select('planet').where({'order <=':3, class:'M'}).get('planets', callback); ``` -You can construct complex WHERE clauses manually and they will be escaped properly as long as there are no paranthesis within it. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! +You can construct complex WHERE clauses manually and they will be escaped properly as long as there are no parenthesis within it. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! ```javascript // SELECT `planet` FROM `planets` WHERE `order` <= 3 AND `class` = 'M' -qb.select('planet').where("order <= 3 AND class = 'M'").get('planets',callback); +qb.select('planet').where("order <= 3 AND class = 'M'").get('planets', callback); ``` You can pass a non-empty array as a value and that portion will be treated as a call to `.where_in()`: @@ -573,8 +581,8 @@ You can pass a non-empty array as a value and that portion will be treated as a // SELECT `star_system` FROM `star_systems` // WHERE `planet_count` >= 4, `star` IN('Sun', 'Betelgeuse') qb.select('star_system') - .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']) - .get('star_systems',callback); + .where({'planet_count >=': 4, star: ['Sun', 'Betelgeuse']}) + .get('star_systems', callback); ``` @@ -587,50 +595,50 @@ This method functions identically to [.where()](#where) except that it joins cla // WHERE `star` = 'Sun' OR `star` = 'Betelgeuse' qb.select('star_system').where('star', 'Sun') .or_where('star', 'Betelgeuse') - .get('star_systems',callback); + .get('star_systems', callback); ``` -#### .where_in(field,values[,escape]) +#### .where_in(field, values[,escape]) This will create a "WHERE IN" statement in traditional SQL which is useful when you're trying to find rows with fields matching many different values... It will be joined with existing "WHERE" statements with 'AND'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') const stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; -qb.select('star_system').where_in('star',stars).get('star_systems',callback); +qb.select('star_system').where_in('star', stars).get('star_systems', callback); ``` -#### .or_where_in(field,values[,escape]) +#### .or_where_in(field, values[,escape]) Same as `.where_in()` except the clauses are joined by 'OR'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `planet_count` = 4 OR `star` IN('Sun', 'Betelgeuse') const stars = ['Sun', 'Betelgeuse']; -qb.select('star_system').where('planet_count',4) - .or_where_in('star',stars) - .get('star_systems',callback); +qb.select('star_system').where('planet_count', 4) + .or_where_in('star', stars) + .get('star_systems', callback); ``` -#### .where_not_in(field,values[,escape]) +#### .where_not_in(field, values[,escape]) Same as `.where_in()` except this generates a "WHERE NOT IN" statement. All clauses are joined with 'AND'. ```javascript // SELECT `star_system` FROM `star_systems` // WHERE `star` NOT IN('Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri') const stars = ['Sun', 'Betelgeuse', 'Sirius', 'Vega', 'Alpha Centauri']; -qb.select('star_system').where_not_in('star',stars).get('star_systems',callback); +qb.select('star_system').where_not_in('star', stars).get('star_systems', callback); ``` -#### .or_where_not_in(field,values[,escape]) +#### .or_where_not_in(field, values[,escape]) Same as `.where_not_in()` except that clauses are joined with 'OR'. ```javascript @@ -640,9 +648,9 @@ Same as `.where_not_in()` except that clauses are joined with 'OR'. const stars = ['Sun', 'Betelgeuse']; const planet_sizes = [2,4,6,8]; qb.select('star_system') - .where_not_in('star',stars) - .or_where_not_in('planet_size',planet_sizes) - .get('star_systems',callback); + .where_not_in('star', stars) + .or_where_not_in('planet_size', planet_sizes) + .get('star_systems', callback); ``` -------------------------------------------------------------------------------- @@ -658,7 +666,7 @@ side | String | 'both' | before: '%value'; after: 'value%', bo **NOTE:** You can, alternatively, use `'right'` and `'left'` in place of `'before'` and '`after`' if you prefer. -#### .like(field,match[,side]) +#### .like(field, match[,side]) All fields are escaped automatically, no exceptions. Multiple calls will be joined together with 'AND'. You can also pass an object of field/match pairs. Wildcard sides are interchangeable between before/left and after/right--choose the one that makes the most sense to you (there are examples of each below). **Examples** @@ -668,7 +676,7 @@ By default, the match string will be wrapped on both sides with the wildcard (%) ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber%' // Potential results: [{first_name: 'Kimberly'},{first_name: 'Amber'}] -qb.select('first_name').like('first_name', 'mber').get('users',callback); +qb.select('first_name').like('first_name', 'mber').get('users', callback); ``` You can specify a side to place the wildcard (%) on if you'd like (before/left, after/right, both): @@ -676,11 +684,11 @@ You can specify a side to place the wildcard (%) on if you'd like (before/left, ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE '%mber' // Potential results: [{first_name: 'Amber'}] -qb.select('first_name').like('first_name', 'mber', 'before').get('users',callback); +qb.select('first_name').like('first_name', 'mber', 'before').get('users', callback); // SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'Kim%' // Potential results: [{first_name: 'Kim'},{first_name: 'Kimberly'}] -qb.select('first_name').like('first_name', 'Kim', 'right').get('users',callback); +qb.select('first_name').like('first_name', 'Kim', 'right').get('users', callback); ``` You can also pass 'none' if you don't want to use the wildcard (%) @@ -688,7 +696,7 @@ You can also pass 'none' if you don't want to use the wildcard (%) ```javascript // SELECT `first_name` FROM `users` WHERE `first_name` LIKE 'kim' // Potential results: [{first_name: 'Kim'}] -qb.select('first_name').like('first_name', 'kim', 'none').get('users',callback); +qb.select('first_name').like('first_name', 'kim', 'none').get('users', callback); ``` If you'd like to have multiple like clauses, you can do that by calling like multiple times: @@ -702,10 +710,10 @@ qb.select('first_name') .like('first_name', 'Kim', 'right') .like('middle_name', 'lyt') .like('last_name', 'arris', 'left') - .get('users',callback); + .get('users', callback); ``` -Or you can do it with an object of field/match pairs. If you want to pass a wildcard side, provide `null` as the second paramter and the side as the third. **Note**: All `match` values in an object will share the same wildcard side. +Or you can do it with an object of field/match pairs. If you want to pass a wildcard side, provide `null` as the second parameter and the side as the third. **Note**: All `match` values in an object will share the same wildcard side. ```javascript // SELECT `first_name` FROM `users` @@ -714,12 +722,12 @@ Or you can do it with an object of field/match pairs. If you want to pass a wild // AND `last_name` LIKE '%is' qb.select('first_name') .like({first_name: 'ly', middle_name: 'the', last_name: 'is'}, null, 'before') - .get('users',callback); + .get('users', callback); ``` -#### .or_like(field,match[,side]) +#### .or_like(field, match[,side]) This is exactly the same as the `.like()` method except that the clauses are joined by 'OR' not 'AND'. **Example** @@ -733,12 +741,12 @@ qb.select('first_name') .or_like('first_name', 'Kim', 'right') .or_like('middle_name', 'lyt') .or_like('last_name', 'arris', 'left') - .get('users',callback); + .get('users', callback); ``` -#### .not_like(field,match[,side]) +#### .not_like(field, match[,side]) This is exactly the same as the `.like()` method except that it creates "NOT LIKE" statements. **Example** @@ -750,12 +758,12 @@ This is exactly the same as the `.like()` method except that it creates "NOT LIK // AND `last_name` NOT LIKE 'C%' qb.select('first_name') .not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') - .get('users',callback); + .get('users', callback); ``` -#### .or_not_like(field,match[,side]) +#### .or_not_like(field, match[,side]) This is exactly the same as the `.not_like()` method except that the clauses are joined by 'OR' not 'AND'. **Example** @@ -767,7 +775,7 @@ This is exactly the same as the `.not_like()` method except that the clauses are // OR `last_name` NOT LIKE 'C%' qb.select('first_name') .or_not_like({first_name: 'A', middle_name: 'B', last_name: 'C'}, null, 'after') - .get('users',callback); + .get('users', callback); ``` -------------------------------------------------------------------------------- @@ -786,20 +794,20 @@ Group by a single field: ```javascript // SELECT * FROM `users` GROUP BY `department_id` -qb.group_by('department_id').get('users',callback); +qb.group_by('department_id').get('users', callback); ``` Group by multiple fields: ```javascript // SELECT * FROM `users` GROUP BY `department_id`, `position_id` -qb.group_by(['department_id', 'position_id']).get('users',callback); +qb.group_by(['department_id', 'position_id']).get('users', callback); ``` -------------------------------------------------------------------------------- ### HAVING -#### .having(field,value) +#### .having(field, value) This SQL command is similar to the 'WHERE' command but is used when aggregate functions are used in the "SELECT" portion of the query. Parameter | Type | Default | Description @@ -818,7 +826,7 @@ If you just want to add a single having clause: // SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` = 5 -qb.group_by('id').having('num_planets',5).count('star_systems',callback); +qb.group_by('id').having('num_planets', 5).count('star_systems', callback); ``` If you need more complex filtering using different operators (`<, >, <=, =>, !=, <>, etc...`), you can simply provide that operator along with the key in the first parameter. The '=' is assumed if a custom operator is not passed: @@ -827,7 +835,7 @@ If you need more complex filtering using different operators (`<, >, <=, =>, !=, // SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > 5 -qb.group_by('id').having('num_planets >',5).count('star_systems',callback); +qb.group_by('id').having('num_planets >', 5).count('star_systems', callback); ``` You can conveniently pass an object of key:value pairs (which can also contain custom operators): @@ -836,7 +844,7 @@ You can conveniently pass an object of key:value pairs (which can also contain c // SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > 5 -qb.group_by('id').having({'num_planets >': 5}).count('star_systems',callback); +qb.group_by('id').having({'num_planets >': 5}).count('star_systems', callback); ``` You can construct complex WHERE clauses manually and they will be escaped properly. _Please, for custom clauses containing subqueries, make sure you escape everything properly!_ **_ALSO NOTE:_** with this method, there may be conflicts between database drivers! @@ -845,7 +853,7 @@ You can construct complex WHERE clauses manually and they will be escaped proper // SELECT COUNT(*) AS `num_planets` FROM `star_systems` // GROUP BY `id` // HAVING `num_planets` > (5+2) -qb.group_by('id').having("`num_planets` > (5+2)",null,false).count('star_systems',callback); +qb.group_by('id').having("`num_planets` > (5+2)", null, false).count('star_systems', callback); ``` @@ -858,9 +866,9 @@ This method functions identically to [.having()](#having) except that it joins c // GROUP BY `id` // HAVING `num_planets` >= 5 OR `num_moons` <= 10 qb.group_by('id') - .having('num_planets >=',5) + .having('num_planets >=', 5) .or_having('num_moons <=', 10) - .count('star_systems',callback); + .count('star_systems', callback); ``` -------------------------------------------------------------------------------- @@ -874,62 +882,62 @@ Parameter | Type | Default | Description fields | String/Array | Required | Field name or an array of field names, possibly with directions as well direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. -This is a very flexible method, offerring a wide constiety of ways you can call it. constiations include: -- Pass the field name and ommit the direction +This is a very flexible method, offering a wide variety of ways you can call it. Variations include: +- Pass the field name and omit the direction - Pass the field name and the direction as the first and second parameters, respectively (most common) -- Pass an array of fields to first paramter, direction to second parameter. -- Pass an array of fields + directions in first parameter and ommit the second one. -- Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter. -- Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. +- Pass an array of fields to first parameter, direction to second parameter. +- Pass an array of fields + directions in first parameter and omit the second one. +- Pass an array of fields (+ directions for some to override second parameter) to first parameter, direction to second parameter. +- Pass a raw comma-separated string of field + directions in first parameter and omit the second one. **Examples** -Pass the field name and ommit the direction +Pass the field name and omit the direction ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` ASC -qb.order_by('galaxy_name').get('galaxies',callback); +qb.order_by('galaxy_name').get('galaxies', callback); ``` Pass the field name and the direction as the first and second parameters, respectively ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC -qb.order_by('galaxy_name', 'desc').get('galaxies',callback); +qb.order_by('galaxy_name', 'desc').get('galaxies', callback); ``` Pass an array of fields to first paramter, direction to second parameter ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` DESC -qb.order_by(['galaxy_name', 'galaxy_size'],'desc').get('galaxies',callback); +qb.order_by(['galaxy_name', 'galaxy_size'],'desc').get('galaxies', callback); ``` Pass an array of fields + directions in first parameter and ommit the second one. ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC -qb.order_by(['galaxy_name desc', 'galaxy_size asc']).get('galaxies',callback); +qb.order_by(['galaxy_name desc', 'galaxy_size asc']).get('galaxies', callback); ``` -Pass an array of fields (+ directions for some to override second parameter) to first paramter, direction to second parameter +Pass an array of fields (+ directions for some to override second parameter) to first parameter, direction to second parameter ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` ASC -qb.order_by(['galaxy_name desc', 'galaxy_size'],'asc').get('galaxies',callback); +qb.order_by(['galaxy_name desc', 'galaxy_size'],'asc').get('galaxies', callback); ``` -Pass a raw comma-seperated string of field + directions in first parameter and ommit the second one. +Pass a raw comma-separated string of field + directions in first parameter and omit the second one. ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` ASC, `galaxy_size` DESC -qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies',callback); +qb.order_by('galaxy_name asc, galaxy_size desc').get('galaxies', callback); ``` -------------------------------------------------------------------------------- ### LIMIT -#### .limit(limit_to,offset) +#### .limit(limit_to, offset) This SQL command is used to limit a result set to a maximum number of results, regardless of the actual number of results that might be returned by a non-limited query. Parameter | Type | Default | Description @@ -941,14 +949,14 @@ offset | Integer | NULL | Optional offset value (where to start before li ```javascript // SELECT * FROM `users` LIMIT 5 -qb.limit(5).get('users',callback); +qb.limit(5).get('users', callback); ``` -You can provide an option offset value instead of calling [.offset()](#offset) seperately: +You can provide an option offset value instead of calling [.offset()](#offset) separately: ```javascript // SELECT * FROM `users` LIMIT 5, 5 -qb.limit(5,5).get('users',callback); +qb.limit(5, 5).get('users', callback); ``` -------------------------------------------------------------------------------- @@ -967,7 +975,7 @@ The practical uses of this method are probably miniscule since the `.limit()` me ```javascript // SELECT * FROM `users` LIMIT 5, 25 -qb.limit(5).offset(25).get('users',callback); +qb.limit(5).offset(25).get('users', callback); ``` -------------------------------------------------------------------------------- @@ -1022,7 +1030,7 @@ API Method | SQL Command | MySQL | MSSQL | Oracle | Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. Both (if provided) will be supplied to the callback function. ### Handling Error Messages and Results -The final parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a javascript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. +The final parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a JavaScript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. ### Response Format Examples @@ -1051,7 +1059,7 @@ const callback = (err, response) => { } }; pool.get_connection(qb => { - qb.get('foo',callback); + qb.get('foo', callback); }); ``` @@ -1086,7 +1094,7 @@ pool.get_connection(qb => { -### .query(query_string,callback) +### .query(query_string, callback) Parameter | Type | Default | Description :----------- | :------- | :------- | :--------------------------------------------- @@ -1118,7 +1126,7 @@ Parameter | Type | Default | Description table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. callback | Function | Required | What to do when the driver has responded -This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) seperately. If the table name is omitted, and the first paramter is a callback function, there will be no need to pass a callback function into the second parameter. +This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) separately. If the table name is omitted, and the first parameter is a callback function, there will be no need to pass a callback function into the second parameter. **Type of Response Sent to Callback** @@ -1182,15 +1190,15 @@ qb.limit(10) -### .get_where(table,where,callback) +### .get_where(table, where, callback) Parameter | Type | Default | Description :-------- | :-------------- | :------- | :------------------------------------------------- -table | String or Array | Required | Used to avoid having to call `.from()` seperately. -where | Object | Required | Used to avoid having to call `.where()` seperately +table | String or Array | Required | Used to avoid having to call `.from()` separately. +where | Object | Required | Used to avoid having to call `.where()` separately callback | Function | Required | What to do when the driver has responded. -This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` seperately). The other difference is that _all_ parameters are required and they must be in the proper order. +This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` separately). The other difference is that _all_ parameters are required and they must be in the proper order. **Type of Response Sent to Callback** @@ -1205,7 +1213,7 @@ Basic example: qb.get_where('galaxies', {'num_stars >': 100000000}, callback); ``` -You can still provide other where statements if you want—they'll all work hapilly together: +You can still provide other where statements if you want—they'll all work happily together: ```javascript // SELECT * FROM `galaxies` WHERE `num_stars` > 100000000 AND `galaxy_type_id` = 3 @@ -1220,7 +1228,7 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca Parameter | Type | Default | Description :-------- | :------- | :-------- | :------------------------------------------------------------ -table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. +table | String | undefined | (optional) Used to avoid having to call `.from()` separately. callback | Function | Required | What to do when the driver has responded. This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. @@ -1236,7 +1244,7 @@ Integer ```javascript // SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 3 const type = 3; -qb.where('type',type).count('galaxies', (err, count) => { +qb.where('type', type).count('galaxies', (err, count) => { if (err) return console.error(err); console.log("There are " + numrows + " Type " + type + " galaxies in the Universe."); }); @@ -1246,20 +1254,20 @@ qb.where('type',type).count('galaxies', (err, count) => { -### .update(table,data[,where],callback) +### .update(table, data[,where], callback) Parameter | Type | Default | Description :-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- table | String | null | (suggested) The table/collection you'd like to update data | Object | null | (suggested) The data to update (ex. `{field: value}`) -where | Object | null | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +where | Object | null | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. callback | Function | Required | What to do when the driver has responded. This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... **NOTE:** -The first and second parameters are not required but I do suggest you use them as your code will be much easier to read. If you choose not to use them, you will need to pass a falsy value to each... you can't simply skip them. My recommendation is to use `null`. The way you would supply these values without using this method would be through the `from()` method for the first paramater and the `set()` method for the second paramter. +The first and second parameters are not required but I do suggest you use them as your code will be much easier to read. If you choose not to use them, you will need to pass a "falsey" value to each... you can't simply skip them. My recommendation is to use `null`. The way you would supply these values without using this method would be through the `from()` method for the first parameter and the `set()` method for the second parameter. **Type of Response Sent to Callback** @@ -1318,14 +1326,14 @@ qb.where('id', 42) -### .update_batch(table,dataset,index[,where],callback) +### .update_batch(table, dataset, index[,where], callback) Parameter | Type | Default | Description :-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- table | String | Required | The table/collection you'd like to insert into dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) index | String | Required | Name of the key in each data object that represents a `where` clause. -where | Object | NULL | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +where | Object | NULL | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. callback | Function | Required | What to do when the driver has responded. This method is a somewhat-complex one and, when using transactional databases, a bit pointless. Nevertheless, this will allow you to update a batch of rows with one query which, in theory, should be faster than running multiple update queries. @@ -1382,7 +1390,7 @@ As you can see, in each `CASE` statement, the `key` and it's value are being use -### .insert(table,data[,ignore[,on_dupe]],callback) +### .insert(table, data[,ignore[,on_dupe]], callback) Parameter | Type | Default | Description :-------- | :------- | :-------- | :-------------------------------------------------------------------------------------------------------------- @@ -1436,7 +1444,7 @@ app.post('/add_article', (req, res) => { -### .insert_batch(table,dataset[,ignore[,on_dupe]],callback) +### .insert_batch(table, dataset[,ignore[,on_dupe]], callback) Parameter | Type | Default | Description :-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ @@ -1476,7 +1484,7 @@ qb.insert_batch('db_engines', data, (err, res) => { -### .insert_ignore(table,data[,on_dupe],callback) +### .insert_ignore(table, data[,on_dupe], callback) Parameter | Type | Default | Description :-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ @@ -1546,12 +1554,12 @@ qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NO -### .delete(table,where,callback) +### .delete(table, where, callback) Parameter | Type | Default | Description :-------- | :------- | :-------- | :---------------------------------------------------------------------------------------------------- table | String | Required | The table/collection you'd like to delete records from. -where | Object | undefined | (optional) Used to avoid having to call `.where()` seperately. Pass NULL if you don't want to use it. +where | Object | undefined | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. callback | Function | Required | What to do when the driver has responded. This method is used to delete records from a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with the number of affected rows. @@ -1598,7 +1606,7 @@ app.post('/delete_comment/:id', (req, res) => { -### .truncate(table,callback) +### .truncate(table, callback) Parameter | Type | Default | Description :-------- | :------- | :------- | :------------------------------------------- @@ -1648,7 +1656,7 @@ pool.get_connection(qb => { -### .empty_table(table,callback) +### .empty_table(table, callback) Parameter | Type | Default | Description :-------- | :------- | :------- | :------------------------------------------- @@ -1758,7 +1766,7 @@ pool.get_connection(qb => { qb.update('users', user, {id: user.id}, (err, res) => { if (user.length > 0) { - setTimeout(update_user,0); + setTimeout(update_user, 0); } else { qb.release(); } @@ -1817,7 +1825,7 @@ Parameter | Type | Default | Description :-------- | :---- | :------- | :------------------------------------------------ value | Mixed | Required | The value to escape based on your database driver -This can be used to excape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). +This can be used to escape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). **What should happen:** _Examples given are for MySQL_ @@ -1847,7 +1855,7 @@ qb.query(sql, (err, res) => { ### SQL Compilation Methods These methods can be used to build a query string without having to execute it. This is a fantastic option if you want to use the querybuilder to simply build queries and display the resulting string or to send the compiled query string off to a driver/engine other than the one offered by `node-querybuilder`. -These are excellent educational tools and can be used like a SQL/NoSQL language rosetta stone of sorts. +These are excellent educational tools and can be used like a SQL/NoSQL language Rosetta Stone of sorts. These methods are not asynchronous and, therefore, just return the compiled query string. @@ -1860,7 +1868,7 @@ These methods are not asynchronous and, therefore, just return the compiled quer Parameter | Type | Default | Description :-------- | :----- | :-------- | :---------------------------------------------------------- -table | String | Undefined | (optional) Used to avoid having to call .from() seperately. +table | String | Undefined | (optional) Used to avoid having to call .from() separately. Compiles a SELECT-like query into a properly-escaped string. @@ -1890,7 +1898,7 @@ console.log(sql); Parameter | Type | Default | Description :-------- | :----- | :-------- | :---------------------------------------------------------- -table | String | Undefined | (optional) Used to avoid having to call .from() seperately. +table | String | Undefined | (optional) Used to avoid having to call .from() separately. Compiles a INSERT-like query into a properly-escaped string. @@ -1911,7 +1919,6 @@ const sql = qb.set(data).get_compiled_insert('users'); // INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES ('foobar', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Foo', 'Bar') console.log(sql); -}); ``` -------------------------------------------------------------------------------- @@ -1923,7 +1930,7 @@ console.log(sql); Parameter | Type | Default | Description :-------- | :----- | :-------- | :---------------------------------------------------------- -table | String | Undefined | (optional) Used to avoid having to call .from() seperately. +table | String | Undefined | (optional) Used to avoid having to call .from() separately. Compiles an UPDATE-like query into a properly-escaped string. @@ -1938,7 +1945,7 @@ const data = { password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), }; const sql = qb - .where('id',4321) + .where('id', 4321) .set(data) .get_compiled_update('users'); @@ -1955,7 +1962,7 @@ console.log(sql); Parameter | Type | Default | Description :-------- | :----- | :-------- | :---------------------------------------------------------- -table | String | Undefined | (optional) Used to avoid having to call .from() seperately. +table | String | Undefined | (optional) Used to avoid having to call .from() separately. Compiles a SELECT-like query into a properly-escaped string. @@ -1965,11 +1972,10 @@ Delete a user ```javascript const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -const sql = qb.where('id',4321).get_compiled_delete('users'); +const sql = qb.where('id', 4321).get_compiled_delete('users'); // DELETE FROM `users` WHERE `id` = 4321 console.log(sql); -}); ``` -------------------------------------------------------------------------------- diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index f74dc0b..33c9b47 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -863,10 +863,14 @@ const QueryBuilder = function() { select = select.split(','); } else { if (escape === true) { + // Prevent it from trying to parse select statements with functions and if statements + if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + + // Identify individual statements within select string let m, open_paren_index, inner_parenthesis; const reg = /\)/g; while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0,m.index).lastIndexOf('('); + open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); if (open_paren_index !== -1) { inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); if (inner.parenthesis.indexOf(',') !== -1) { diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index 6376d23..f9119b7 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -136,14 +136,36 @@ describe('select()', () => { qb.select('universe.galaxy.star_system.planet as planet'); qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); }); - it('should not allow subqueries or functions with commas in them without the second parameter being false', () => { + it('should not allow subqueries without the second parameter being false', () => { qb.reset_query(); expect( () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') ).to.throw(Error); expect( - () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets',false) + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = 42) as num_planets') + ).to.throw(Error); + + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets', false) + ).to.not.throw(Error); + }); + it('should not allow functions without the second paramter being false', () => { + expect( + () => qb.select('s.star_systems, count(planets) as num_planets') + ).to.throw(Error); + + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets') + ).to.throw(Error); + + + expect( + () => qb.select('s.star_systems, count(planets) as num_planets', false) + ).to.not.throw(Error); + + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets', false) ).to.not.throw(Error); }); it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', () => { @@ -181,4 +203,4 @@ for (const i in prefixes) { qb.select_array.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); }); }); -} \ No newline at end of file +} From 23be2457b3033af4354eb692dd304347b2c77dc7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Jun 2018 16:52:40 -0400 Subject: [PATCH 137/200] 1.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80d2161..12be499 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.2.0", + "version": "1.2.1", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 47a34bfdc5839fb6d50d315f0e6b1f9603427a3d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Jun 2018 22:53:58 -0400 Subject: [PATCH 138/200] In reference to #21 Added some documentation on how to handle releasing connections. --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index af5810e..95a6077 100755 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single' qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', (err, response) => { + qb.disconnect(); + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 @@ -179,6 +181,40 @@ This library currently supports 3 connection methods: const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); ``` +## Handling Connections + +It's important to handle your connections properly. When not using a pool, for every connection you make, you'll need to disconnect it when you're done. If you're using a pool (or cluster), it's a similar concept... but you'll be _releasing_ the connection back to the pool so it can be used again later. + +**Single Connection Example:** + +```javascript +const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); + +qb.get('planets', (err, response) => { + // Disconnect right away unless you're going to use it again for subsequent query + qb.disconnect(); + + if (err) return console.error(err); + return console.log("Results: ", response); +}); +``` + +**Connection Pool Example:** + +```javascript +const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); + +pool.get_connection(qb => { + qb.get('planets', (err, response) => { + // Release right away unless you're going to use it again for subsequent query + qb.release(); + + if (err) return console.error(err); + return console.log("Results: ", response); + }); +}); +``` + # API Methods **_NOTE:_** The compatibility portions of these tables are subject to change as features and drivers are written! From 24ea9c34cfa15449d7c5a4db4fa647038e121c70 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Jun 2018 22:55:17 -0400 Subject: [PATCH 139/200] Quick clarification --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 95a6077..9000bb8 100755 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ qb.get('planets', (err, response) => { ```javascript const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +// Get a connection (aka a QueryBuilder instance) from the pool pool.get_connection(qb => { qb.get('planets', (err, response) => { // Release right away unless you're going to use it again for subsequent query From 8f17e3a0f578d963cd37e8fa4074d9df7329179f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 5 Jun 2018 22:58:44 -0400 Subject: [PATCH 140/200] Marked "cluster" connection type as incomplete --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9000bb8..19df916 100755 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ This library currently supports 3 connection methods: - This will utilize the driver's connection pooling capabilities if it is offered. Connection pooling allows your application to pull from a pool of connections that were created by the driver. Typically the connections will be handed out to requesting methods in a round-robin fashion. This is ideal for a web application. - **_cluster_** + - _NOTE: This feature is currently incomplete._ - When you have a cluster of servers and you want to create pools of connections to different servers to help load balance your stack, using the `cluster` connection type can come in handy. This is ideal for high-traffic web sites and applications that utilize a farm of database servers as opposed to just one. **Note:** You will specify the type of connection as the third parameter to the contructor From 58f1f714a6391cd67da80c515f5399f1152918d4 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 6 Jun 2018 15:53:44 -0400 Subject: [PATCH 141/200] 1.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12be499..8f89d00 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "1.2.1", + "version": "1.2.2", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", From 8410d95961087cc2f9424fffed889cc0b905826c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 11 Jun 2018 11:24:39 -0400 Subject: [PATCH 142/200] Added MSSQL driver. Wrote adapter tests for MSSQL. Updated history and readme file. --- HISTORY.md | 6 + Makefile | 4 +- README.md | 351 ++++--- drivers/drivers.json | 10 +- drivers/mssql/adapters.js | 222 +++++ drivers/mssql/adapters_old.js | 224 +++++ drivers/mssql/query_builder.js | 1709 ++++++++++++++++++++++++++++++++ drivers/mssql/query_exec.js | 208 ++++ drivers/mysql/adapters.js | 279 +++--- drivers/mysql/adapters_old.js | 233 +++++ index.js | 170 ++-- package.json | 1 + test/mssql/tests-adapter.js | 293 ++++++ test/mysql/mock_data.sql | 0 test/mysql/tests-adapter.js | 536 +++++----- 15 files changed, 3629 insertions(+), 617 deletions(-) create mode 100755 drivers/mssql/adapters.js create mode 100755 drivers/mssql/adapters_old.js create mode 100755 drivers/mssql/query_builder.js create mode 100755 drivers/mssql/query_exec.js create mode 100755 drivers/mysql/adapters_old.js create mode 100755 test/mssql/tests-adapter.js mode change 100644 => 100755 test/mysql/mock_data.sql diff --git a/HISTORY.md b/HISTORY.md index 2e4ceef..13ae1de 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,12 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. +## v2.0.0 (2018-06-15) + +* Added mssql support +* Updated class files to use new ES6 class syntax for easier-maintainability +* Changed the Query Builder instantiation syntax + ## v1.2.0 (2018-05-18) * retroactive fix of this change log diff --git a/Makefile b/Makefile index bb1afe9..273e562 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -TESTS = test/mysql/*.js +TESTS = test/mssql/*.js test: mocha --timeout 5000 --check-leaks --reporter spec $(TESTS) - + .PHONY: test diff --git a/README.md b/README.md index 19df916..0707c47 100755 --- a/README.md +++ b/README.md @@ -64,19 +64,20 @@ Licensed under the GPL license and MIT: This quick example shows how to connect to and asynchronously query a MySQL database using a single connection. ```javascript +const QueryBuilder = require('node-querybuilder'); const settings = { host: 'localhost', database: 'mydatabase', user: 'myuser', password: 'MyP@ssw0rd' }; -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = new QueryBuilder(settings, 'mysql', 'single'); qb.select('name', 'position') .where({type: 'rocky', 'diameter <': 12000}) .get('planets', (err, response) => { qb.disconnect(); - + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 @@ -93,11 +94,11 @@ qb.select('name', 'position') Driver | Default | Ready | single | pool | cluster | Additional Connection Options :----------------------------------------- | :------- | :------ | :----- | :--- | :------ | :---------------------------------------------------------------------------------------- -[mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Yes | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) -[mssql](//www.npmjs.com/package/mssql) | | No | Yes | ??? | ??? | +[mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Pending | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) +[mssql](//www.npmjs.com/package/mssql) | | Yes | Yes | Yes | ??? | [node-mssql connection options](https://github.com/tediousjs/node-mssql#general-same-for-all-drivers) [sqlite3](//www.npmjs.com/package/sqlite3) | | No | Yes | ??? | ??? | [oracle](//www.npmjs.com/package/oracle) | | No | Yes | ??? | ??? | -[postgres](//www.npmjs.com/package/pg) | | Pending | Yes | Yes | ??? | +[postgres](//www.npmjs.com/package/pg) | | No | Yes | Yes | ??? | [mongodb](//www.npmjs.com/package/mongodb) | | No | Yes | ??? | ??? | ## Standard Connection Settings @@ -135,7 +136,7 @@ We'll call this `db.json`. ```javascript const settings = require('db.json'); // Second and third parameters of the QueryBuilder method default to 'mysql' and 'standard', respectively -const qb = require('node-querybuilder').QueryBuilder(settings); +const qb = new require('node-querybuilder')(settings); ``` Of course you can also just have a normal javascript object directly within your code somwhere if you're honing your inner Chuck Norris: @@ -143,7 +144,7 @@ Of course you can also just have a normal javascript object directly within your **Chuck Norris App** ```javascript -const qb = require('node-querybuilder').QueryBuilder({ +const qb = new require('node-querybuilder')({ host: 'db.myserver.com', user: 'myusername', password: 'P@s$w0rD', @@ -158,7 +159,7 @@ This part is super simple. Just pass which one you'd like to use as the second p **_Example:_** ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'postgres'); +const qb = new require('node-querybuilder')(settings, 'postgres'); ``` ## Choosing the Connection Type @@ -179,7 +180,7 @@ This library currently supports 3 connection methods: **Example:** ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const qb = new require('node-querybuilder')(settings, 'mysql', 'pool'); ``` ## Handling Connections @@ -189,12 +190,12 @@ It's important to handle your connections properly. When not using a pool, for e **Single Connection Example:** ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +const qb = new require('node-querybuilder')(settings, 'mysql'); qb.get('planets', (err, response) => { // Disconnect right away unless you're going to use it again for subsequent query qb.disconnect(); - + if (err) return console.error(err); return console.log("Results: ", response); }); @@ -203,7 +204,7 @@ qb.get('planets', (err, response) => { **Connection Pool Example:** ```javascript -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); // Get a connection (aka a QueryBuilder instance) from the pool pool.get_connection(qb => { @@ -223,33 +224,33 @@ pool.get_connection(qb => { ## Chainable Methods Chainable methods can be called as many times as you'd like in any order you like. The final query will not be built and executed until one of the [execution methods](#execution-methods), like `get()`, are callled. As the name implies, the methods can be chained together indefinitely but this is not required. You definitely call them individually with the same effect at execution time. -API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo -:------------------------------------ | :---------- | :------: | :---: | :----: | :----: | :------: | :---: -[select()](#select) | SELECT | ✓ | | | | | -[distinct()](#distinct) | DISTINCT | ✓ | | | | | -[select_min()](#min) | MIN | ✓ | | | | | -[select_max()](#max) | MAX | ✓ | | | | | -[select_avg()](#avg) | AVG | ✓ | | | | | -[select_sum()](#sum) | SUM | ✓ | | | | | -[from()](#from) | FROM | ✓ | | | | | -[join()](#join) | JOIN | ✓ | | | | | -[where()](#where) | WHERE | ✓ | | | | | -[where_in()](#where_in) | IN | ✓ | | | | | -[where_not_in()](#where_not_in) | WHERE | ✓ | | | | | -[or_where()](#or_where) | WHERE | ✓ | | | | | -[or_where_in()](#or_where_in) | WHERE | ✓ | | | | | -[or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | -[like()](#like) | LIKE | ✓ | | | | | -[or_like()](#or_like) | LIKE | ✓ | | | | | -[or_not_like()](#or_not_like) | LIKE | ✓ | | | | | -[not_like()](#not_like) | LIKE | ✓ | | | | | -[group_by()](#group-by) | GROUP BY | ✓ | | | | | -[having()](#having) | HAVING | ✓ | | | | | -[or_having()](#or_having) | HAVING | ✓ | | | | | -[order_by()](#order-by) | ORDER BY | ✓ | | | | | -[limit()](#limit) | LIMIT | ✓ | | | | | -[offset()](#offset) | OFFSET | ✓ | | | | | -[set()](#set) | SET | ✓ | | | | | +API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:------------------------------------ | :---------- | :------: | :------: | :------: | :------: | :------: | :------: +[select()](#select) | SELECT | ✓ | | | | | +[distinct()](#distinct) | DISTINCT | ✓ | | | | | +[select_min()](#min) | MIN | ✓ | | | | | +[select_max()](#max) | MAX | ✓ | | | | | +[select_avg()](#avg) | AVG | ✓ | | | | | +[select_sum()](#sum) | SUM | ✓ | | | | | +[from()](#from) | FROM | ✓ | | | | | +[join()](#join) | JOIN | ✓ | | | | | +[where()](#where) | WHERE | ✓ | | | | | +[where_in()](#where_in) | IN | ✓ | | | | | +[where_not_in()](#where_not_in) | WHERE | ✓ | | | | | +[or_where()](#or_where) | WHERE | ✓ | | | | | +[or_where_in()](#or_where_in) | WHERE | ✓ | | | | | +[or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | +[like()](#like) | LIKE | ✓ | | | | | +[or_like()](#or_like) | LIKE | ✓ | | | | | +[or_not_like()](#or_not_like) | LIKE | ✓ | | | | | +[not_like()](#not_like) | LIKE | ✓ | | | | | +[group_by()](#group-by) | GROUP BY | ✓ | | | | | +[having()](#having) | HAVING | ✓ | | | | | +[or_having()](#or_having) | HAVING | ✓ | | | | | +[order_by()](#order-by) | ORDER BY | ✓ | | | | | +[limit()](#limit) | LIMIT | ✓ | | | | | +[offset()](#offset) | OFFSET | ✓ | | | | | +[set()](#set) | SET | ✓ | | | | | -------------------------------------------------------------------------------- @@ -1104,7 +1105,7 @@ pool.get_connection(qb => { #### Using the Same Connection Pool Connection for Successive Calls ```javascript -const pool = require('node-querybuilder').QueryBuilder(settings,'mysql','pool'); +const pool = new require('node-querybuilder')(settings,'mysql','pool'); const data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; pool.get_connection(qb => { @@ -1319,7 +1320,7 @@ Here's a contrived example of how it might be used in an app made with the Expre const express = require('express'); const app = express(); const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); app.post('/update_account', (req, res) => { const user_id = req.session.user_id; @@ -1350,7 +1351,7 @@ app.post('/update_account', (req, res) => { Here's another (more-direct) example where one decided to supply the table, data, and filters through alternative methods: ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = new require('node-querybuilder')(settings, 'mysql', 'single'); qb.where('id', 42) .from('users') .set('email', 'email@domain.net') @@ -1383,7 +1384,7 @@ The important thing to understand is that there are, essentially, _two_ `where` **Example:** ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'single'); +const qb = new require('node-querybuilder')(settings, 'mysql', 'single'); // The key to use as the local where clause const key = 'id'; @@ -1452,7 +1453,7 @@ Here's a contrived example of how it might be used in an app made with the Expre const express = require('express'); const app = express(); const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); app.post('/add_article', (req, res) => { const user_id = req.session.user_id; @@ -1501,7 +1502,7 @@ Object containing information about the result of the query. **Example** ```javascript -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +const qb = new require('node-querybuilder')(settings, 'mysql'); const data = [ {name: 'MySQL', version: '5.5.40'}, @@ -1550,7 +1551,7 @@ Object containing information about the result of the query. * ] */ -const qb = require('node-querybuilder').QueryBuilder(settings, 'mysql'); +const qb = new require('node-querybuilder')(settings, 'mysql'); const data = {name: 'Postgres', version: '8.4'}; qb.insert_ignore('db_engines', data, (err, res) => { if (err) throw err; @@ -1616,7 +1617,7 @@ Here's a contrived example of how it might be used in an app made with the Expre const express = require('express'); const app = express(); const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); app.post('/delete_comment/:id', (req, res) => { const comment_id = req.params.id; @@ -1663,7 +1664,7 @@ Object containing information about the result of the query. ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); /* * Assume we have a table like this to start with... @@ -1711,7 +1712,7 @@ Object containing information about the result of the query. ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); /* * Assume we have a table like this to start with... @@ -1740,91 +1741,181 @@ pool.get_connection(qb => { -------------------------------------------------------------------------------- -## Other Library-Specifc Methods -These are methods that aren't part of the query-building chain, but, rather, methods you might call before, after, or during (but not as part of) building a query. - -API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo -:-------------------------------------------- | :------: | :---: | :----: | :----: | :------: | :---: -[get_connection()](#get_connection) | ✓ | | | | | -[release()](#release) | ✓ | | | | | -[last_query()](#last_query) | ✓ | | | | | -[escape()](#escape) | ✓ | | | | | -[get_compiled_select()](#get_compiled_select) | ✓ | | | | | -[get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | -[get_compiled_update()](#get_compiled_update) | ✓ | | | | | -[get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | +## Other Library-Specific Methods +These are methods that are not part of the query-building chain, but, rather, methods you might call before, after, or during (but not as part of) building a query. + +API Method | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:-------------------------------------------- | :------: | :------: | :------: | :------: | :------: | :------: +[connection](#connection_settings) | ✓ | ✓ | | | | +[connection_settings()](#connection_settings) | ✓ | ✓ | | | | +[disconnect()](#disconnect) | ✓ | ✓ | | | | +[escape()](#escape) | ✓ | | | | | +[get_connection()](#get_connection) | ✓ | ✓ | | | | +[last_query()](#last_query) | ✓ | | | | | +[release()](#release) | ✓ | ✓ | | | | +[get_compiled_select()](#get_compiled_select) | ✓ | | | | | +[get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | +[get_compiled_update()](#get_compiled_update) | ✓ | | | | | +[get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | -------------------------------------------------------------------------------- - - -### .get_connection(callback) + -Parameter | Type | Default | Description -:-------- | :------- | :------- | :--------------------------------------------------------- -callback | Function | Required | What to do when the connection is retrieved from the pool. +### .connection() -Used to get a new connection from the connection pool or cluster pool. An instances of the QueryBuilder adapter for your specific connection will be passed to the callback. Make sure that your connection is [release](#release)d when you are done with it! +Get a reference to an instance of the connection handle from the driver that's being utilized under the surface. With that connection handle instance, you can run any of the native methods from that driver that don't have equivalents in this library. For instance, with the `mssql` driver, you would be able to execute stored procedures. **Example** ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mssql', 'pool'); pool.get_connection(qb => { - qb.limit(10).get('users', (err, res) => { + const conn = qb.get_connection(); + const request = new conn.Request(); + + request.input('input_parameter', sql.Int, 8675309); + request.output('output_parameter', sql.Int); + + request.execute('call_phone_number', (err, result) => { qb.release(); - // Do stuff with results or err + // Do stuff }); }); ``` -------------------------------------------------------------------------------- - + -### .release() -Releases a connection back to the pool when you are done with it. Calling this is _super_ important! +### .connection_settings() + +Simply returns your connection settings object (the configuration object used to create your QueryBuilder instance) for reference or use elsewhere. This is not something you'll likely find yourself using but we'll document it nonetheless. + +**Example** + +```javascript +const settings = require('db.json'); +const qb = new require('node-querybuilder')(settings, 'mssql'); + +// This results in the same value as the value of the `settings` variable +const connection_settings = qb.connection_settings(); +``` + +-------------------------------------------------------------------------------- + + + +### .disconnect(callback) + +Parameter | Type | Default | Description +:-------- | :------- | :------- | :--------------------------------------------------------- +callback | Function | Required | What to do when the connection is fully terminated. + +Disconnect from the server after your query is complete. You _must_ call this each time after your done querying the database! **NOTE:** You would _not_ use this when using a connection pool. This should only be called when working with single one-off connections. After disconnecting, all subsequent queries will fail unless you reconnect ([connect()](#connect)). **Examples** -Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. +Below is a contrived example that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not terminated until all the queries that needed to be executed have been executed. ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const qb = new require('node-querybuilder')(settings, 'mysql'); + +qb.like('username','|','right').get_where('users', {active: true}, (err, res) => { + let users = res; + (function update_user() { + const user = users.shift(); + user.username = user.username.replace(/\^|/,''); + + qb.update('users', user, {id: user.id}, (err, res) => { + if (err) { + console.error("Failed to update user...", err); + qb.diconnect(); + } -pool.get_connection(qb => { - qb.like('username','|','right').get_where('users', {active: true}, (err, res) => { - const users = users; - (function update_user() { - const user = users.shift(); - user.username = user.username.replace(/\^|/,''); + if (user.length > 0) { + setTimeout(update_user, 0); + } else { + qb.disconnect(); + } + }); + })(); +}); - qb.update('users', user, {id: user.id}, (err, res) => { - if (user.length > 0) { - setTimeout(update_user, 0); - } else { - qb.release(); - } - }); - })(); - }); +``` + +Here's a simpler example so you can better see how it will most often be used: + +```javascript +const settings = require('db.json'); +const qb = new require('node-querybuilder')(settings, 'mysql'); + +qb.get_where('users', {username: 'foobar'}, (err, res) => { + qb.disconnect(); + if (err) throw err; + console.log("Success: ", res); }); ``` -Here's a simpler example so you can better see how it will most often be used +-------------------------------------------------------------------------------- + + + +### .escape(value) + +Parameter | Type | Default | Description +:-------- | :---- | :------- | :------------------------------------------------ +value | Mixed | Required | The value to escape based on your database driver + +This can be used to escape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). + +**What should happen:** _Examples given are for MySQL_ + +Input Type | Output Type | Ex. Input | Ex. Output +:--------- | :---------: | :----------------: | :------------------------: +String | String | "\n\s\x1a" | "\\n\\s\\x1a" +Integer | String | 76 | '76' +Array | String | [1,2,3] | '1','2',3' +Date | String | new Date() | '2015-01-30 16:54:23.1856' +Buffer | String | new Buffer(1) | 'X\'00\'' +Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" + +**Example** + +```javascript +const qb = new require('node-querybuilder')(require('db.json'), 'mysql'); +const sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; +qb.query(sql, (err, res) => { + // SELECT count(*) FROM `star_systems` WHERE `planet_num` = 5 LIMIT 10 + console.log(res); +}); +``` + +-------------------------------------------------------------------------------- + + + +### .get_connection(callback) + +Parameter | Type | Default | Description +:-------- | :------- | :------- | :--------------------------------------------------------- +callback | Function | Required | What to do when the connection is retrieved (or not) from the pool. + +Used to get a new connection from the connection pool or cluster pool. An instance of the QueryBuilder adapter for your specific connection will be passed to the callback. Make sure that your connection is [release](#release)d when you are done with it! + +**Example** ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); pool.get_connection(qb => { - qb.get_where('users', {username: 'foobar'}, (err, res) => { + qb.limit(10).get('users', (err, res) => { qb.release(); - if (err) throw err; - console.dir(res); + // Do stuff with results or err }); }); ``` @@ -1842,7 +1933,7 @@ If you'd rather the engine not execute the query first, you can always use the a ```javascript const settings = require('db.json'); -const pool = require('node-querybuilder').QueryBuilder(settings, 'mysql', 'pool'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); pool.get_connection(qb => { const id = 4531; qb.get('comments', {id: id}, (err, res) => { @@ -1855,34 +1946,50 @@ pool.get_connection(qb => { -------------------------------------------------------------------------------- - + -### .escape(value) +### .release() +Releases a connection back to the pool when you are done with it. Calling this is _super_ important! -Parameter | Type | Default | Description -:-------- | :---- | :------- | :------------------------------------------------ -value | Mixed | Required | The value to escape based on your database driver +**Examples** -This can be used to escape a value using your driver's native escape method. If your driver does not have a native escape method, the value will simply be returned. This is useful for when you want to build a SQL string manually (for instance, you don't want certain items to be escaped). +Below is a contrived example (with no error handling--for brevity) that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not released until all the queries that needed to be executed have been executed. -**What should happen:** _Examples given are for MySQL_ +```javascript +const settings = require('db.json'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); -Input Type | Output Type | Ex. Input | Ex. Output -:--------- | :---------: | :----------------: | :------------------------: -String | String | "\n\s\x1a" | "\\n\\s\\x1a" -Integer | String | 76 | '76' -Array | String | [1,2,3] | '1','2',3' -Date | String | new Date() | '2015-01-30 16:54:23.1856' -Buffer | String | new Buffer(1) | 'X\'00\'' -Object | String | {foo: 'bar', i: 3} | "`foo` = 'bar', `i` = 3" +pool.get_connection(qb => { + qb.like('username','|','right').get_where('users', {active: true}, (err, res) => { + let users = res; + (function update_user() { + const user = users.shift(); + user.username = user.username.replace(/\^|/,''); -**Example** + qb.update('users', user, {id: user.id}, (err, res) => { + if (user.length > 0) { + setTimeout(update_user, 0); + } else { + qb.release(); + } + }); + })(); + }); +}); +``` + +Here's a simpler example so you can better see how it will most often be used ```javascript -const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); -const sql = 'SELECT count(*) FROM `star_systems` WHERE ' + qb.escape({planet_num: 5}) + ' LIMIT 10'; -qb.query(sql, (err, res) => { - console.dir(res); +const settings = require('db.json'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); + +pool.get_connection(qb => { + qb.get_where('users', {username: 'foobar'}, (err, res) => { + qb.release(); + if (err) throw err; + console.dir(res); + }); }); ``` @@ -1915,7 +2022,7 @@ Compiles a SELECT-like query into a properly-escaped string. Get certain details of a user account ```javascript -const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = new require('node-querybuilder')(require('db.json'), 'mysql'); const sql = qb .select(['id','username','first_name','last_name']) @@ -1945,7 +2052,7 @@ Compiles a INSERT-like query into a properly-escaped string. Add a new user to a `users` table. ```javascript -const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = new require('node-querybuilder')(require('db.json'), 'mysql'); const crypto = require('crypto'); const data = { username: 'foobar', @@ -1977,7 +2084,7 @@ Compiles an UPDATE-like query into a properly-escaped string. Update the password of a user ```javascript -const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = new require('node-querybuilder')(require('db.json'), 'mysql'); const crypto = require('crypto'); const data = { password: crypto.createHash('sha1').update('P@$$w0rD').digest('hex'), @@ -2009,7 +2116,7 @@ Compiles a SELECT-like query into a properly-escaped string. Delete a user ```javascript -const qb = require('node-querybuilder').QueryBuilder(require('db.json'), 'mysql'); +const qb = new require('node-querybuilder')(require('db.json'), 'mysql'); const sql = qb.where('id', 4321).get_compiled_delete('users'); // DELETE FROM `users` WHERE `id` = 4321 diff --git a/drivers/drivers.json b/drivers/drivers.json index 8af906c..106721d 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -1,6 +1,6 @@ { "mysql": { - "connection_types": { "single": true, "pool": true, "cluster": true }, + "connection_types": { "single": true, "pool": true, "cluster": false }, "versions": { "2.5.4": { "path": "./drivers/mysql/", @@ -12,13 +12,13 @@ } }, "mssql": { - "connection_types": { "single": true, "pool": false, "cluster": false }, + "connection_types": { "single": true, "pool": true, "cluster": false }, "versions": { - "1.3.0": { + "4.1.0": { "path": "./drivers/mssql/", - "active": false + "active": true }, - "default": "1.3.0" + "default": "4.1.0" } }, "oracle": { diff --git a/drivers/mssql/adapters.js b/drivers/mssql/adapters.js new file mode 100755 index 0000000..6915ecc --- /dev/null +++ b/drivers/mssql/adapters.js @@ -0,0 +1,222 @@ +const mssql = require('mssql'); +class Adapter { + constructor(nqb) { + // Verify that an instance of Node QueryBuilder was passed in + if (!nqb || typeof nqb !== 'object') { + throw new Error("No/Invalid QueryBuilder super object instance supplied."); + } + + // Store QB super object as class prop + this.nqb = Object.assign({}, nqb); + + // Verify setting property exists + if (!this.nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + // Enable debugging if necessary + this.debugging = false; + if (this.nqb.settings.hasOwnProperty('qb_debug') && this.nqb.settings.qb_debug === true) { + this.debugging = true; + delete this.nqb.settings.qb_debug; + } + + // Verify that required fields are provided... + if (Object.keys(this.nqb.settings).length === 0) throw new Error("No connection information provided!"); + if (!this.nqb.settings.hasOwnProperty('host')) this.nqb.settings.host = 'localhost'; + if (!this.nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + + this.map_connection_settings(); + } + + // **************************************************************************** + // Map generic NQB connection settings to mssql's format + // **************************************************************************** + map_connection_settings() { + this.connection_settings = { + server: this.nqb.settings.host, + user: this.nqb.settings.user, + password: this.nqb.settings.password + } + if (this.nqb.settings.hasOwnProperty('database')) { + this.connection_settings.database = this.nqb.settings.database; + delete this.nqb.settings.database + } + if (this.nqb.settings.hasOwnProperty('port')) { + this.connection_settings.port = this.nqb.settings.port; + delete this.nqb.settings.port + } + + // Remove mapped settings: + delete this.nqb.settings.host + delete this.nqb.settings.user + delete this.nqb.settings.password + + // Merge any driver-specific settings into connection settings + this.connection_settings = Object.assign(this.connection_settings, this.nqb.settings); + } + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object + // **************************************************************************** + get_query_builder() { + try { + return require('./query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + this.nqb.driver + ": " + e); + } + } + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object + // **************************************************************************** + get_query_exec(qb, conn) { + try { + return require('./query_exec.js').QueryExec(qb, conn); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + this.nqb.driver + ": " + e); + } + } +} + + +// **************************************************************************** +// Generic Single Adapter +// ----- +// @return Object Single Adapter object +// **************************************************************************** +class Single extends Adapter { + constructor(nqb, settings) { + super(nqb); + + // Set defaults + this.pool = null; + this._connection = null; + + // If the Pool object is instatiating this Adapter, use it's connection + if (settings && settings.pool) { + this.pool = settings.pool.pool; + this._connection = settings.pool.connection; + } + // Otherwise, let's create a new connection + else { + this._connection = mssql.connect(this.connection_settings); + } + + this.qb = this.get_query_builder(); + this.qe = this.get_query_exec(this.qb, this._connection); + + const self = this; + + return Object.assign({ + connection_settings: function() { + return self.connection_settings; + }, + + connect: function(callback) { + return self._connection.then(err => { + return callback(err); + }); + }, + + connection: function() { + return self._connection; + }, + + escape: function(str) { + throw new Error("The `escape` method is not supported with the mssql driver!"); + }, + + escape_id: function(str) { + throw new Error("The `escape` method is not supported with the mssql driver!"); + }, + + disconnect: function(callback) { + return self.connection.end(callback); + }, + + release: function() { + if (!self.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + self.pool.releaseConnection(self.connection); + } + }, self.qb, self.qe); + } +} + +// **************************************************************************** +// Connection Pool Adapter +// ----- +// @return Object Adapter object +// **************************************************************************** +class Pool extends Adapter { + constructor(nqb) { + super(nqb); + + // Create pool for node-querybuilder object if it doesn't already have one. + if (!this.nqb.hasOwnProperty('pool') || this.nqb.pool.length === 0) { + // Create connection Pool + this.nqb.pool = new mssql.ConnectionPool(this.connection_settings, err => { + if (this.debugging === true) { + if (err) { + console.error(err); + } else { + console.log('mssql connection pool created'); + } + } + }); + } + + const self = this; + + return { + pool: function() { + return self.nqb.pool; + }, + get_connection: function(callback) { + if (self.nqb.pool === null) { + const error_msg = "Connection pool not available!"; + if (self.debugging === true) console.error(error_msg); + throw new Error(error_msg); + } + + self.nqb.pool.getConnection((err, connection) => { + if (err) throw err; + const adapter = new Single(nqb, { + pool: { + pool: self.nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + self.nqb.pool.close(callback); + } + } + } +} + +// **************************************************************************** +// Clustered Connection Pool Adapter +// ----- +// @return Object Adapter object +// **************************************************************************** +class Cluster extends Adapter { + constructor(nqb) { + super(nqb); + return {}; + } +} + +exports.Adapters = {Single,Pool,Cluster}; diff --git a/drivers/mssql/adapters_old.js b/drivers/mssql/adapters_old.js new file mode 100755 index 0000000..533d2bc --- /dev/null +++ b/drivers/mssql/adapters_old.js @@ -0,0 +1,224 @@ +const Adapters = function(nqb) { + // Load MySQL Driver + const mssql = require('mssql'); + + // Verify setting property exists + if (!nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + // Enable debugging if necessary + this.debugging = false; + if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { + this.debugging = true; + delete nqb.settings.qb_debug; + } + + // Verify that required fields are provided... + if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); + if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; + if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + + this.connection_settings = {}; + const that = this; + + // **************************************************************************** + // Map generic NQB connection settings to mssql's format + // **************************************************************************** + const map_connection_settings = () => { + this.connection_settings = { + server: nqb.settings.host, + user: nqb.settings.user, + password: nqb.settings.password + } + if (nqb.settings.hasOwnProperty('database')) { + this.connection_settings.database = nqb.settings.database; + delete nqb.settings.database + } + if (nqb.settings.hasOwnProperty('port')) { + this.connection_settings.port = nqb.settings.port; + delete nqb.settings.port + } + + // Remove mapped settings: + delete nqb.settings.host + delete nqb.settings.user + delete nqb.settings.password + + // Merge any driver-specific settings into connection settings + this.connection_settings = Object.assign(this.connection_settings, nqb.settings); + } + + map_connection_settings(); + + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object + // **************************************************************************** + const get_query_builder = () => { + try { + return require('./query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object + // **************************************************************************** + const get_query_exec = (qb, conn) => { + try { + return require('./query_exec.js').QueryExec(qb, conn); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Generic Single Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Adapter = function(settings) { + let pool, connection; + + // If the Pool object is instatiating this Adapter, use it's connection + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } + // Otherwise, let's create a new connection + else { + connection = mssql; + } + + const qb = get_query_builder(); + const qe = get_query_exec(qb, connection); + + const adapter = Object.assign({ + connection_settings: function() { + return that.connection_settings; + }, + + connect: function(callback) { + return connection.connect(that.connection_settings, callback); + }, + + connection: function() { + return connection; + }, + + escape: function(str) { + throw new Error("The `escape` method is not supported with the mssql driver!"); + }, + + escape_id: function(str) { + throw new Error("The `escape` method is not supported with the mssql driver!"); + }, + + disconnect: function(callback) { + return connection.end(callback); + }, + + release: function() { + if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + pool.releaseConnection(connection); + } + }, qb, qe); + + return adapter; + }; + + // **************************************************************************** + // Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Pool = function() { + // Return Pool Object + const return_pool = () => { + return { + pool: function() { + return nqb.pool; + }, + get_connection: function(callback) { + if (nqb.pool === null) { + const error_msg = "Connection pool not available!"; + if (that.debuggin === true) console.error(error_msg); + throw new Error(error_msg); + } + + nqb.pool.getConnection((err, connection) => { + if (err) throw err; + const adapter = new Adapter({ + pool: { + pool: nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + nqb.pool.close(callback); + } + } + }; + + // Create pool for node-querybuilder object if it doesn't already have one. + if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { + // Create connection Pool + nqb.pool = new mssql.ConnectionPool(that.connection_settings, err => { + if (that.debugging === true) + if (err) { + console.error(err); + } else { + console.log('mssql connection pool created'); + } + } + }); + } + return return_pool(); + }; + + // **************************************************************************** + // Clustered Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Cluster = () => { + + }; + + // **************************************************************************** + // Basic Description + // ----- + // @param + // @return + // **************************************************************************** + const determine_adapter = () => { + switch(nqb.connection_type) { + case 'cluster': + return new Cluster(); + case 'pool': + return new Pool(); + case 'single': + default: + return new Adapter({}); + } + } + + return determine_adapter(); +}; + +exports.Adapters = Adapters; diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js new file mode 100755 index 0000000..33c9b47 --- /dev/null +++ b/drivers/mssql/query_builder.js @@ -0,0 +1,1709 @@ +const QueryBuilder = function() { + + // ------------------------------ GENERIC FUNCTIONS ------------------------------// + const array_values = item => { + const keys = Object.keys(item); + const length = keys.length; + const values = Array(length); + for (let i = 0; i < length; i++) { + values[i] = item[keys[i]]; + } + return values; + }; + + const prepare_for_limit_and_offset = (item, type = 'limit') => { + + type = type.toLowerCase(); + + if (!/^(string|number)$/.test(typeof item)) { + throw new Error("Only integers or integers in the form of a string are allowed"); + } + + if (typeof item === 'string') { + item = item.trim(); + if (!/^\d+$/.test(item)) { + throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); + } + // Force to an integer + item = parseInt(item); + } + + // Make sure the number is a good one + if (typeof item === 'number') { + // Check for NaN and Infinity + if (item !== +item || item === Infinity) { + throw new Error("You have not provided a valid number to " + type + " by!"); + } + + // Make sure it's positive + if (item < 0) { + throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); + } + + // Only allow integers + if (item % 1 !== 0) { + throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); + } + } + else { + throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); + } + + return item; + } + + const extract_having_parts = (key,key_array) => { + let m; + key = key.trim().replace(/\s+/g,' '); + const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting + const sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting + const num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + const bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; + + if (m = str_condition.exec(key)) { + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = sstr_condition.exec(key)) { + //console.log("Key has sstring value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = num_condition.exec(key)) { + //console.log("Key has numeric value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = bool_condition.exec(key)) { + //console.log("Key has boolean value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else { + throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); + } + + return key_array; + } + + // Simply setting all properties to [] causes reference issues in the parent class. + const clear_array = (a,debug) => { + if (debug === true) { + console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { + for (let key in a) { + if (a.hasOwnProperty(key)) { + delete a[key]; + } + } + } + else if (Array.isArray(a)) { + while (a.length > 0) { + a.pop(); + } + } + if (debug === true) { + console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + }; + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + const track_aliases = (qb,table) => { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (let i in table) { + const t = table[i]; + track_aliases(qb,t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return track_aliases(qb,table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); + + // Store the alias, if it doesn't already exist + if (qb.aliased_tables.indexOf(alias) == -1) { + qb.aliased_tables.push(alias); + } + } + }; + + const create_aliases_from_table = item => { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + }; + + const escape_identifiers = (item = '*') => { + if (item === '*') { + return item; + } + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + for (let i in item) { + item[i] = escape_identifiers(item[i]); + } + return item; + } + else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + return item; + } + + let str; + if (item.indexOf('.' + '*') !== -1) { + str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); + } + else { + str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); + } + + // remove duplicates if the user already included the escape + return str.replace(/[`]+/g,'`'); + }; + + const protect_identifiers = (qb,item,protect_identifiers) => { + if (item === '') return item; + + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + const escaped_array = {}; + + for (k in item) { + const v = item[k]; + escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); + } + + return escaped_array; + } + + // Convert tabs or multiple spaces into single spaces + item = item.replace(/\s+/g, ' '); + + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { + const has_alias = item.lastIndexOf(')'); + let alias; + if (has_alias >= 0) { + alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); + alias = escape_identifiers(alias); + if (alias != '') + alias = ' AS ' + alias; + item = item.substr(0,has_alias + 1); + } else { + alias = ''; + } + + return item + alias; + } + + let alias = ''; + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (/\sAS\s/ig.test(item)) { + const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); + alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + else if (item.indexOf(' ') !== -1) { + const alias_index = item.indexOf(' '); + + alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + const parts = item.split('.'); + const first_seg = parts[0].trim().replace(/`/g,''); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (qb.aliased_tables.indexOf(first_seg) !== -1) { + if (protect_identifiers === true) { + for (let key in parts) { + const val = parts[key]; + if (val !== '*') { + parts[key] = escape_identifiers(val); + } + } + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + } + if (protect_identifiers === true) { + item = escape_identifiers(item); + } + + return item + alias; + }; + + const has_operator = function (str) { + if (typeof str === 'string' && str.length > 0) { + const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); + if (!match) { + return false; + } + } + return true; + }; + + const qb_escape = (qb,str) => { + const mysql = require('mysql'); + + if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { + str *= 1; + } else { + str = mysql.escape(str); + } + + return str; + }; + + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + const build_where_clause = qb => { + let sql = ''; + if (qb.where_array.length > 0) { + sql += " WHERE "; + } + sql += qb.where_array.join(" "); + return sql; + }; + + const build_from_clause = qb => { + let sql = ''; + if (qb.from_array.length > 0) { + sql += " FROM "; + } else { + throw new Error("You have not provided any tables, views, or store procedures for this query!!"); + } + sql += qb.from_array.join(', '); + return sql; + }; + + const build_join_string = qb => { + let sql = ''; + sql += qb.join_array.join(' '); + if (sql.length > 0) sql = ' ' + sql; + return sql; + }; + + const build_group_by_clause = qb => { + if (qb.group_by_array.length <= 0) return ''; + + let sql = ' GROUP BY '; + sql += qb.group_by_array.join(', '); + return sql; + }; + + const build_having_clause = qb => { + if (qb.having_array.length <= 0) return ''; + + let sql = ' HAVING '; + sql += qb.having_array.join(' '); + return sql; + }; + + const build_order_by_clause = qb => { + if (qb.order_by_array.length <= 0) return ''; + + let sql = ' ORDER BY '; + sql += qb.order_by_array.join(', '); + + return sql; + }; + + const build_limit_clause = (sql, limit, offset) => { + if (!limit) return sql; + + sql += ' '; + + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + }; + + const compile_select = qb => { + const distinct_clause = qb.distinct_clause[0] || ''; + let sql = 'SELECT ' + distinct_clause; + if (qb.select_array.length === 0) { + sql += '*'; + } else { + sql += qb.select_array.join(', '); + } + + sql += build_from_clause(qb) + + build_join_string(qb) + + build_where_clause(qb) + + build_group_by_clause(qb) + + build_having_clause(qb) + + build_order_by_clause(qb); + + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; + + sql = build_limit_clause(sql,limit_to,offset_val); + return sql; + }; + + const compile_delete = qb => { + if (qb.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } + + qb.from_array = qb.from_array.slice(0,1); + + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; + + const sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); + return build_limit_clause(sql,limit_to,offset_val); + }; + + const compile_update = qb => { + const valstr = []; + for (let i in qb.set_array) { + const key = Object.keys(qb.set_array[i])[0]; + const val = qb.set_array[i][key]; + valstr.push(key + ' = ' + val); + } + + if (qb.from_array.length !== 1) { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + const table = qb.from_array.toString(); + + const limit_to = qb.limit_to[0] || false; + const offset_val = qb.offset_val[0] || false; + + let sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); + sql += build_where_clause(qb); + sql += build_order_by_clause(qb); + return build_limit_clause(sql, limit_to, offset_val); + }; + + const compile_insert = (qb, ignore, suffix='') => { + const keys = []; + const values = []; + + for (let i in qb.set_array) { + const key = Object.keys(qb.set_array[i])[0]; + const val = qb.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + + if (qb.from_array.length === 1) { + const table = qb.from_array.toString(); + } else { + if (qb.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; + } + + return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + }; + + // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// + return { + where_array: [], + where_in_array: [], + from_array: [], + join_array: [], + select_array: [], + set_array: [], // has to be array to work as reference + order_by_array: [], + group_by_array: [], + having_array: [], + limit_to: [], // has to be array to work as reference + offset_val: [], // has to be array to work as reference + join_clause: [], + last_query_string: [], // has to be array to work as reference + distinct_clause: [], // has to be array to work as reference + aliased_tables: [], + + reset_query: function(new_last_query,debug) { + clear_array(this.where_array,debug); + clear_array(this.where_in_array); + clear_array(this.from_array); + clear_array(this.join_array); + clear_array(this.select_array); + clear_array(this.set_array); + clear_array(this.order_by_array); + clear_array(this.group_by_array); + clear_array(this.having_array); + clear_array(this.limit_to); + clear_array(this.offset_val); + clear_array(this.join_clause); + clear_array(this.distinct_clause); + clear_array(this.aliased_tables); + + clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + }, + + where: function(key, value = null, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + }, + + or_where: function(key, value=null, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + }, + + _where: function(key, value=null, type='AND ', escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { + const filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + const that = this; + const parse_statement = (statement,joiner) => { + const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || /AND/i.test(joiner)) { + that.where(key, value, true); + } else { + that.or_where(key, value, true); + } + } + }; + parse_statement(filters.shift(),null); + while (filters.length > 0) { + const joiner = filters.shift(); + const statement = filters.shift(); + parse_statement(statement, joiner); + } + return this; + } + } + + const key_array = {}; + key_array[key] = value; + key = key_array; + } + + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } + + for (let k in key) { + let v = key[k]; + + if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { + return this._where_in(k,v,false,type,escape); + } + + const prefix = (this.where_array.length == 0 ? '' : type); + + if (v === null && !has_operator(k)) { + k += ' IS NULL'; + } + + if (v !== null) { + if (escape === true) { + k = protect_identifiers(this,k,escape); + v = ' ' + qb_escape(this,v); + } + + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + qb_escape(this,v); + } + + if (!has_operator(k)) { + k += ' ='; + } + } + else { + k = protect_identifiers(this,k,escape); + } + + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } + + return this; + }, + + where_in: function(key, values, escape) { + return this._where_in(key,values,false,'AND ', escape); + }, + + or_where_in: function(key, values, escape) { + return this._where_in(key,values,false,'OR ', escape); + }, + + where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'AND ', escape); + }, + + or_where_not_in: function(key, values, escape) { + return this._where_in(key,values,true,'OR ', escape); + }, + + _where_in: function(key='', values=[], not, type='AND ', escape) { + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } + + // Values must be an array... + if (!Array.isArray(values)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); + } + } + + for (let i in values) { + this.where_in_array.push(qb_escape(this,values[i])); + } + + const prefix = (this.where_array.length == 0 ? '' : type); + const where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); + + // reset the array for multiple calls + clear_array(this.where_in_array); + return this; + }, + + like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + }, + + not_like: function(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + }, + + or_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + }, + + or_not_like: function(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + }, + + _like: function(field, match, type='AND ', side='both', not='') { + match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); + + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if (!/^(string|object)$/.test(typeof field)) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } + + if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); + } + + const field_array = {}; + field_array[field] = match; + field = field_array; + } + + for(let k in field) { + let like_statement; + const v = field[k]; + k = protect_identifiers(this,k.trim()); + + // Make sure value is only string, number, or boolean + if (!/^(string|number|boolean)$/.test(typeof v)) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); + } + + this._where(like_statement,null,type,false); + } + + return this; + }, + + from: function(from_param) { + if (!Array.isArray(from_param)) { + from_param = [from_param]; + } + for (let i in from_param) { + let val = from_param[i]; + + if (typeof val !== 'string' || val.trim() === '') continue; + + if (val.indexOf(',') !== -1) { + const objects = val.split(','); + for (let j in objects) { + const v = objects[j].trim(); + + track_aliases(this,v); + + this.from_array.push(protect_identifiers(this,v,true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + track_aliases(this,val); + + this.from_array.push(protect_identifiers(this,val,true)); + } + } + + return this; + }, + + join: function(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } + + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + const valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } + + direction += ' '; + } + + track_aliases(this,table); + + // Split multiple conditions + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; + if (escape === true && m) { + while(k = regex.exec(relation)) { + matches.push(k); + } + + let new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); + new_relation += matches[j][0]; + } + + relation = ' ON ' + new_relation; + } + + // Split apart the condition and protect the identifiers + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); + } + else if (!has_operator(relation)) { + relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; + } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } + else { + relation = ' '; + } + + // Do we want to escape the table name? + if (escape === true) { + table = protect_identifiers(this,table,true); + } + + const join = direction + 'JOIN ' + table + relation; + + this.join_array.push(join); + return this; + }, + + select: function(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); + } + } + else if (Array.isArray(select)) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); + } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + + if (typeof escape !== 'boolean') escape = true; + + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + // Prevent it from trying to parse select statements with functions and if statements + if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + + // Identify individual statements within select string + let m, open_paren_index, inner_parenthesis; + const reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; + } + } + } + } + else { + select = [select]; + } + } + } + + for (let i in select) { + const val = select[i].trim(); + + if (val !== '') { + this.select_array.push(protect_identifiers(this,val,escape)); + } + } + return this; + }, + + select_min: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MIN'); + }, + + select_max: function(select,alias) { + return this._min_max_avg_sum(select,alias,'MAX'); + }, + + select_avg: function(select,alias) { + return this._min_max_avg_sum(select,alias,'AVG'); + }, + + select_sum: function(select,alias) { + return this._min_max_avg_sum(select,alias,'SUM'); + }, + + _min_max_avg_sum: function(select='',alias='',type='MAX') { + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } + + type = type.toUpperCase(); + + if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } + + if (alias == '') { + alias = create_aliases_from_table(select.trim()); + } + + const sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + + this.select_array.push(sql); + + return this; + }, + + distinct: function(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + + if (do_distinct) { + clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + clear_array(this.distinct_clause); + } + return this; + }, + + group_by: function(by) { + if (typeof by === 'string') { + by = by.trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + by = by.split(','); + } + + if (!Array.isArray(by)) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } + + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + + for (let key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); + } + + const val = by[key].trim(); + + if (val !== '') { + this.group_by_array.push(protect_identifiers(this,val)); + } + } + return this; + }, + + having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + }, + + or_having: function(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + }, + + _having: function(key, value, type='AND ', escape) { + + let m; + let key_array = {}; + const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + const key_is_array = Array.isArray(key); + + if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = extract_having_parts(key,key_array); + } + } + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (let i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); + } + else { + key_array = extract_having_parts(key[i],key_array); + } + } + key = key_array; + } + } + } + else { + throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); + } + + + for (let k in key) { + let v = key[k]; + const prefix = (this.having_array.length == 0 ? '' : type); + + if (escape === true) { + k = protect_identifiers(this,k); + } + + if (v === null) { + k += ' IS'; + } + else if (!has_operator(k)) { + k += ' ='; + } + + if (v != '') { + v = ' ' + qb_escape(this,v); + } + + this.having_array.push(prefix + k + v); + } + + return this; + }, + + order_by: function(orderby, direction) { + let m; + const rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(rand_word); + return this; + } + + // normalize orderby to be an array of items + if (!Array.isArray(orderby)) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { + this.order_by_array.push(rand_word); + return this; + } + else { + throw new Error("No field provided to order by!"); + } + } + + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } + + for (let i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); + + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.order_by_array.push(rand_word); + return this; + } + orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (/^(ASC|DESC)$/i.test(direction) || direction === '') { + orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + } else { + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); + } + } + + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } + + return this; + }, + + limit: function(limit, offset) { + clear_array(this.limit_to); + this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); + + if (offset !== undefined) { + return this.offset(offset); + } + + return this; + }, + + offset: function(offset) { + clear_array(this.offset_val); + this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); + return this; + }, + + set: function(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) { + throw new Error("set(): Invalid field name provided!"); + } + + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); + + const key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); + } + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); + } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } + + + // Add each key:value pair to the set_array + for (let i in key) { + let v = key[i]; + if (typeof v === 'undefined') continue; + + if (v instanceof Date) v = v.toString(); + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + + // Escape the key to be DRY + const escaped_key = protect_identifiers(this,i,escape); + + // Build a temporary object with escaped key and val + const temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = qb_escape(this,v); + } + + // Determine if this key has already been set + let found_index = null; + for (let j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; + } + } + + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); + } + } + + //console.dir(this.set_array); + + return this; + }, + + insert: function(table, set, ignore, suffix) { + table = table || '' + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } + + if (Array.isArray(set)) { + return this.insert_batch(table, set, ignore, suffix); + } + + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } + } + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table === '') { + if (this.from_array.length == 0) { + throw new Error('insert(): No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + return compile_insert(this, ignore, suffix); + }, + + insert_ignore: function(table, set, suffix) { + return this.insert(table, set, true, suffix); + }, + + insert_batch: function(table,set=null,ignore,suffix) { + + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + if (!Array.isArray(set)) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (let key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (let i in row) { + const v = row[i]; + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } + } + } + + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } + + const map = []; + const columns = []; + + // Obtain all the column names + for (let key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(protect_identifiers(this,key)); + } + } + } + + for (let i = 0; i < set.length; i++) { + (i => { + const row = []; + for (let key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(qb_escape(this,set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(', ') + ')'); + })(i); + } + + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; + }, + + get: function(table) { + if (typeof table === 'string' || Array.isArray(table)) { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); + } + } + return compile_select(this); + }, + + get_where: function(table=null, where=null) { + + // Check if table is either a string or array + if (typeof table !== 'string' && !Array.isArray(table)) + throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); + + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); + + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (let v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; + } + } + } + + this.from(table); + + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + + this.where(where); + + return compile_select(this); + }, + + count: function(table) { + if (typeof table === 'string') { + this.from(table); + } + + const sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') + + build_from_clause(this) + + build_join_string(this) + + build_where_clause(this); + + return sql; + }, + + update: function(table, set, where=null) { + + table = table || ''; + set = set || null; + + // Send to batch_update if the data param is an array + if (Array.isArray(set)) { + let index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; + } + } + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + } + } + + // If set is a number, boolean, a non-empty string, or regex, fail + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } + + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } + + return compile_update(this); + }, + + update_batch: function(table='', set=null, index=null, where=null) { + + + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } + + // Check to make sure we have a dataset + if (!Array.isArray(set)) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } + + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } + + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (let i in set) { + const clean = {}; + const row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + const keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (let j in row) { + clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); + } + this.set_array.push(clean); + } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } + + // Verify there is a table in the from_array + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); + } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } + + table = this.from_array.toString(); + + + // Limit to 100 rows per batch + const batches = []; + for (let i = 0, total = this.set_array.length; i < total; i += 100) { + const when_then = {}; + const ids = []; + const where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); + const chunk = this.set_array.slice(i,100); + + // Escape the index + index = protect_identifiers(this, index); + + for (let j in chunk) { + ids.push(chunk[j][index]); + + const keys = Object.keys(chunk[j]); + for (let k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; + } + when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + } + } + } + + // Build the actual SQL statement + let sql = 'UPDATE (' + table + ') SET '; + let cases = ''; + + for (let l in when_then) { + cases += l + ' = CASE '; + + for (let m in when_then[l]) { + cases += when_then[l][m]; + } + + cases += 'ELSE ' + l + ' END, '; + } + + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; + + // Add query to batch + batches.push(sql); + } + + return batches; + }, + + delete: function(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + } + else { + this.where(where); + } + } + + return compile_delete(this); + }, + + get_compiled_select: function(table) { + if (typeof table !== 'undefined') { + track_aliases(this,table); + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; + } + } + + return compile_select(this); + }, + + get_compiled_delete: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + + return compile_delete(this); + }, + + get_compiled_update: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_update(this); + }, + + get_compiled_insert: function(table) { + if (typeof table !== 'function') { + track_aliases(this,table); + this.from(table); + } + return compile_insert(this); + }, + + compile_select: function(table) { + return this.get_compiled_select(table); + }, + + compile_delete: function(table) { + return this.get_compiled_delete(table); + }, + + compile_update: function(table) { + return this.get_compiled_update(table); + }, + + compile_insert: function(table) { + return this.get_compiled_insert(table); + }, + + last_query: function() { + return this.last_query_string[0] || ''; + }, + + escape: function(val) { + return qb_escape(this, val); + }, + + empty_table: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } + + return "DELETE FROM " + this.from_array[0]; + }, + + truncate: function(table) { + if (typeof table === 'string' && table.trim().length > 0) { + clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } + + return "TRUNCATE " + this.from_array[0]; + }, + } +}; + +exports.QueryBuilder = QueryBuilder; diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js new file mode 100755 index 0000000..a88715a --- /dev/null +++ b/drivers/mssql/query_exec.js @@ -0,0 +1,208 @@ +// **************************************************************************** +// QueryBuilder "Query Execution" methods. +// ----- +// @param Object qb The QueryBuilder object +// @param Object adapter The connection adapter object +// **************************************************************************** +const QueryExec = function (qb, conn) { + + const exec = (sql, callback) => { + if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { + conn.query(sql, (err, results) => { + // Standardize some important properties + if (!err && results.length > 0) { + + // Insert ID + if (results.hasOwnProperty('insertId')) { + results.insert_id = results.insertId; + } + + // Affected Rows + if (results.hasOwnProperty('affectedRows')) { + results.affected_rows = results.affectedRows; + } + + // Changed Rows + if (results.hasOwnProperty('changedRows')) { + results.changed_rows = results.changedRows; + } + } + + callback(err, results); + }); + } else { + throw new Error("No connection object supplied to the Query Exec Library!"); + } + }; + + return { + query: function(sql, callback) { + exec(sql, callback); + }, + + count: function(table, callback) { + if (typeof table === 'function' && typeof callback !== 'function') { + table = null; + callback = table; + } + + const sql = qb.count(table); + qb.reset_query(sql); + exec(sql, (err, row) => { + if (!err) { + //console.dir(row[0].numrows); + callback(err, row[0].numrows); + } + else { + callback(err, row); + } + }); + }, + + get: function(table, callback,conn) { + // The table parameter is optional, it could be the callback... + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + } + else if (typeof table === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your 'get' call!"); + } + + const sql = qb.get(table); + qb.reset_query(sql); + exec(sql, callback); + }, + + get_where: function(table,where, callback) { + if (typeof table !== 'string' && !Array.isArray(table)) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } + const sql = qb.get_where(table,where); + qb.reset_query(sql); + exec(sql, callback); + }, + + insert: function(table,set, callback,ignore,suffix) { + const sql = qb.insert(table,set,ignore,suffix); + qb.reset_query(sql); + exec(sql, callback); + }, + + insert_ignore: function(table,set,on_dupe, callback) { + if (typeof on_dupe === 'function') { + callback = on_dupe; + on_dupe = null; + } + const sql = qb.insert_ignore(table,set,on_dupe); + qb.reset_query(sql); + exec(sql, callback); + }, + + insert_batch: function(table,set, callback) { + const sql = qb.insert_batch(table,set); + qb.reset_query(sql); + exec(sql, callback); + }, + + update: function(table,set,where, callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sql = qb.update(table,set,where); + qb.reset_query(sql); + exec(sql, callback); + }, + + // TODO: Write this complicated-ass function + update_batch: function(table,set,index,where, callback) { + // The where parameter is optional, it could be the callback... + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = null; + } + else if (typeof where === 'undefined' && typeof callback !== 'function') { + throw new Error("No callback function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sqls = qb.update_batch(table,set,index,where); + const results = null; + const errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + const sql = sqls.shift(); + qb.reset_query(sql); + + exec(sql, (err, res) => { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } + } else { + errors.push(err); + } + + if (sqls.length > 0) { + setTimeout(next_batch,0); + } else { + return callback(errors, results); + } + }); + })(); + }, + + delete: function(table, where, callback) { + if (typeof where === 'function' && typeof callback !== 'function') { + callback = where; + where = undefined; + } + + if (typeof table === 'function' && typeof callback !== 'function') { + callback = table; + table = undefined; + where = undefined; + } + + if (typeof callback !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } + + const sql = qb.delete(table, where); + + qb.reset_query(sql); + exec(sql, callback); + }, + + empty_table: function(table, callback) { + const sql = qb.empty_table(table, callback); + qb.reset_query(sql); + exec(sql, callback); + }, + + truncate: function(table, callback) { + const sql = qb.truncate(table, callback); + qb.reset_query(sql); + exec(sql, callback); + }, + } +} + +exports.QueryExec = QueryExec; diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js index 026a3a2..f0f9a01 100755 --- a/drivers/mysql/adapters.js +++ b/drivers/mysql/adapters.js @@ -1,27 +1,36 @@ -const Adapters = function(nqb) { - // Load MySQL Driver - const mysql = require('mysql'); +// Load MySQL Driver +const mysql = require('mysql'); + +class Adapter { + constructor(nqb) { + // Verify that an instance of Node QueryBuilder was passed in + if (!nqb || typeof nqb !== 'object') { + throw new Error("No/Invalid QueryBuilder super object instance supplied."); + } - // Verify setting property exists - if (!nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } + // Store QB super object as class prop + this.nqb = Object.assign({}, nqb); - // Enable debugging if necessary - this.debugging = false; - if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { - this.debugging = true; - delete nqb.settings.qb_debug; - } + // Verify setting property exists + if (!this.nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } - // Verify that required fields are provided... - if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; - if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + // Enable debugging if necessary + this.debugging = false; + if (this.nqb.settings.hasOwnProperty('qb_debug') && this.nqb.settings.qb_debug === true) { + this.debugging = true; + delete this.nqb.settings.qb_debug; + } + + // Verify that required fields are provided... + if (Object.keys(this.nqb.settings).length === 0) throw new Error("No connection information provided!"); + if (!this.nqb.settings.hasOwnProperty('host')) this.nqb.settings.host = 'localhost'; + if (!this.nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!this.nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); - this.connection_settings = {}; - const that = this; + this.map_connection_settings(); + } // **************************************************************************** // Map generic NQB connection settings to node-mysql's format @@ -29,163 +38,142 @@ const Adapters = function(nqb) { // NOTE: MySQL connection settings names are the same as Node Querybuilder, // it's just good practice to go ahead and do this in case things change. // **************************************************************************** - const map_connection_settings = () => { + map_connection_settings() { + const nqb_settings = JSON.parse(JSON.stringify(this.nqb.settings)); + this.connection_settings = { - host: nqb.settings.host, - user: nqb.settings.user, - password: nqb.settings.password - } - if (nqb.settings.hasOwnProperty('database')) { - this.connection_settings.database = nqb.settings.database; - delete nqb.settings.database + host: nqb_settings.host, + user: nqb_settings.user, + password: nqb_settings.password, + }; + + if (nqb_settings.hasOwnProperty('database')) { + this.connection_settings.database = nqb_settings.database; + delete nqb_settings.database; } - if (nqb.settings.hasOwnProperty('port')) { - this.connection_settings.port = nqb.settings.port; - delete nqb.settings.port + if (nqb_settings.hasOwnProperty('port')) { + this.connection_settings.port = nqb_settings.port; + delete nqb_settings.port; } // Remove mapped settings: - delete nqb.settings.host - delete nqb.settings.user - delete nqb.settings.password + delete nqb_settings.host; + delete nqb_settings.user; + delete nqb_settings.password; // Merge any driver-specific settings into connection settings - this.connection_settings = Object.assign(this.connection_settings, nqb.settings); + this.connection_settings = Object.assign(this.connection_settings, nqb_settings); } - map_connection_settings(); - - // **************************************************************************** // Try to load the driver's query builder library and modify QueryBuilder object // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object // **************************************************************************** - const get_query_builder = () => { + get_query_builder() { try { return require('./query_builder.js').QueryBuilder(); } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); + throw new Error("Couldn't load the QueryBuilder library for " + this.nqb.driver + ": " + e); } - }; + } // **************************************************************************** // Get the the driver's QueryExec object so that queries can actually be // executed by this library. // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object // **************************************************************************** - const get_query_exec = (qb, conn) => { + get_query_exec(qb, conn) { try { return require('./query_exec.js').QueryExec(qb, conn); } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); + throw new Error("Couldn't load the QueryExec library for " + this.nqb.driver + ": " + e); } - }; + } +} - // **************************************************************************** - // Generic Single Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Adapter = function(settings) { - let pool, connection; + +// ----------------------------------------------------------------------------------------------------------------------------- + + +class Single extends Adapter { + constructor(nqb, settings) { + super(nqb); + + // Set defaults + this.pool = null; + this._connection = null; // If the Pool object is instatiating this Adapter, use it's connection if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; + this.pool = settings.pool.pool; + this._connection = settings.pool.connection; } // Otherwise, let's create a new connection else { - connection = new mysql.createConnection(that.connection_settings); + this._connection = new mysql.createConnection(this.connection_settings); } - const qb = get_query_builder(); - const qe = get_query_exec(qb, connection); + if (!this._connection) throw new Error("No connection could be established!"); + + this.qb = this.get_query_builder(); + this.qe = this.get_query_exec(this.qb, this._connection); + + const self = this; - const adapter = Object.assign({ + return Object.assign({ connection_settings: function() { - return that.connection_settings; + return self.connection_settings; }, connect: function(callback) { - return connection.connect(callback); + return self._connection.connect(callback); }, connection: function() { - return connection; + return self._connection; }, escape: function(str) { - return connection.escape(str); + return self._connection.escape(str); }, escape_id: function(str) { - return connection.escapeId(str); + return self._connection.escapeId(str); }, disconnect: function(callback) { - return connection.end(callback); + return self._connection.end(callback); }, release: function() { - if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - pool.releaseConnection(connection); + if (!self.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + self.pool.releaseConnection(self._connection); } - }, qb, qe); + }, this.qb, this.qe); + } +} - return adapter; - }; - // **************************************************************************** - // Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Pool = function() { - // Return Pool Object - const return_pool = () => { - return { - pool: function() { - return nqb.pool; - }, - get_connection: function(callback) { - if (null === nqb.pool) { - const error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - - nqb.pool.getConnection((err, connection) => { - if (err) throw err; - const adapter = new Adapter({ - pool: { - pool: nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - nqb.pool.end(callback); - } - } - }; +// ----------------------------------------------------------------------------------------------------------------------------- + + +class Pool extends Adapter { + constructor(nqb) { + super(nqb); // Create pool for node-querybuild object if it doesn't already have one. - if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { + if (!this.nqb.hasOwnProperty('pool') || this.nqb.pool.length === 0) { // Create connection Pool - nqb.pool = mysql.createPool(that.connection_settings); + this.nqb.pool = mysql.createPool(this.connection_settings); // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) - if (that.debugging === true) { - nqb.pool.getConnection((err, connection) => { + if (this.debugging === true) { + this.nqb.pool.getConnection((err, connection) => { connection.query('SELECT 1 + 1 AS solution', (err) => { connection.release(); if (err) { @@ -197,37 +185,48 @@ const Adapters = function(nqb) { }); } } - return return_pool(); - }; - // **************************************************************************** - // Clustered Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Cluster = () => { + const self = this; - }; + return { + pool: function() { + return self.nqb.pool; + }, + get_connection: function(callback) { + if (null === self.nqb.pool) { + const error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } - // **************************************************************************** - // Basic Description - // ----- - // @param - // @return - // **************************************************************************** - const determine_adapter = () => { - switch(nqb.connection_type) { - case 'cluster': - return new Cluster(); - case 'pool': - return new Pool(); - case 'single': - default: - return new Adapter({}); + self.nqb.pool.getConnection((err, connection) => { + if (err) throw err; + const adapter = new Single(self.nqb, { + pool: { + pool: self.nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + self.nqb.pool.end(callback); + } } } +} + - return determine_adapter(); -}; +// ----------------------------------------------------------------------------------------------------------------------------- + + +class Cluster extends Adapter { + constructor(nqb) { + super(nqb); + return {}; + } +} -exports.Adapters = Adapters; +exports.Adapters = {Single,Pool,Cluster}; diff --git a/drivers/mysql/adapters_old.js b/drivers/mysql/adapters_old.js new file mode 100755 index 0000000..026a3a2 --- /dev/null +++ b/drivers/mysql/adapters_old.js @@ -0,0 +1,233 @@ +const Adapters = function(nqb) { + // Load MySQL Driver + const mysql = require('mysql'); + + // Verify setting property exists + if (!nqb.hasOwnProperty('settings')) { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + // Enable debugging if necessary + this.debugging = false; + if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { + this.debugging = true; + delete nqb.settings.qb_debug; + } + + // Verify that required fields are provided... + if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); + if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; + if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); + //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + + this.connection_settings = {}; + const that = this; + + // **************************************************************************** + // Map generic NQB connection settings to node-mysql's format + // ---- + // NOTE: MySQL connection settings names are the same as Node Querybuilder, + // it's just good practice to go ahead and do this in case things change. + // **************************************************************************** + const map_connection_settings = () => { + this.connection_settings = { + host: nqb.settings.host, + user: nqb.settings.user, + password: nqb.settings.password + } + if (nqb.settings.hasOwnProperty('database')) { + this.connection_settings.database = nqb.settings.database; + delete nqb.settings.database + } + if (nqb.settings.hasOwnProperty('port')) { + this.connection_settings.port = nqb.settings.port; + delete nqb.settings.port + } + + // Remove mapped settings: + delete nqb.settings.host + delete nqb.settings.user + delete nqb.settings.password + + // Merge any driver-specific settings into connection settings + this.connection_settings = Object.assign(this.connection_settings, nqb.settings); + } + + map_connection_settings(); + + + // **************************************************************************** + // Try to load the driver's query builder library and modify QueryBuilder object + // ----- + // @param Object qb The QueryBuilder object + // @return Object QueryBuilder object + // **************************************************************************** + const get_query_builder = () => { + try { + return require('./query_builder.js').QueryBuilder(); + } catch(e) { + throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Get the the driver's QueryExec object so that queries can actually be + // executed by this library. + // ----- + // @param Object qb The QueryBuilder object + // @param Object conn The Connnection object + // @return Object QueryExec Object + // **************************************************************************** + const get_query_exec = (qb, conn) => { + try { + return require('./query_exec.js').QueryExec(qb, conn); + } catch(e) { + throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); + } + }; + + // **************************************************************************** + // Generic Single Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Adapter = function(settings) { + let pool, connection; + + // If the Pool object is instatiating this Adapter, use it's connection + if (settings && settings.pool) { + pool = settings.pool.pool; + connection = settings.pool.connection; + } + // Otherwise, let's create a new connection + else { + connection = new mysql.createConnection(that.connection_settings); + } + + const qb = get_query_builder(); + const qe = get_query_exec(qb, connection); + + const adapter = Object.assign({ + connection_settings: function() { + return that.connection_settings; + }, + + connect: function(callback) { + return connection.connect(callback); + }, + + connection: function() { + return connection; + }, + + escape: function(str) { + return connection.escape(str); + }, + + escape_id: function(str) { + return connection.escapeId(str); + }, + + disconnect: function(callback) { + return connection.end(callback); + }, + + release: function() { + if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + pool.releaseConnection(connection); + } + }, qb, qe); + + return adapter; + }; + + // **************************************************************************** + // Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Pool = function() { + // Return Pool Object + const return_pool = () => { + return { + pool: function() { + return nqb.pool; + }, + get_connection: function(callback) { + if (null === nqb.pool) { + const error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + + nqb.pool.getConnection((err, connection) => { + if (err) throw err; + const adapter = new Adapter({ + pool: { + pool: nqb.pool, + connection: connection + } + }); + + callback(adapter); + }); + }, + disconnect: function(callback) { + nqb.pool.end(callback); + } + } + }; + + // Create pool for node-querybuild object if it doesn't already have one. + if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { + // Create connection Pool + nqb.pool = mysql.createPool(that.connection_settings); + + // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) + if (that.debugging === true) { + nqb.pool.getConnection((err, connection) => { + connection.query('SELECT 1 + 1 AS solution', (err) => { + connection.release(); + if (err) { + console.error(err); + } else { + console.log('mysql connection pool created'); + } + }); + }); + } + } + return return_pool(); + }; + + // **************************************************************************** + // Clustered Connection Pool Adapter + // ----- + // @return Object Adapter object + // **************************************************************************** + const Cluster = () => { + + }; + + // **************************************************************************** + // Basic Description + // ----- + // @param + // @return + // **************************************************************************** + const determine_adapter = () => { + switch(nqb.connection_type) { + case 'cluster': + return new Cluster(); + case 'pool': + return new Pool(); + case 'single': + default: + return new Adapter({}); + } + } + + return determine_adapter(); +}; + +exports.Adapters = Adapters; diff --git a/index.js b/index.js index 7a7793c..cce7f24 100755 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ /** * QueryBuilder for Node.js - * (C) Kyle Farris 2014-2015 + * (C) Kyle Farris 2014-2018 * kyle@chomponllc.com * * A generic Query Builder for any SQL or NOSQL database adapter. @@ -38,94 +38,102 @@ * OTHER DEALINGS IN THE SOFTWARE. * **/ -const lo_assign = require('lodash.assign'); -const QueryBuilder = (settings,driver,type) => { +class QueryBuilder { + constructor(settings, driver, type) { + this.settings = (settings ? Object.assign({}, settings) : {}); + this.driver = driver || 'mysql'; + this.connection_type = type || 'single'; + this.drivers = require('./drivers/drivers.json'); + this.driver_version = 'default'; + this.driver_info = null; + this.pool = []; - this.settings = (settings ? lo_assign({}, settings) : {}); - this.driver = driver || 'mysql'; - this.connection_type = type || 'single'; - this.drivers = require('./drivers/drivers.json'); - this.driver_version = 'default'; - this.driver_info = null; - this.pool = []; + this.get_driver_info(); + this.get_connection_type(); + return this.get_adapter(); + } - // **************************************************************************** - // Get information about the driver the user wants to use and modify QB object - // ----- - // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object - // **************************************************************************** - const get_driver_info = qb => { - // A driver must be specified - if (typeof this.driver !== 'string') { - throw new Error("No database driver specified!"); - } + // **************************************************************************** + // Get information about the driver the user wants to use and modify QB object + // ----- + // @return Object Modified QueryBuilder object + // **************************************************************************** + get_driver_info() { + // A driver must be specified + if (typeof this.driver !== 'string') { + throw new Error("No database driver specified!"); + } - qb.driver = qb.driver.toLowerCase(); + this.driver = this.driver.toLowerCase(); - // Verify that the driver is one we fundamentally support - if (Object.keys(qb.drivers).indexOf(qb.driver) === -1) { - throw new Error("Invalid driver specified!"); - } + // Verify that the driver is one we fundamentally support + if (Object.keys(this.drivers).indexOf(this.driver) === -1) { + throw new Error("Invalid driver specified!"); + } - // Determine version of driver to use - if (qb.settings.hasOwnProperty('version') && /^(string|number)$/i.test(typeof qb.settings.version)) { - qb.driver_version = qb.settings.version; - delete qb.settings.version; - } + // Determine version of driver to use + if (this.settings.hasOwnProperty('version') && /^(string|number)$/i.test(typeof this.settings.version)) { + this.driver_version = this.settings.version; + delete this.settings.version; + } - // Retrieve info about driver if available, error if not - if (qb.drivers[qb.driver].versions.hasOwnProperty(qb.driver_version)) { - if (qb.drivers[qb.driver].versions[qb.driver_version].hasOwnProperty('version')) { - qb.driver_info = qb.drivers[qb.driver].versions[qb.drivers[qb.driver].versions[qb.driver_version].version]; - } else { - qb.driver_info = qb.drivers[qb.driver].versions[qb.driver_version]; - } - } else { - throw new Error(qb.driver_version + " is not a version of the " + qb.driver + " driver that this library specifically supports. Try being more generic."); - } + // Retrieve info about driver if available, error if not + if (this.drivers[this.driver].versions.hasOwnProperty(this.driver_version)) { + if (this.drivers[this.driver].versions[this.driver_version].hasOwnProperty('version')) { + this.driver_info = this.drivers[this.driver].versions[this.drivers[this.driver].versions[this.driver_version].version]; + } else { + this.driver_info = this.drivers[this.driver].versions[this.driver_version]; + } + } else { + throw new Error(`${this.driver_version} is not a version of the ${this.driver} driver that this library specifically supports. Try being more generic.`); + } - // Fail if specified driver is inactive - if (qb.driver_info.active === false) { - const err = (qb.driver_version == 'default' ? 'The default version' : "Version " + qb.driver_version) - + " of the " + qb.driver + " driver you are attempting to load is not currently available!"; - throw new Error(err); - } - }; - get_driver_info(this); + // Fail if specified driver is inactive + if (this.driver_info.active === false) { + const err = (this.driver_version == 'default' ? 'The default version' : "Version " + this.driver_version) + + ` of the ${this.driver} driver you are attempting to load is not currently available!`; + throw new Error(err); + } + } - // **************************************************************************** - // Determine the type of connection (single, pool, cluster, etc...) - // ----- - // @param Object qb The QueryBuilder object - // @return Object Modified QueryBuilder object - // **************************************************************************** - const get_connection_type = qb => { - if (!Object.keys(qb.drivers[qb.driver].connection_types).includes(qb.connection_type)) { - throw new Error("You have specified a invalid database connection method: " + qb.connection_type); - } - if (qb.drivers[qb.driver].connection_types[qb.connection_type] !== true) { - throw new Error("You cannot connect to a " + qb.driver + " database using the " + qb.connection_type + " connection type using this library."); - } - return qb; - } - get_connection_type(this); + // **************************************************************************** + // Determine the type of connection (single, pool, cluster, etc...) + // ----- + // @return Object Modified QueryBuilder object + // **************************************************************************** + get_connection_type() { + if (!Object.keys(this.drivers[this.driver].connection_types).includes(this.connection_type)) { + throw new Error(`You have specified a invalid database connection method: ${this.connection_type}`); + } + if (this.drivers[this.driver].connection_types[this.connection_type] !== true) { + throw new Error(`You cannot connect to a ${this.driver} database using the ${this.connection_type} connection type using this library.`); + } + return this; + } - // **************************************************************************** - // Returns the single, pool, or cluster adapter - // ----- - // @return VOID This method responds asychronously via a callback - // **************************************************************************** - const get_adapter = qb => { - try { - const adapter = require(qb.driver_info.path + 'adapters.js').Adapters(qb); - return adapter; - } catch(e) { - throw new Error("Couldn't load the Connection library for " + qb.driver + "(" + JSON.stringify(qb.settings) + "): " + e); - } - }; + // **************************************************************************** + // Returns the single, pool, or cluster adapter + // ----- + // @return VOID This method responds asychronously via a callback + // **************************************************************************** + get_adapter() { + try { + const {Cluster,Pool,Single} = require(this.driver_info.path + 'adapters.js').Adapters; - return get_adapter(this); -}; + switch (this.connection_type) { + case 'cluster': + return new Cluster(this); + case 'pool': + return new Pool(this); + case 'single': + return new Single(this, {}); + default: + return new Single(this, {}); + } + } catch(e) { + throw new Error(`Couldn't load the Connection library for ${this.driver} (${JSON.stringify(this.settings)}): ${e}`); + } + } +} -exports.QueryBuilder = QueryBuilder; +module.exports = QueryBuilder; diff --git a/package.json b/package.json index 8f89d00..6e9815b 100755 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ ], "dependencies": { "lodash.assign": "^4.0.7", + "mssql": "^4.1.0", "mysql": "^2.10.1" }, "devDependencies": { diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js new file mode 100755 index 0000000..95328f8 --- /dev/null +++ b/test/mssql/tests-adapter.js @@ -0,0 +1,293 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../index.js'); + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +const connection_released = qb => { + const connection = qb.connection(); + expect(connection._pool._freeConnections).to.have.length(0); + qb.release(); + expect(connection._pool._freeConnections).to.have.length(1); +}; + +describe('QueryBuilder() - MS SQL Adapter', () => { + afterEach(done => { + + done(); + }); + + const driver = 'mssql'; + const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '4.1.0', + port: 3306 + }; + + const bad_user = Object.assign({},settings, {user: 'foobar'}); + const bad_host = Object.assign({},settings, {host: 'nonlocalhost'}); + const bad_password = Object.assign({},settings, {password: 'password'}); + const bad_database = Object.assign({},settings, {database: 'bad_mock_db'}); + const bad_port = Object.assign({},settings, {port: 1}); + const bad_version = Object.assign({},settings, {version: 12}); + + it('should exist', () => { + should.exist(QueryBuilder); + }); + it('should be a function', () => { + QueryBuilder.should.be.a('function'); + }); + it('should have all the QueryBuilder methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; + expect(qb).to.include.keys(children); + }); + it('should have all the QueryExec methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; + expect(qb).to.include.keys(children); + }); + it('should have all the miscellaneous methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['last_query','escape','empty_table','truncate']; + expect(qb).to.include.keys(children); + }); + it('should establish a single connection given valid connection credentials', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should be connected').to.not.be.instanceof(Error); + }); + }); + }); + it('should allow us to disconnect from MS SQL', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + should.exist(qb.disconnect); + qb.disconnect.should.be.a('function'); + + qb.disconnect(err => { + const connection = qb.connection(); + + check(done, () => { + expect(err, 'should be diconnected').to.not.be.instanceof(Error); + expect(connection._protocol._ended).to.be.true; + }); + }); + }); + }); + + + it('should fail to establish a single connection given no parameters', () => { + expect(() => new QueryBuilder()).to.throw(Error); + }); + it('should fail to establish a single connection given no connection credentials', () => { + expect(() => new QueryBuilder({},driver)).to.throw(Error); + }); + // it('should fail to establish a single connection given connection credentials with bad user', done => { + // let qb; + // + // try { + // qb = new QueryBuilder(bad_user, driver); + // } catch(e) { + // expect(e, 'should not get a connection').to.be.instanceof(Error); + // return; + // } + // expect(qb, 'should have connect property').to.have.property('connect'); + // qb.connect(err => { + // check(done, () => { + // expect(err, 'should not be connected').to.be.instanceof(Error); + // }); + // }); + // }); + // it('should fail to establish a single connection given connection credentials with bad host', done => { + // + // let qb; + // + // try { + // qb = new QueryBuilder(bad_host, driver); + // } catch(e) { + // expect(e, 'should not get a connection').to.be.instanceof(Error); + // return; + // } + // expect(qb, 'should have connect property').to.have.property('connect'); + // qb.connect(err => { + // check(done, () => { + // expect(err, 'should not be connected').to.be.instanceof(Error); + // }); + // }); + // }); + // it('should fail to establish a single connection given connection credentials with bad database', done => { + // + // let qb; + // + // try { + // qb = new QueryBuilder(bad_database, driver); + // } catch(e) { + // expect(e, 'should not get a connection').to.be.instanceof(Error); + // return; + // } + // expect(qb, 'should have connect property').to.have.property('connect'); + // qb.connect(err => { + // check(done, () => { + // expect(err, 'should not be connected').to.be.instanceof(Error); + // }); + // }); + // }); + // it('should fail to establish a single connection given connection credentials with bad password', done => { + // + // let qb; + // + // try { + // qb = new QueryBuilder(bad_password, driver); + // } catch(e) { + // expect(e, 'should not get a connection').to.be.instanceof(Error); + // return; + // } + // expect(qb, 'should have connect property').to.have.property('connect'); + // qb.connect(err => { + // check(done, () => { + // expect(err, 'should not be connected').to.be.instanceof(Error); + // }); + // }); + // }); + // it('should fail to establish a single connection given connection credentials with bad port', done => { + // let qb; + // try { + // qb = new QueryBuilder(bad_port, driver); + // } catch(e) { + // expect(e, 'should not get a connection').to.be.instanceof(Error); + // return; + // } + // expect(qb, 'should have connect property').to.have.property('connect'); + // qb.connect(err => { + // check(done, () => { + // expect(err, 'should not be connected').to.be.instanceof(Error); + // }); + // }); + // }); + // it('should fail to establish connection if an invalid driver is specified', () => { + // let qb; + // expect(() => new QueryBuilder(settings), 'no driver specified').to.not.throw(Error); + // expect(() => new QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); + // }); + // it('should fail to establish connection if an invalid driver version is specified', () => { + // let qb; + // expect(() => new QueryBuilder( Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); + // expect(() => new QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); + // }); + // + // it('should allow us to retrieve our connection settings for reference', done => { + // const conn_settings = Object.assign({}, settings, {password: undefined}); + // delete conn_settings.version; + // + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.connect(err => { + // check(done, () => { + // should.exist(qb.connection_settings); + // qb.connection_settings.should.be.a('function'); + // const settings = qb.connection_settings(); + // expect(settings).to.be.instanceof(Object); + // expect(settings).to.be.eql(conn_settings); + // qb.disconnect(); + // }); + // }); + // }); + // it('should allow us to escape certain values', done => { + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.connect(err => { + // check(done, () => { + // should.exist(qb.escape); + // qb.escape.should.be.a('function'); + // expect(qb.escape(null)).to.be.eql('NULL'); + // expect(qb.escape('3')).to.be.eql(3); + // expect(qb.escape(false)).to.be.eql(0); + // expect(qb.escape(true)).to.be.eql(1); + // expect(qb.escape(null)).to.be.eql('NULL'); + // qb.disconnect(); + // }); + // }); + // }); + // it('should allow us to escape identifiers the MS SQL way', done => { + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.connect(err => { + // check(done, () => { + // should.exist(qb.escape_id); + // qb.escape_id.should.be.a('function'); + // expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); + // expect(qb.escape_id('`foo`'), 'pre-escaped').to.be.eql('```foo```'); + // expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('`foo`.`bar`'); + // qb.disconnect(); + // }); + // }); + // }); + // it('should allow us to execute a query', done => { + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.connect(err => { + // qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { + // check(done, () => { + // expect(err).to.not.be.instanceof(Error); + // expect(res).to.not.be.empty; + // expect(res).to.have.length(3); + // }); + // }); + // }); + // }); + // it('should not be able to release a non-pooled connection', done => { + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.connect(err => { + // check(done, () => { + // expect(() => qb.release()).to.throw(Error); + // }); + // }); + // }); + // it('should create a connection pool object if asked', () => { + // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + // expect(pool).to.be.instanceof.object; + // expect(pool).to.include.keys(['pool','get_connection','disconnect']); + // pool.pool.should.be.a('function'); + // pool.get_connection.should.be.a('function'); + // pool.disconnect.should.be.a('function'); + // }); + // it('should create a QueryBuilder adapter when getting a connection from the pool', done => { + // const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + // pool.get_connection(qb => { + // check(done, () => { + // expect(qb).to.include.keys(Object.keys(qb2)); + // }); + // }); + // }); + // it('should allow one to release a connection from the pool', done => { + // const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + // pool.get_connection(qb => { + // check(done, () => connection_released(qb)); + // }); + // }); + // it('should allow one use the same connection pool connection for multiple queries', done => { + // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + // + // pool.get_connection(qb => { + // qb.query('select * from `cities` where `city` = "Gainesville"', (err, res) => { + // if (res.length > 0) { + // qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', (err, res) => { + // check(done, () => connection_released(qb)); + // }); + // } else { + // check(done, () => connection_released(qb)); + // } + // }); + // }); + // }); +}); diff --git a/test/mysql/mock_data.sql b/test/mysql/mock_data.sql old mode 100644 new mode 100755 diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 609ee62..538f710 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -1,292 +1,294 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const nqb = require('../../index.js'); +const chai = require('chai'); +chai.Assertion.includeStack = true; +const should = chai.should(); +const expect = chai.expect; +const QueryBuilder = require('../../index.js'); const check = (done, f) => { - try { - f(); - done(); - } catch(e) { - done(e); - } + try { + f(); + done(); + } catch(e) { + done(e); + } }; const connection_released = qb => { - const connection = qb.connection(); - expect(connection._pool._freeConnections).to.have.length(0); - qb.release(); - expect(connection._pool._freeConnections).to.have.length(1); + const connection = qb.connection(); + expect(connection._pool._freeConnections).to.have.length(0); + qb.release(); + expect(connection._pool._freeConnections).to.have.length(1); }; describe('QueryBuilder() - MySQL Adapter', () => { - const on_connect = err => { - if (err) { console.error("Not connected!"); return; } - console.log("connected!"); - }; - const driver = 'mysql'; - const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306 - }; - const bad_user = Object.assign({},settings); bad_user.user = 'foobar'; - const bad_host = Object.assign({},settings); bad_host.host = 'nonlocalhost'; - const bad_password = Object.assign({},settings); bad_password.password = 'password'; - const bad_database = Object.assign({},settings); bad_database.database = 'bad_mock_db'; - const bad_port = Object.assign({},settings); bad_port.port = 1; - const bad_version = Object.assign({},settings); bad_version.version = 12; + const on_connect = err => { + if (err) { console.error("Not connected!"); return; } + console.log("connected!"); + }; + const driver = 'mysql'; + const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306 + }; + const bad_user = Object.assign({},settings, {user: 'foobar'}); + const bad_host = Object.assign({},settings, {host: 'nonlocalhost'}); + const bad_password = Object.assign({},settings, {password: 'password'}); + const bad_database = Object.assign({},settings, {database: 'bad_mock_db'}); + const bad_port = Object.assign({},settings, {port: 1}); + const bad_version = Object.assign({},settings, {version: 12}); - it('should exist', () => { - should.exist(nqb.QueryBuilder); - }); - it('should be a function', () => { - nqb.QueryBuilder.should.be.a('function'); - }); - it('should have all the QueryBuilder methods', () => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; - expect(qb).to.include.keys(children); - }); - it('should have all the QueryExec methods', () => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; - expect(qb).to.include.keys(children); - }); - it('should have all the miscellaneous methods', () => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - const children = ['last_query','escape','empty_table','truncate']; - expect(qb).to.include.keys(children); - }); - it('should establish a single connection given valid connection credentials', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should be connected').to.not.be.instanceof(Error); - }); - }); - }); - it('should allow us to disconnect from MySQL', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - should.exist(qb.disconnect); - qb.disconnect.should.be.a('function'); + it('should exist', () => { + should.exist(QueryBuilder); + }); + it('should be a function/class', () => { + QueryBuilder.should.be.a('function'); + }); + it('should have all the QueryBuilder methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; + expect(qb).to.include.keys(children); + }); + it('should have all the QueryExec methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; + expect(qb).to.include.keys(children); + }); + it('should have all the miscellaneous methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = ['last_query','escape','empty_table','truncate']; + expect(qb).to.include.keys(children); + }); + it('should establish a single connection given valid connection credentials', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should be connected').to.not.be.instanceof(Error); + }); + }); + }); + it('should allow us to disconnect from MySQL', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + should.exist(qb.disconnect); + qb.disconnect.should.be.a('function'); - qb.disconnect(err => { - const connection = qb.connection(); + qb.disconnect(err => { + const connection = qb.connection(); - check(done, () => { - expect(err, 'should be diconnected').to.not.be.instanceof(Error); - expect(connection._protocol._ended).to.be.true; - }); - }); - }); - }); + check(done, () => { + expect(err, 'should be diconnected').to.not.be.instanceof(Error); + expect(connection._protocol._ended).to.be.true; + }); + }); + }); + }); - it('should fail to establish a single connection given no parameters', () => { - expect(() => nqb.QueryBuilder()).to.throw(Error); - }); - it('should fail to establish a single connection given no connection credentials', () => { - expect(() => nqb.QueryBuilder({},driver)).to.throw(Error); - }); - it('should fail to establish a single connection given connection credentials with bad user', done => { + it('should fail to establish a single connection given no parameters', () => { + expect(() => new QueryBuilder()).to.throw(Error); + }); + it('should fail to establish a single connection given no connection credentials', () => { + expect(() => new QueryBuilder({},driver)).to.throw(Error); + }); + it('should fail to establish a single connection given connection credentials with bad user', done => { - let qb; + let qb; - try { - qb = nqb.QueryBuilder(bad_user, driver); - } catch(e) { - expect(e, 'should not get a connection').to.be.instanceof(Error); - return; - } - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should not be connected').to.be.instanceof(Error); - }); - }); - }); - it('should fail to establish a single connection given connection credentials with bad host', done => { + try { + qb = new QueryBuilder(bad_user, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad host', done => { - let qb; + let qb; - try { - qb = nqb.QueryBuilder(bad_host, driver); - } catch(e) { - expect(e, 'should not get a connection').to.be.instanceof(Error); - return; - } - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should not be connected').to.be.instanceof(Error); - }); - }); - }); - it('should fail to establish a single connection given connection credentials with bad database', done => { + try { + qb = new QueryBuilder(bad_host, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad database', done => { - let qb; + let qb; - try { - qb = nqb.QueryBuilder(bad_database, driver); - } catch(e) { - expect(e, 'should not get a connection').to.be.instanceof(Error); - return; - } - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should not be connected').to.be.instanceof(Error); - }); - }); - }); - it('should fail to establish a single connection given connection credentials with bad password', done => { + try { + qb = new QueryBuilder(bad_database, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad password', done => { - let qb; + let qb; - try { - qb = nqb.QueryBuilder(bad_password, driver); - } catch(e) { - expect(e, 'should not get a connection').to.be.instanceof(Error); - return; - } - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should not be connected').to.be.instanceof(Error); - }); - }); - }); - it('should fail to establish a single connection given connection credentials with bad port', done => { - let qb; - try { - qb = nqb.QueryBuilder(bad_port, driver); - } catch(e) { - expect(e, 'should not get a connection').to.be.instanceof(Error); - return; - } - expect(qb, 'should have connect property').to.have.property('connect'); - qb.connect(err => { - check(done, () => { - expect(err, 'should not be connected').to.be.instanceof(Error); - }); - }); - }); - it('should fail to establish connection if an invalid driver is specified', () => { - let qb; - expect(() => nqb.QueryBuilder(settings), 'no driver specified').to.not.throw(Error); - expect(() => nqb.QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); - }); - it('should fail to establish connection if an invalid driver version is specified', () => { - let qb; - expect(() => nqb.QueryBuilder( Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); - expect(() => nqb.QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); - }); + try { + qb = new QueryBuilder(bad_password, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad port', done => { + let qb; + try { + qb = new QueryBuilder(bad_port, driver); + } catch(e) { + expect(e, 'should not get a connection').to.be.instanceof(Error); + return; + } + expect(qb, 'should have connect property').to.have.property('connect'); + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish connection if an invalid driver is specified', () => { + let qb; + expect(() => new QueryBuilder(settings), 'no driver specified').to.not.throw(Error); + expect(() => new QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); + }); + it('should fail to establish connection if an invalid driver version is specified', () => { + let qb; + expect(() => new QueryBuilder( Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); + expect(() => new QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); + }); - it('should allow us to retrieve our connection settings for reference', done => { - const conn_settings = Object.assign({}, settings, {password: undefined}); - delete conn_settings.version; + it('should allow us to retrieve our connection settings for reference', done => { + const conn_settings = Object.assign({}, settings, {password: undefined}); + delete conn_settings.version; - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - check(done, () => { - should.exist(qb.connection_settings); - qb.connection_settings.should.be.a('function'); - const settings = qb.connection_settings(); - expect(settings).to.be.instanceof(Object); - expect(settings).to.be.eql(conn_settings); - qb.disconnect(); - }); - }); - }); - it('should allow us to escape certain values', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - check(done, () => { - should.exist(qb.escape); - qb.escape.should.be.a('function'); - expect(qb.escape(null)).to.be.eql('NULL'); - expect(qb.escape('3')).to.be.eql(3); - expect(qb.escape(false)).to.be.eql(0); - expect(qb.escape(true)).to.be.eql(1); - expect(qb.escape(null)).to.be.eql('NULL'); - qb.disconnect(); - }); - }); - }); - it('should allow us to escape identifiers the MySQL way', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - check(done, () => { - should.exist(qb.escape_id); - qb.escape_id.should.be.a('function'); - expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); - expect(qb.escape_id('`foo`'), 'pre-escaped').to.be.eql('```foo```'); - expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('`foo`.`bar`'); - qb.disconnect(); - }); - }); - }); - it('should allow us to execute a query', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { - check(done, () => { - expect(err).to.not.be.instanceof(Error); - expect(res).to.not.be.empty; - expect(res).to.have.length(3); - }); - }); - }); - }); - it('should not be able to release a non-pooled connection', done => { - const qb = nqb.QueryBuilder(Object.assign({}, settings), driver); - qb.connect(err => { - check(done, () => { - expect(() => qb.release()).to.throw(Error); - }); - }); - }); - it('should create a connection pool object if asked', () => { - const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - expect(pool).to.be.instanceof.object; - expect(pool).to.include.keys(['pool','get_connection','disconnect']); - pool.pool.should.be.a('function'); - pool.get_connection.should.be.a('function'); - pool.disconnect.should.be.a('function'); - }); - it('should create a QueryBuilder adapter when getting a connection from the pool', done => { - const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); - const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(qb => { - check(done, () => { - expect(qb).to.include.keys(Object.keys(qb2)); - }); - }); - }); - it('should allow one to release a connection from the pool', done => { - const qb2 = nqb.QueryBuilder(Object.assign({}, settings), driver); - const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(qb => { - check(done, () => connection_released(qb)); - }); - }); - it('should allow one use the same connection pool connection for multiple queries', done => { - const pool = nqb.QueryBuilder(Object.assign({}, settings), driver, 'pool'); + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.connection_settings); + qb.connection_settings.should.be.a('function'); + const settings = qb.connection_settings(); + expect(settings).to.be.instanceof(Object); + expect(settings).to.be.eql(conn_settings); + qb.disconnect(); + }); + }); + }); + it('should allow us to escape certain values', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.escape); + qb.escape.should.be.a('function'); + expect(qb.escape(null)).to.be.eql('NULL'); + expect(qb.escape('3')).to.be.eql(3); + expect(qb.escape(false)).to.be.eql(0); + expect(qb.escape(true)).to.be.eql(1); + expect(qb.escape(null)).to.be.eql('NULL'); + qb.disconnect(); + }); + }); + }); + it('should allow us to escape identifiers the MySQL way', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.escape_id); + qb.escape_id.should.be.a('function'); + expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); + expect(qb.escape_id('`foo`'), 'pre-escaped').to.be.eql('```foo```'); + expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('`foo`.`bar`'); + qb.disconnect(); + }); + }); + }); + it('should allow us to execute a query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { + check(done, () => { + expect(err).to.not.be.instanceof(Error); + expect(res).to.not.be.empty; + expect(res).to.have.length(3); + }); + }); + }); + }); + it('should not be able to release a non-pooled connection', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + expect(() => qb.release()).to.throw(Error); + }); + }); + }); + it('should create a connection pool object if asked', () => { + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + expect(pool).to.be.instanceof.object; + expect(pool).to.include.keys(['pool','get_connection','disconnect']); + pool.pool.should.be.a('function'); + pool.get_connection.should.be.a('function'); + pool.disconnect.should.be.a('function'); + }); + it('should create a QueryBuilder adapter when getting a connection from the pool', done => { + const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + pool.get_connection(qb => { + check(done, () => { + expect(qb).to.include.keys(Object.keys(qb2)); + }); + }); + }); + it('should allow one to release a connection from the pool', done => { + const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + pool.get_connection(qb => { + check(done, () => connection_released(qb)); + }); + }); + it('should allow one use the same connection pool connection for multiple queries', done => { + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - pool.get_connection(qb => { - qb.query('select * from `cities` where `city` = "Gainesville"', (err, res) => { - if (res.length > 0) { - qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', (err, res) => { - check(done, () => connection_released(qb)); - }); - } else { - check(done, () => connection_released(qb)); - } - }); - }); - }); + pool.get_connection(qb => { + qb.query('select * from `cities` where `city` = "Gainesville"', (err, res) => { + if (res.length > 0) { + qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', (err, res) => { + check(done, () => connection_released(qb)); + }); + } else { + check(done, () => connection_released(qb)); + } + }); + }); + }); }); From 6e6d3804d8e8b585bc80aebaf3ca1767c0b03d4b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 12 Jun 2018 17:47:40 -0400 Subject: [PATCH 143/200] Switched to and instead of . All tests are adapter tests are passing. --- HISTORY.md | 3 + Makefile | 2 +- README.md | 22 ++- drivers/mssql/adapters.js | 143 ++++++++++----- drivers/mssql/adapters_old.js | 224 ----------------------- drivers/mssql/query_exec.js | 23 ++- package.json | 3 +- test/mssql/tests-adapter.js | 331 +++++++++++++++++----------------- 8 files changed, 302 insertions(+), 449 deletions(-) delete mode 100755 drivers/mssql/adapters_old.js diff --git a/HISTORY.md b/HISTORY.md index 13ae1de..0d4f044 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,9 @@ you spot any mistakes. * Added mssql support * Updated class files to use new ES6 class syntax for easier-maintainability * Changed the Query Builder instantiation syntax +* Added new options: +** `pool_min` (minimum number of pooled connections (`mssql` driver only)) +** `acquireTimeout` (milliseconds before a timeout occurs during the connection acquisition) ## v1.2.0 (2018-05-18) diff --git a/Makefile b/Makefile index 273e562..4e994ba 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mssql/*.js test: - mocha --timeout 5000 --check-leaks --reporter spec $(TESTS) + mocha --timeout 5000 --reporter spec $(TESTS) .PHONY: test diff --git a/README.md b/README.md index 0707c47..2eacafb 100755 --- a/README.md +++ b/README.md @@ -104,16 +104,18 @@ Driver | Default | Ready | single | pool ## Standard Connection Settings The options listed below are available for all database drivers. Additional properties may be passed if the driver of the database you are connecting to supports them. See the "Additional Connection Options" column above for a link to the a specific driver's connection options documentation. -Option | Default | Optional | Description -:------------ | :-------- | :------- | :------------------------------------------- -**host** | localhost | No | The server you're connecting to -**user** | NULL | No | The database user -**password** | NULL | Yes | The database `user`'s password -**database** | NULL | Yes | The database to connect to -**port** | NULL | Yes | The database port to use when connecting -**pool_size** | 10 | Yes | Max connections for `pool` connection type -**debug** | false | Yes | If true, debug info will be place in app log -**version** | default | Yes | Version of database driver to use +Option | Default | Optional | Description +:------------------ | :-------- | :------- | :------------------------------------------- +**host** | localhost | No | The server you're connecting to +**user** | NULL | No | The database user +**password** | NULL | Yes | The database `user`'s password +**database** | NULL | Yes | The database to connect to +**port** | NULL | Yes | The database port to use when connecting +**pool_size** | 10 | Yes | Max connections for `pool` connection type +**pool_min** | 10 | Yes | Min connections for `pool` connection type (`mssql` only) +**acquireTimeout** | 10000 | Yes | The milliseconds before a timeout occurs during the connection acquisition. +**debug** | false | Yes | If true, debug info will be place in app log +**version** | default | Yes | Version of database driver to use The best way to store these options is in a JSON file outsite of your web root where only root and the server user can access them. diff --git a/drivers/mssql/adapters.js b/drivers/mssql/adapters.js index 6915ecc..61bbd50 100755 --- a/drivers/mssql/adapters.js +++ b/drivers/mssql/adapters.js @@ -1,4 +1,5 @@ -const mssql = require('mssql'); +const ConnectionPool = require('tedious-connection-pool'); +const Connection = require('tedious').Connection; class Adapter { constructor(nqb) { // Verify that an instance of Node QueryBuilder was passed in @@ -16,45 +17,91 @@ class Adapter { // Enable debugging if necessary this.debugging = false; - if (this.nqb.settings.hasOwnProperty('qb_debug') && this.nqb.settings.qb_debug === true) { + if (this.nqb.settings.hasOwnProperty('debug') && this.nqb.settings.debug === true) { this.debugging = true; - delete this.nqb.settings.qb_debug; + delete this.nqb.settings.debug; } // Verify that required fields are provided... if (Object.keys(this.nqb.settings).length === 0) throw new Error("No connection information provided!"); if (!this.nqb.settings.hasOwnProperty('host')) this.nqb.settings.host = 'localhost'; - if (!this.nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); + if (!this.nqb.settings.hasOwnProperty('user')) { console.log("Settings:", this.nqb.settings); throw new Error("No user property provided. Hint: It can be NULL"); } this.map_connection_settings(); + } // **************************************************************************** // Map generic NQB connection settings to mssql's format // **************************************************************************** map_connection_settings() { - this.connection_settings = { - server: this.nqb.settings.host, - user: this.nqb.settings.user, - password: this.nqb.settings.password + const settings = Object.assign({}, this.nqb.settings); + + this._connection_settings = { + server: settings.host, + userName: settings.user, + password: settings.password, + options: { + port: 1433, + encrypt: false, + rowCollectionOnRequestCompletion: true, + fallbackToDefaultDb: false, + debug: { + packet: this.debugging, + data: this.debugging, + payload: this.debugging, + token: this.debugging, + } + } + }; + + if (settings.hasOwnProperty('database')) { + this._connection_settings.options.database = settings.database; + delete settings.database; } - if (this.nqb.settings.hasOwnProperty('database')) { - this.connection_settings.database = this.nqb.settings.database; - delete this.nqb.settings.database + if (settings.hasOwnProperty('port')) { + this._connection_settings.options.port = settings.port; + delete settings.port; + } + + // Remove mapped connection settings: + delete settings.host; + delete settings.user; + delete settings.password; + + // Set default pool settings + this.pool_settings = { + min: 10, + max: 10, + acquireTimeout: 60000, + log: this.debugging, + }; + + // Override default pool settings + if (settings.hasOwnProperty('pool_size')) { + this.pool_settings.max = settings.pool_size; + delete settings.pool_size; } - if (this.nqb.settings.hasOwnProperty('port')) { - this.connection_settings.port = this.nqb.settings.port; - delete this.nqb.settings.port + if (settings.hasOwnProperty('pool_min')) { + this.pool_settings.min = settings.pool_min; + delete settings.pool_min; } + if (settings.hasOwnProperty('acquireTimeout')) { + this.pool_settings.acquireTimeout = settings.acquireTimeout; + delete settings.acquireTimeout; + } + - // Remove mapped settings: - delete this.nqb.settings.host - delete this.nqb.settings.user - delete this.nqb.settings.password + if (settings.hasOwnProperty('options') && typeof settings.options === 'object') { + let options = this._connection_settings.options; + options = Object.assign(options, settings.options); + options.debug = this._connection_settings.options.debug; + this._connection_settings.options = options; + delete settings.options; + } - // Merge any driver-specific settings into connection settings - this.connection_settings = Object.assign(this.connection_settings, this.nqb.settings); + // Merge any additional driver-specific settings into connection settings + this._connection_settings = Object.assign(this._connection_settings, settings); } // **************************************************************************** @@ -67,7 +114,7 @@ class Adapter { try { return require('./query_builder.js').QueryBuilder(); } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + this.nqb.driver + ": " + e); + throw new Error(`Couldn't load the QueryBuilder library for ${this.nqb.driver}: ${e}`); } } @@ -83,7 +130,7 @@ class Adapter { try { return require('./query_exec.js').QueryExec(qb, conn); } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + this.nqb.driver + ": " + e); + throw new Error(`Couldn't load the QueryExec library for ${this.nqb.driver}: ${e}`); } } } @@ -109,23 +156,30 @@ class Single extends Adapter { } // Otherwise, let's create a new connection else { - this._connection = mssql.connect(this.connection_settings); + const self = this; + function SQLConnection() {}; + SQLConnection.prototype.connect = function(callback) { + this.connection = new Connection(self._connection_settings); + this.connection.on('error', callback); + this.connection.on('connect', callback); + return this.connection; + } + this.sql_connection = new SQLConnection(); } this.qb = this.get_query_builder(); - this.qe = this.get_query_exec(this.qb, this._connection); + this.qe = this.get_query_exec(this.qb, this.sql_connection); const self = this; return Object.assign({ connection_settings: function() { - return self.connection_settings; + return {connection_settings: self._connection_settings, pool_settings: self.pool_settings}; }, connect: function(callback) { - return self._connection.then(err => { - return callback(err); - }); + if (self._connection) return self._connection; + self._connection = self.sql_connection.connect(callback); }, connection: function() { @@ -141,12 +195,17 @@ class Single extends Adapter { }, disconnect: function(callback) { - return self.connection.end(callback); + if (self.pool) { + self.pool.drain(); + } else { + self._connection.close(); + } + if (callback && typeof callback === 'function') callback(null); }, release: function() { if (!self.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - self.pool.releaseConnection(self.connection); + self.pool.release(self._connection); } }, self.qb, self.qe); } @@ -164,14 +223,11 @@ class Pool extends Adapter { // Create pool for node-querybuilder object if it doesn't already have one. if (!this.nqb.hasOwnProperty('pool') || this.nqb.pool.length === 0) { // Create connection Pool - this.nqb.pool = new mssql.ConnectionPool(this.connection_settings, err => { - if (this.debugging === true) { - if (err) { - console.error(err); - } else { - console.log('mssql connection pool created'); - } - } + const ps = Object.assign({}, this.pool_settings); + const cs = Object.assign({}, this._connection_settings); + this.nqb.pool = new ConnectionPool(ps, cs); + this.nqb.pool.on('error', err => { + if (this.debugging === true) console.error(err); }); } @@ -188,20 +244,21 @@ class Pool extends Adapter { throw new Error(error_msg); } - self.nqb.pool.getConnection((err, connection) => { + self.nqb.pool.acquire((err, connection) => { if (err) throw err; - const adapter = new Single(nqb, { + const adapter = new Single(self.nqb, { pool: { pool: self.nqb.pool, - connection: connection + connection: connection, } }); callback(adapter); }); + }, disconnect: function(callback) { - self.nqb.pool.close(callback); + self.nqb.pool.drain(callback); } } } diff --git a/drivers/mssql/adapters_old.js b/drivers/mssql/adapters_old.js deleted file mode 100755 index 533d2bc..0000000 --- a/drivers/mssql/adapters_old.js +++ /dev/null @@ -1,224 +0,0 @@ -const Adapters = function(nqb) { - // Load MySQL Driver - const mssql = require('mssql'); - - // Verify setting property exists - if (!nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } - - // Enable debugging if necessary - this.debugging = false; - if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { - this.debugging = true; - delete nqb.settings.qb_debug; - } - - // Verify that required fields are provided... - if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; - if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); - - this.connection_settings = {}; - const that = this; - - // **************************************************************************** - // Map generic NQB connection settings to mssql's format - // **************************************************************************** - const map_connection_settings = () => { - this.connection_settings = { - server: nqb.settings.host, - user: nqb.settings.user, - password: nqb.settings.password - } - if (nqb.settings.hasOwnProperty('database')) { - this.connection_settings.database = nqb.settings.database; - delete nqb.settings.database - } - if (nqb.settings.hasOwnProperty('port')) { - this.connection_settings.port = nqb.settings.port; - delete nqb.settings.port - } - - // Remove mapped settings: - delete nqb.settings.host - delete nqb.settings.user - delete nqb.settings.password - - // Merge any driver-specific settings into connection settings - this.connection_settings = Object.assign(this.connection_settings, nqb.settings); - } - - map_connection_settings(); - - - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object - // **************************************************************************** - const get_query_builder = () => { - try { - return require('./query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object - // **************************************************************************** - const get_query_exec = (qb, conn) => { - try { - return require('./query_exec.js').QueryExec(qb, conn); - } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Generic Single Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Adapter = function(settings) { - let pool, connection; - - // If the Pool object is instatiating this Adapter, use it's connection - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } - // Otherwise, let's create a new connection - else { - connection = mssql; - } - - const qb = get_query_builder(); - const qe = get_query_exec(qb, connection); - - const adapter = Object.assign({ - connection_settings: function() { - return that.connection_settings; - }, - - connect: function(callback) { - return connection.connect(that.connection_settings, callback); - }, - - connection: function() { - return connection; - }, - - escape: function(str) { - throw new Error("The `escape` method is not supported with the mssql driver!"); - }, - - escape_id: function(str) { - throw new Error("The `escape` method is not supported with the mssql driver!"); - }, - - disconnect: function(callback) { - return connection.end(callback); - }, - - release: function() { - if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - pool.releaseConnection(connection); - } - }, qb, qe); - - return adapter; - }; - - // **************************************************************************** - // Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Pool = function() { - // Return Pool Object - const return_pool = () => { - return { - pool: function() { - return nqb.pool; - }, - get_connection: function(callback) { - if (nqb.pool === null) { - const error_msg = "Connection pool not available!"; - if (that.debuggin === true) console.error(error_msg); - throw new Error(error_msg); - } - - nqb.pool.getConnection((err, connection) => { - if (err) throw err; - const adapter = new Adapter({ - pool: { - pool: nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - nqb.pool.close(callback); - } - } - }; - - // Create pool for node-querybuilder object if it doesn't already have one. - if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { - // Create connection Pool - nqb.pool = new mssql.ConnectionPool(that.connection_settings, err => { - if (that.debugging === true) - if (err) { - console.error(err); - } else { - console.log('mssql connection pool created'); - } - } - }); - } - return return_pool(); - }; - - // **************************************************************************** - // Clustered Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Cluster = () => { - - }; - - // **************************************************************************** - // Basic Description - // ----- - // @param - // @return - // **************************************************************************** - const determine_adapter = () => { - switch(nqb.connection_type) { - case 'cluster': - return new Cluster(); - case 'pool': - return new Pool(); - case 'single': - default: - return new Adapter({}); - } - } - - return determine_adapter(); -}; - -exports.Adapters = Adapters; diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index a88715a..6275d3d 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -1,3 +1,5 @@ +const Request = require('tedious').Request; + // **************************************************************************** // QueryBuilder "Query Execution" methods. // ----- @@ -5,10 +7,12 @@ // @param Object adapter The connection adapter object // **************************************************************************** const QueryExec = function (qb, conn) { - const exec = (sql, callback) => { if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - conn.query(sql, (err, results) => { + //console.log("Connection: ", conn); + const request = new Request(sql, (err, count, results) => { + //console.log("Results:" , results); + // Standardize some important properties if (!err && results.length > 0) { @@ -18,18 +22,27 @@ const QueryExec = function (qb, conn) { } // Affected Rows - if (results.hasOwnProperty('affectedRows')) { - results.affected_rows = results.affectedRows; + if (results.hasOwnProperty('rowsAffected')) { + results.affected_rows = count; } // Changed Rows if (results.hasOwnProperty('changedRows')) { - results.changed_rows = results.changedRows; + results.changed_rows = count; } } callback(err, results); }); + + if (!conn.connection) { + conn.connect(err => { + if (err) return callback(err, null); + conn.connection.execSql(request); + }); + } else { + conn.connection.execSql(request); + } } else { throw new Error("No connection object supplied to the Query Exec Library!"); } diff --git a/package.json b/package.json index 6e9815b..0408887 100755 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "dependencies": { "lodash.assign": "^4.0.7", "mssql": "^4.1.0", - "mysql": "^2.10.1" + "mysql": "^2.10.1", + "tedious-connection-pool": "^1.0.5" }, "devDependencies": { "chai": "~1.4.2", diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js index 95328f8..2145591 100755 --- a/test/mssql/tests-adapter.js +++ b/test/mssql/tests-adapter.js @@ -13,26 +13,48 @@ const check = (done, f) => { const connection_released = qb => { const connection = qb.connection(); - expect(connection._pool._freeConnections).to.have.length(0); + const settings = qb.connection_settings(); + + //console.log("Connection Pool: ", connection.pool); + + let used_connections = connection.pool.connections.filter(v => v.status === 2).length; + let available_connections = connection.pool.connections.filter(v => v.status !== 2).length; + expect(used_connections, 'one used connection').to.be.eql(1); + expect(available_connections, 'max -1 available connections').to.be.eql(settings.pool_settings.min - 1); + qb.release(); - expect(connection._pool._freeConnections).to.have.length(1); + + // Release of the connection is not really immediate... ugh... + setTimeout(() => { + used_connections = connection.pool.connections.filter(v => v.status === 2).length; + available_connections = connection.pool.connections.filter(v => v.status !== 2).length; + //console.log("Connections: ", connection.pool.connections); + expect(used_connections, 'no used connections').to.be.eql(0); + expect(available_connections, 'max connections available').to.be.eql(settings.pool_settings.min); + }, 100); }; describe('QueryBuilder() - MS SQL Adapter', () => { - afterEach(done => { - - done(); - }); - const driver = 'mssql'; const settings = { host: '127.0.0.1', database: 'mock_db', user: 'travis', version: '4.1.0', - port: 3306 + port: 3306, + options: { + encrypt: false + } }; + + + afterEach(done => { + // const qb = new QueryBuilder(Object.assign({}, settings), driver); + // qb.disconnect(); + done(); + }); + const bad_user = Object.assign({},settings, {user: 'foobar'}); const bad_host = Object.assign({},settings, {host: 'nonlocalhost'}); const bad_password = Object.assign({},settings, {password: 'password'}); @@ -77,11 +99,8 @@ describe('QueryBuilder() - MS SQL Adapter', () => { qb.disconnect.should.be.a('function'); qb.disconnect(err => { - const connection = qb.connection(); - check(done, () => { - expect(err, 'should be diconnected').to.not.be.instanceof(Error); - expect(connection._protocol._ended).to.be.true; + expect(err, 'should not have errored during disconnect process').to.not.be.instanceof(Error); }); }); }); @@ -94,115 +113,95 @@ describe('QueryBuilder() - MS SQL Adapter', () => { it('should fail to establish a single connection given no connection credentials', () => { expect(() => new QueryBuilder({},driver)).to.throw(Error); }); - // it('should fail to establish a single connection given connection credentials with bad user', done => { - // let qb; - // - // try { - // qb = new QueryBuilder(bad_user, driver); - // } catch(e) { - // expect(e, 'should not get a connection').to.be.instanceof(Error); - // return; - // } - // expect(qb, 'should have connect property').to.have.property('connect'); - // qb.connect(err => { - // check(done, () => { - // expect(err, 'should not be connected').to.be.instanceof(Error); - // }); - // }); - // }); - // it('should fail to establish a single connection given connection credentials with bad host', done => { - // - // let qb; - // - // try { - // qb = new QueryBuilder(bad_host, driver); - // } catch(e) { - // expect(e, 'should not get a connection').to.be.instanceof(Error); - // return; - // } - // expect(qb, 'should have connect property').to.have.property('connect'); - // qb.connect(err => { - // check(done, () => { - // expect(err, 'should not be connected').to.be.instanceof(Error); - // }); - // }); - // }); - // it('should fail to establish a single connection given connection credentials with bad database', done => { - // - // let qb; - // - // try { - // qb = new QueryBuilder(bad_database, driver); - // } catch(e) { - // expect(e, 'should not get a connection').to.be.instanceof(Error); - // return; - // } - // expect(qb, 'should have connect property').to.have.property('connect'); - // qb.connect(err => { - // check(done, () => { - // expect(err, 'should not be connected').to.be.instanceof(Error); - // }); - // }); - // }); - // it('should fail to establish a single connection given connection credentials with bad password', done => { - // - // let qb; - // - // try { - // qb = new QueryBuilder(bad_password, driver); - // } catch(e) { - // expect(e, 'should not get a connection').to.be.instanceof(Error); - // return; - // } - // expect(qb, 'should have connect property').to.have.property('connect'); - // qb.connect(err => { - // check(done, () => { - // expect(err, 'should not be connected').to.be.instanceof(Error); - // }); - // }); - // }); - // it('should fail to establish a single connection given connection credentials with bad port', done => { - // let qb; - // try { - // qb = new QueryBuilder(bad_port, driver); - // } catch(e) { - // expect(e, 'should not get a connection').to.be.instanceof(Error); - // return; - // } - // expect(qb, 'should have connect property').to.have.property('connect'); - // qb.connect(err => { - // check(done, () => { - // expect(err, 'should not be connected').to.be.instanceof(Error); - // }); - // }); - // }); - // it('should fail to establish connection if an invalid driver is specified', () => { - // let qb; - // expect(() => new QueryBuilder(settings), 'no driver specified').to.not.throw(Error); - // expect(() => new QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); - // }); - // it('should fail to establish connection if an invalid driver version is specified', () => { - // let qb; - // expect(() => new QueryBuilder( Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); - // expect(() => new QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); - // }); - // - // it('should allow us to retrieve our connection settings for reference', done => { - // const conn_settings = Object.assign({}, settings, {password: undefined}); - // delete conn_settings.version; - // - // const qb = new QueryBuilder(Object.assign({}, settings), driver); - // qb.connect(err => { - // check(done, () => { - // should.exist(qb.connection_settings); - // qb.connection_settings.should.be.a('function'); - // const settings = qb.connection_settings(); - // expect(settings).to.be.instanceof(Object); - // expect(settings).to.be.eql(conn_settings); - // qb.disconnect(); - // }); - // }); - // }); + it('should fail to establish a single connection given connection credentials with bad user', done => { + const qb = new QueryBuilder(bad_user, driver); + + expect(qb, 'should have connect method').to.have.property('connect'); + + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad host', done => { + const qb = new QueryBuilder(bad_host, driver); + + expect(qb, 'should have connect method').to.have.property('connect'); + + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad database', done => { + const qb = new QueryBuilder(bad_database, driver); + + expect(qb, 'should have connect method').to.have.property('connect'); + + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad password', done => { + const qb = new QueryBuilder(bad_password, driver); + + expect(qb, 'should have connect method').to.have.property('connect'); + + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish a single connection given connection credentials with bad port', done => { + const qb = new QueryBuilder(bad_port, driver); + + expect(qb, 'should have connect method').to.have.property('connect'); + + qb.connect(err => { + check(done, () => { + expect(err, 'should not be connected').to.be.instanceof(Error); + }); + }); + }); + it('should fail to establish connection if no driver is specified', () => { + expect(() => new QueryBuilder(settings), 'no driver specified').to.throw(Error); + }); + it('should fail to establish connection if an invalid driver is specified', () => { + expect(() => new QueryBuilder(settings, 'foobar'), 'invalid driver specified').to.throw(Error); + }); + it('should fail to establish connection if an invalid driver version is specified', () => { + expect(() => new QueryBuilder(Object.assign({}, settings), driver), 'valid driver version').to.not.throw(Error); + expect(() => new QueryBuilder(bad_version, driver), 'invalid driver version').to.throw(Error); + }); + + it('should allow us to retrieve our connection settings for reference', done => { + const qb_settings = Object.assign({}, settings); + const qb = new QueryBuilder(qb_settings, driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.connection_settings); + qb.connection_settings.should.be.a('function'); + const all_settings = qb.connection_settings(); + const settings = all_settings.connection_settings; + expect(settings).to.be.instanceof(Object); + expect(settings).to.have.property('server'); + expect(settings).to.have.property('userName'); + expect(settings).to.have.property('password'); + expect(settings).to.have.property('options'); + expect(settings.options).to.have.property('database'); + expect(settings.options).to.have.property('port'); + expect(settings.server).to.be.eql(qb_settings.host); + expect(settings.userName).to.be.eql(qb_settings.user); + expect(settings.password).to.be.eql(qb_settings.password); + expect(settings.options.database).to.be.eql(qb_settings.database); + }); + }); + }); // it('should allow us to escape certain values', done => { // const qb = new QueryBuilder(Object.assign({}, settings), driver); // qb.connect(err => { @@ -231,50 +230,52 @@ describe('QueryBuilder() - MS SQL Adapter', () => { // }); // }); // }); - // it('should allow us to execute a query', done => { - // const qb = new QueryBuilder(Object.assign({}, settings), driver); - // qb.connect(err => { - // qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { - // check(done, () => { - // expect(err).to.not.be.instanceof(Error); - // expect(res).to.not.be.empty; - // expect(res).to.have.length(3); - // }); - // }); - // }); - // }); - // it('should not be able to release a non-pooled connection', done => { - // const qb = new QueryBuilder(Object.assign({}, settings), driver); - // qb.connect(err => { - // check(done, () => { - // expect(() => qb.release()).to.throw(Error); - // }); - // }); - // }); - // it('should create a connection pool object if asked', () => { - // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - // expect(pool).to.be.instanceof.object; - // expect(pool).to.include.keys(['pool','get_connection','disconnect']); - // pool.pool.should.be.a('function'); - // pool.get_connection.should.be.a('function'); - // pool.disconnect.should.be.a('function'); - // }); - // it('should create a QueryBuilder adapter when getting a connection from the pool', done => { - // const qb2 = new QueryBuilder(Object.assign({}, settings), driver); - // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - // pool.get_connection(qb => { - // check(done, () => { - // expect(qb).to.include.keys(Object.keys(qb2)); - // }); - // }); - // }); - // it('should allow one to release a connection from the pool', done => { - // const qb2 = new QueryBuilder(Object.assign({}, settings), driver); - // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - // pool.get_connection(qb => { - // check(done, () => connection_released(qb)); - // }); - // }); + it('should allow us to execute a query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.query("select * from [_employees] where [FirstName] like 'Kyl%' and [Department] = 111", (err, res) => { + check(done, () => { + expect(err).to.not.be.instanceof(Error); + expect(res).to.not.be.empty; + expect(res).to.have.length(1); + }); + }); + }); + }); + it('should not be able to release a non-pooled connection', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + expect(() => qb.release()).to.throw(Error); + }); + }); + }); + it('should create a connection pool object if asked', () => { + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + expect(pool).to.be.instanceof.object; + expect(pool).to.include.keys(['pool','get_connection','disconnect']); + pool.pool.should.be.a('function'); + pool.get_connection.should.be.a('function'); + pool.disconnect.should.be.a('function'); + }); + it('should create a QueryBuilder adapter when getting a connection from the pool', done => { + const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + pool.get_connection(qb => { + check(done, () => { + expect(qb).to.include.keys(Object.keys(qb2)); + }); + }); + }); + it('should allow one to release a connection from the pool', done => { + const qb2 = new QueryBuilder(Object.assign({}, settings), driver); + const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); + pool.get_connection(qb => { + check(done, () => connection_released(qb)); + }); + }); // it('should allow one use the same connection pool connection for multiple queries', done => { // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); // From 5004a214f6032b9ad59e685afc50c68106fd1060 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 13 Jun 2018 14:03:31 -0400 Subject: [PATCH 144/200] Got mock database stuff created and working using linux sql server. --- test/create_mock_dbs.sh | 2 +- test/mssql/create_mssql_mock.sh | 25 +++++++ test/mssql/mock_data.sql | 119 ++++++++++++++++++++++++++++++++ test/mssql/tests-adapter.js | 15 ++-- 4 files changed, 152 insertions(+), 9 deletions(-) create mode 100755 test/mssql/create_mssql_mock.sh create mode 100755 test/mssql/mock_data.sql diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 7ff1cde..659474a 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -./test/mysql/create_mysql_mock.sh \ No newline at end of file +./test/mysql/create_mysql_mock.sh diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh new file mode 100755 index 0000000..ebcad18 --- /dev/null +++ b/test/mssql/create_mssql_mock.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +server=localhost +admin_user=sa +admin_pass=Password123 +db_user=travis +password=Password123 +db_name=mock_db +db_table=cities + +# Initial Admin Stuff +sqlcmd -S $server -U $admin_user -P $admin_pass -Q "If not Exists (SELECT loginname FROM [master].[dbo].[syslogins] WHERE name = '$db_user') CREATE LOGIN $db_user WITH PASSWORD='$password';" +sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db_name') IS NULL) CREATE DATABASE $db_name;" +sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "IF NOT EXISTS (SELECT * FROM [sys].[server_principals] WHERE [name] = '$db_user') CREATE USER $db_user FOR LOGIN $db_user;" +sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "GRANT CONTROL ON DATABASE:: $db_name TO $db_user;" + +# User stuff +sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "IF OBJECT_ID('dbo.$db_table', 'U') IS NOT NULL DROP TABLE dbo.$db_table;" +sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" +sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql + +# Check to see if table exists now... +#sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" + +# Check to see if data exists in $db_name +sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [cities];" diff --git a/test/mssql/mock_data.sql b/test/mssql/mock_data.sql new file mode 100755 index 0000000..93906b4 --- /dev/null +++ b/test/mssql/mock_data.sql @@ -0,0 +1,119 @@ +INSERT INTO [cities] VALUES ('Aaronsburg','PA'),('Abbeville','AL'),('Abbeville','GA'),('Abbeville','LA'),('Abbeville','MS'),('Abbeville','SC'),('Abbot','ME'),('Abbotsford','WI'),('Abbott','TX'),('Abbottstown','PA'),('Abbyville','KS'),('Abell','MD'),('Abercrombie','ND'),('Aberdeen','ID'),('Aberdeen','KY'),('Aberdeen','MD'),('Aberdeen','MS'),('Aberdeen','NC'),('Aberdeen','OH'),('Aberdeen','SD'),('Aberdeen','WA'),('Aberdeen Proving Ground','MD'),('Abernant','AL'),('Abernathy','TX'),('Abie','NE'),('Abilene','KS'),('Abilene','TX'),('Abingdon','IL'),('Abingdon','MD'),('Abingdon','VA'),('Abington','CT'),('Abington','MA'),('Abington','PA'),('Abiquiu','NM'),('Abita Springs','LA'),('Abrams','WI'),('Absaraka','ND'),('Absarokee','MT'),('Absecon','NJ'),('Acampo','CA'),('Accident','MD'),('Accokeek','MD'),('Accomac','VA'),('Accord','MA'),('Accord','NY'),('Accoville','WV'),('Ace','TX'),('Achille','OK'),('Achilles','VA'),('Ackerly','TX'),('Ackerman','MS'),('Ackermanville','PA'),('Ackley','IA'),('Ackworth','IA'),('Acme','LA'),('Acme','MI'),('Acme','PA'),('Acme','WA'),('Acosta','PA'),('Acra','NY'),('Acton','CA'),('Acton','MA'),('Acton','ME'),('Acton','MT'),('Acushnet','MA'),('Acworth','GA'),('Acworth','NH'),('Ada','MI'),('Ada','MN'),('Ada','OH'),('Ada','OK'),('Adah','PA'),('Adair','IA'),('Adair','IL'),('Adair','OK'),('Adairsville','GA'),('Adairville','KY'),('Adak','AK'),('Adamant','VT'),('Adams','KY'),('Adams','MA'),('Adams','MN'),('Adams','ND'),('Adams','NE'),('Adams','NY'),('Adams','OK'),('Adams','OR'),('Adams','TN'),('Adams','WI'),('Adams Basin','NY'),('Adams Center','NY'),('Adams Run','SC'),('Adamsburg','PA'),('Adamstown','MD'),('Adamstown','PA'),('Adamsville','AL'),('Adamsville','OH'),('Adamsville','PA'),('Adamsville','RI'),('Adamsville','TN'),('Addieville','IL'),('Addington','OK'),('Addis','LA'),('Addison','AL'),('Addison','IL'),('Addison','ME'),('Addison','MI'),('Addison','NY'),('Addison','PA'),('Addison','TX'),('Addy','WA'),('Addyston','OH'),('Adel','GA'),('Adel','IA'),('Adel','OR'),('Adelanto','CA'),('Adell','WI'),('Adelphi','OH'),('Adelphia','NJ'),('Adena','OH'),('Adger','AL'),('Adin','CA'),('Adirondack','NY'),('Adjuntas','PR'),('Adkins','TX'),('Admire','KS'),('Adna','WA'),('Adolph','MN'),('Adolphus','KY'),('Adona','AR'),('Adrian','GA'),('Adrian','MI'),('Adrian','MN'),('Adrian','MO'),('Adrian','OR'),('Adrian','PA'),('Adrian','TX'),('Adrian','WV'),('Advance','IN'),('Advance','MO'),('Advance','NC'),('Advent','WV'),('Afton','IA'),('Afton','MI'),('Afton','MN'),('Afton','NY'),('Afton','OK'),('Afton','TN'),('Afton','TX'),('Afton','VA'),('Afton','WI'),('Afton','WY'),('Agar','SD'),('Agate','CO'),('Agate','ND'),('Agawam','MA'),('Agency','IA'),('Agency','MO'),('Agenda','KS'),('Ages Brookside','KY'),('Agness','OR'),('Agoura Hills','CA'),('Agra','KS'),('Agra','OK'),('Agua Dulce','TX'),('Aguada','PR'),('Aguadilla','PR'),('Aguanga','CA'),('Aguas Buenas','PR'),('Aguila','AZ'),('Aguilar','CO'),('Aguirre','PR'),('Ah Gwah Ching','MN'),('Ahmeek','MI'),('Ahoskie','NC'),('Ahsahka','ID'),('Ahwahnee','CA'),('Aibonito','PR'),('Aiea','HI'),('Aiken','SC'),('Aiken','TX'),('Ailey','GA'),('Aimwell','LA'),('Ainsworth','IA'),('Ainsworth','NE'),('Airville','PA'),('Airway Heights','WA'),('Aitkin','MN'),('Ajo','AZ'),('Akaska','SD'),('Akeley','MN'),('Akers','LA'),('Akiachak','AK'),('Akiak','AK'),('Akin','IL'),('Akron','AL'),('Akron','CO'),('Akron','IA'),('Akron','IN'),('Akron','MI'),('Akron','NY'),('Akron','OH'),('Akron','PA'),('Akutan','AK'),('Alabaster','AL'),('Alachua','FL'),('Aladdin','WY'),('Alakanuk','AK'),('Alamance','NC'),('Alameda','CA'),('Alamo','CA'),('Alamo','GA'),('Alamo','IN'),('Alamo','ND'),('Alamo','NV'),('Alamo','TN'),('Alamo','TX'),('Alamogordo','NM'),('Alamosa','CO'),('Alanreed','TX'),('Alanson','MI'),('Alapaha','GA'),('Alba','MI'),('Alba','MO'),('Alba','PA'),('Alba','TX'),('Albany','CA'),('Albany','GA'),('Albany','IL'),('Albany','IN'),('Albany','KY'),('Albany','LA'),('Albany','MN'),('Albany','MO'),('Albany','NY'),('Albany','OH'),('Albany','OK'),('Albany','OR'),('Albany','TX'),('Albany','VT'),('Albany','WI'),('Albemarle','NC'),('Albers','IL'),('Albert','KS'),('Albert','OK'),('Albert City','IA'),('Albert Lea','MN'),('Alberta','AL'),('Alberta','MN'),('Alberta','VA'),('Alberton','MT'),('Albertson','NC'),('Albertson','NY'),('Albertville','AL'),('Albertville','MN'),('Albia','IA'),('Albin','WY'),('Albion','CA'),('Albion','IA'),('Albion','ID'),('Albion','IL'),('Albion','IN'),('Albion','ME'),('Albion','MI'),('Albion','NE'),('Albion','NY'),('Albion','OK'),('Albion','PA'),('Albion','RI'),('Albion','WA'),('Alborn','MN'),('Albright','WV'),('Albrightsville','PA'),('Albuquerque','NM'),('Alburgh','VT'),('Alburnett','IA'),('Alburtis','PA'),('Alcalde','NM'),('Alcester','SD'),('Alcoa','TN'),('Alcolu','SC'),('Alcova','WY'),('Alcove','NY'),('Alda','NE'),('Alden','IA'),('Alden','IL'),('Alden','KS'),('Alden','MI'),('Alden','MN'),('Alden','NY'),('Alder','MT'),('Alder Creek','NY'),('Alderpoint','CA'),('Alderson','OK'),('Alderson','WV'),('Aldie','VA'),('Aldrich','MN'),('Aldrich','MO'),('Aledo','IL'),('Aledo','TX'),('Aleknagik','AK'),('Aleppo','PA'),('Alex','OK'),('Alexander','AR'),('Alexander','IA'),('Alexander','IL'),('Alexander','KS'),('Alexander','NC'),('Alexander','ND'),('Alexander','NY'),('Alexander City','AL'),('Alexandria','AL'),('Alexandria','IN'),('Alexandria','KY'),('Alexandria','LA'),('Alexandria','MN'),('Alexandria','MO'),('Alexandria','NE'),('Alexandria','OH'),('Alexandria','PA'),('Alexandria','SD'),('Alexandria','TN'),('Alexandria','VA'),('Alexandria Bay','NY'),('Alexis','IL'),('Alexis','NC'),('Alford','FL'),('Alfred','ME'),('Alfred','NY'),('Alfred Station','NY'),('Alger','MI'),('Alger','OH'),('Algodones','NM'),('Algoma','MS'),('Algoma','WI'),('Algona','IA'),('Algonac','MI'),('Algonquin','IL'),('Alhambra','CA'),('Alhambra','IL'),('Alice','TX'),('Aliceville','AL'),('Alicia','AR'),('Alief','TX'),('Aline','OK'),('Aliquippa','PA'),('Aliso Viejo','CA'),('Alix','AR'),('Alkol','WV'),('Allakaket','AK'),('Allamuchy','NJ'),('Allardt','TN'),('Alledonia','OH'),('Alleene','AR'),('Allegan','MI'),('Allegany','NY'),('Allegany','OR'),('Alleghany','CA'),('Alleman','IA'),('Allen','KS'),('Allen','KY'),('Allen','MD'),('Allen','MI'),('Allen','NE'),('Allen','OK'),('Allen','SD'),('Allen','TX'),('Allen Junction','WV'),('Allen Park','MI'),('Allendale','IL'),('Allendale','MI'),('Allendale','MO'),('Allendale','NJ'),('Allendale','SC'),('Allenhurst','GA'),('Allenhurst','NJ'),('Allenport','PA'),('Allenspark','CO'),('Allensville','KY'),('Allensville','PA'),('Allenton','MI'),('Allenton','MO'),('Allenton','WI'),('Allentown','GA'),('Allentown','NJ'),('Allentown','NY'),('Allentown','PA'),('Allenwood','NJ'),('Allenwood','PA'),('Allerton','IA'),('Allerton','IL'),('Alleyton','TX'),('Allgood','AL'),('Alliance','NC'),('Alliance','NE'),('Alliance','OH'),('Alligator','MS'),('Allison','IA'),('Allison','PA'),('Allison','TX'),('Allison Park','PA'),('Allons','TN'),('Allouez','MI'),('Alloway','NJ'),('Alloy','WV'),('Allport','PA'),('Allred','TN'),('Allston','MA'),('Allyn','WA'),('Alma','AL'),('Alma','AR'),('Alma','CO'),('Alma','GA'),('Alma','IL'),('Alma','KS'),('Alma','MI'),('Alma','MO'),('Alma','NE'),('Alma','NY'),('Alma','WI'),('Alma','WV'),('Alma Center','WI'),('Almelund','MN'),('Almena','KS'),('Almena','WI'),('Almira','WA'),('Almo','ID'),('Almo','KY'),('Almond','NC'),('Almond','NY'),('Almond','WI'),('Almont','CO'),('Almont','MI'),('Almont','ND'),('Almyra','AR'),('Alna','ME'),('Alpaugh','CA'),('Alpena','AR'),('Alpena','MI'),('Alpena','SD'),('Alpha','IL'),('Alpha','KY'),('Alpha','MI'),('Alpha','MN'),('Alpha','OH'),('Alpharetta','GA'),('Alpine','AL'),('Alpine','AR'),('Alpine','AZ'),('Alpine','CA'),('Alpine','NJ'),('Alpine','NY'),('Alpine','TN'),('Alpine','TX'),('Alpine','UT'),('Alpine','WY'),('Alplaus','NY'),('Alsea','OR'),('Alsen','ND'),('Alsey','IL'),('Alsip','IL'),('Alstead','NH'),('Alston','GA'),('Alta','CA'),('Alta','IA'),('Alta','WY'),('Alta Vista','IA'),('Alta Vista','KS'),('Altadena','CA'),('Altair','TX'),('Altamahaw','NC'),('Altamont','IL'),('Altamont','KS'),('Altamont','MO'),('Altamont','NY'),('Altamont','TN'),('Altamont','UT'),('Altamonte Springs','FL'),('Altaville','CA'),('Altavista','VA'),('Altenburg','MO'),('Altha','FL'),('Altheimer','AR'),('Altmar','NY'),('Alto','GA'),('Alto','MI'),('Alto','NM'),('Alto','TX'),('Alto Pass','IL'),('Alton','AL'),('Alton','IA'),('Alton','IL'),('Alton','KS'),('Alton','MO'),('Alton','NH'),('Alton','NY'),('Alton','UT'),('Alton','VA'),('Alton Bay','NH'),('Altona','IL'); + +INSERT INTO [cities] VALUES ('Altona','NY'),('Altonah','UT'),('Altoona','AL'),('Altoona','FL'),('Altoona','IA'),('Altoona','KS'),('Altoona','PA'),('Altoona','WI'),('Altura','MN'),('Alturas','CA'),('Alturas','FL'),('Altus','AR'),('Altus','OK'),('Altus Afb','OK'),('Alum Bank','PA'),('Alum Bridge','WV'),('Alum Creek','WV'),('Alva','FL'),('Alva','OK'),('Alva','WY'),('Alvada','OH'),('Alvadore','OR'),('Alvarado','MN'),('Alvarado','TX'),('Alvaton','KY'),('Alverda','PA'),('Alverton','PA'),('Alvin','IL'),('Alvin','TX'),('Alviso','CA'),('Alvo','NE'),('Alvord','IA'),('Alvord','TX'),('Alvordton','OH'),('Alzada','MT'),('Ama','LA'),('Amado','AZ'),('Amador City','CA'),('Amagansett','NY'),('Amagon','AR'),('Amalia','NM'),('Amana','IA'),('Amanda','OH'),('Amanda Park','WA'),('Amargosa Valley','NV'),('Amarillo','TX'),('Amasa','MI'),('Amawalk','NY'),('Amazonia','MO'),('Amber','OK'),('Amberg','WI'),('Amberson','PA'),('Ambia','IN'),('Ambler','AK'),('Ambler','PA'),('Amboy','CA'),('Amboy','IL'),('Amboy','IN'),('Amboy','MN'),('Amboy','WA'),('Ambridge','PA'),('Ambrose','GA'),('Ambrose','ND'),('Amelia','LA'),('Amelia','NE'),('Amelia','OH'),('Amelia Court House','VA'),('Amenia','ND'),('Amenia','NY'),('American Canyon','CA'),('American Falls','ID'),('American Fork','UT'),('Americus','GA'),('Americus','KS'),('Amery','WI'),('Ames','IA'),('Ames','NE'),('Ames','OK'),('Amesbury','MA'),('Amesville','OH'),('Amherst','CO'),('Amherst','MA'),('Amherst','NE'),('Amherst','NH'),('Amherst','OH'),('Amherst','SD'),('Amherst','TX'),('Amherst','VA'),('Amherst','WI'),('Amherst Junction','WI'),('Amherstdale','WV'),('Amidon','ND'),('Amigo','WV'),('Amissville','VA'),('Amistad','NM'),('Amite','LA'),('Amity','AR'),('Amity','MO'),('Amity','OR'),('Amity','PA'),('Amityville','NY'),('Amlin','OH'),('Amma','WV'),('Ammon','VA'),('Amo','IN'),('Amonate','VA'),('Amoret','MO'),('Amorita','OK'),('Amory','MS'),('Amsterdam','MO'),('Amsterdam','NY'),('Amsterdam','OH'),('Amston','CT'),('Anabel','MO'),('Anacoco','LA'),('Anaconda','MT'),('Anacortes','WA'),('Anadarko','OK'),('Anaheim','CA'),('Anahola','HI'),('Anahuac','TX'),('Anaktuvuk Pass','AK'),('Analomink','PA'),('Anamoose','ND'),('Anamosa','IA'),('Anasco','PR'),('Anatone','WA'),('Anawalt','WV'),('Anchor','IL'),('Anchor Point','AK'),('Anchorage','AK'),('Anchorville','MI'),('Ancona','IL'),('Ancram','NY'),('Ancramdale','NY'),('Andale','KS'),('Andalusia','AL'),('Andalusia','IL'),('Anderson','AK'),('Anderson','AL'),('Anderson','CA'),('Anderson','IN'),('Anderson','MO'),('Anderson','SC'),('Anderson','TX'),('Anderson Island','WA'),('Andersonville','GA'),('Andersonville','TN'),('Andes','NY'),('Andover','CT'),('Andover','IA'),('Andover','IL'),('Andover','KS'),('Andover','MA'),('Andover','ME'),('Andover','MN'),('Andover','NH'),('Andover','NJ'),('Andover','NY'),('Andover','OH'),('Andover','SD'),('Andover','VA'),('Andreas','PA'),('Andrew','IA'),('Andrews','IN'),('Andrews','NC'),('Andrews','SC'),('Andrews','TX'),('Andrews Air Force Base','MD'),('Aneta','ND'),('Aneth','UT'),('Angel Fire','NM'),('Angela','MT'),('Angeles','PR'),('Angelica','NY'),('Angels Camp','CA'),('Angelus Oaks','CA'),('Angie','LA'),('Angier','NC'),('Angle Inlet','MN'),('Angleton','TX'),('Angola','IN'),('Angola','LA'),('Angola','NY'),('Angoon','AK'),('Angora','MN'),('Angora','NE'),('Anguilla','MS'),('Angwin','CA'),('Aniak','AK'),('Animas','NM'),('Anita','IA'),('Anita','PA'),('Aniwa','WI'),('Ankeny','IA'),('Anmoore','WV'),('Ann Arbor','MI'),('Anna','IL'),('Anna','OH'),('Anna','TX'),('Anna Maria','FL'),('Annabella','UT'),('Annada','MO'),('Annandale','MN'),('Annandale','NJ'),('Annandale','VA'),('Annandale On Hudson','NY'),('Annapolis','CA'),('Annapolis','IL'),('Annapolis','MD'),('Annapolis','MO'),('Annapolis Junction','MD'),('Annawan','IL'),('Annemanie','AL'),('Anniston','AL'),('Anniston','MO'),('Annona','TX'),('Annville','KY'),('Annville','PA'),('Anoka','MN'),('Anselmo','NE'),('Ansley','NE'),('Anson','ME'),('Anson','TX'),('Ansonia','CT'),('Ansonia','OH'),('Ansonville','NC'),('Ansted','WV'),('Antelope','CA'),('Antelope','MT'),('Antelope','OR'),('Antes Fort','PA'),('Anthon','IA'),('Anthony','FL'),('Anthony','KS'),('Anthony','NM'),('Anthony','TX'),('Antigo','WI'),('Antimony','UT'),('Antioch','CA'),('Antioch','IL'),('Antioch','TN'),('Antler','ND'),('Antlers','OK'),('Antoine','AR'),('Anton','CO'),('Anton','TX'),('Anton Chico','NM'),('Antonito','CO'),('Antrim','NH'),('Antwerp','NY'),('Antwerp','OH'),('Anvik','AK'),('Anza','CA'),('Apache','OK'),('Apache Junction','AZ'),('Apalachicola','FL'),('Apalachin','NY'),('Apex','NC'),('Apison','TN'),('Aplington','IA'),('Apollo','PA'),('Apollo Beach','FL'),('Apopka','FL'),('Appalachia','VA'),('Apple Creek','OH'),('Apple Grove','WV'),('Apple River','IL'),('Apple Springs','TX'),('Apple Valley','CA'),('Applegate','CA'),('Applegate','MI'),('Appleton','MN'),('Appleton','NY'),('Appleton','WA'),('Appleton','WI'),('Appleton City','MO'),('Appling','GA'),('Appomattox','VA'),('Aptos','CA'),('Apulia Station','NY'),('Aquasco','MD'),('Aquashicola','PA'),('Aquebogue','NY'),('Aquilla','TX'),('Arab','AL'),('Arabi','GA'),('Arabi','LA'),('Aragon','GA'),('Aragon','NM'),('Aransas Pass','TX'),('Arapaho','OK'),('Arapahoe','CO'),('Arapahoe','NC'),('Arapahoe','NE'),('Arapahoe','WY'),('Ararat','NC'),('Ararat','VA'),('Arbela','MO'),('Arboles','CO'),('Arbon','ID'),('Arbovale','WV'),('Arbuckle','CA'),('Arbyrd','MO'),('Arcade','NY'),('Arcadia','CA'),('Arcadia','FL'),('Arcadia','IA'),('Arcadia','IN'),('Arcadia','KS'),('Arcadia','LA'),('Arcadia','MI'),('Arcadia','MO'),('Arcadia','NE'),('Arcadia','OH'),('Arcadia','OK'),('Arcadia','PA'),('Arcadia','SC'),('Arcadia','WI'),('Arcanum','OH'),('Arcata','CA'),('Arch Cape','OR'),('Archbald','PA'),('Archbold','OH'),('Archer','FL'),('Archer','IA'),('Archer','NE'),('Archer City','TX'),('Archibald','LA'),('Archie','MO'),('Arco','ID'),('Arco','MN'),('Arcola','IL'),('Arcola','IN'),('Arcola','MO'),('Arcola','MS'),('Arcola','PA'),('Arcola','VA'),('Arctic Village','AK'),('Ardara','PA'),('Arden','NC'),('Arden','NY'),('Ardenvoir','WA'),('Ardmore','AL'),('Ardmore','OK'),('Ardmore','PA'),('Ardmore','TN'),('Ardsley','NY'),('Ardsley On Hudson','NY'),('Arecibo','PR'),('Aredale','IA'),('Arena','WI'),('Arenas Valley','NM'),('Arendtsville','PA'),('Arenzville','IL'),('Argenta','IL'),('Argillite','KY'),('Argonia','KS'),('Argonne','WI'),('Argos','IN'),('Argusville','ND'),('Argyle','FL'),('Argyle','GA'),('Argyle','IA'),('Argyle','MI'),('Argyle','MN'),('Argyle','MO'),('Argyle','NY'),('Argyle','TX'),('Argyle','WI'),('Ariel','WA'),('Arimo','ID'),('Arion','IA'),('Aripeka','FL'),('Arispe','IA'),('Aristes','PA'),('Ariton','AL'),('Arivaca','AZ'),('Arizona City','AZ'),('Arjay','KY'),('Ark','VA'),('Arkabutla','MS'),('Arkadelphia','AR'),('Arkansas City','AR'),('Arkansas City','KS'),('Arkansaw','WI'),('Arkdale','WI'),('Arkoma','OK'),('Arkport','NY'),('Arkville','NY'),('Arlee','MT'),('Arley','AL'),('Arlington','AL'),('Arlington','AZ'),('Arlington','CO'),('Arlington','GA'),('Arlington','IA'),('Arlington','IL'),('Arlington','IN'),('Arlington','KS'),('Arlington','KY'),('Arlington','MA'),('Arlington','MN'),('Arlington','NE'),('Arlington','OH'),('Arlington','OR'),('Arlington','SD'),('Arlington','TN'),('Arlington','TX'),('Arlington','VA'),('Arlington','VT'),('Arlington','WA'),('Arlington','WI'),('Arlington Heights','IL'),('Arlington Heights','MA'),('Arma','KS'),('Armada','MI'),('Armagh','PA'),('Armbrust','PA'),('Armington','IL'),('Arminto','WY'),('Armona','CA'),('Armonk','NY'),('Armorel','AR'),('Armour','SD'),('Armstrong','IA'),('Armstrong','IL'),('Armstrong','MO'),('Armstrong','TX'),('Armstrong Creek','WI'),('Armuchee','GA'),('Arnaudville','LA'),('Arnegard','ND'),('Arnett','OK'),('Arnett','WV'),('Arnold','CA'),('Arnold','KS'),('Arnold','MD'),('Arnold','MI'),('Arnold','MO'),('Arnold','NE'),('Arnold A F B','TN'),('Arnolds Park','IA'),('Arnoldsburg','WV'),('Arnoldsville','GA'),('Arnot','PA'),('Arock','OR'),('Aroda','VA'),('Aroma Park','IL'),('Aromas','CA'),('Arona','PA'),('Arp','TX'),('Arpin','WI'),('Arrey','NM'),('Arriba','CO'),('Arrington','TN'),('Arrington','VA'),('Arrow Rock','MO'),('Arrowsmith','IL'),('Arroyo','PR'),('Arroyo Grande','CA'),('Arroyo Hondo','NM'),('Arroyo Seco','NM'),('Art','TX'),('Artemas','PA'),('Artemus','KY'),('Artesia','CA'),('Artesia','MS'),('Artesia','NM'),('Artesia Wells','TX'),('Artesian','SD'),('Arthur','IA'),('Arthur','IL'),('Arthur','ND'),('Arthur','NE'),('Arthur','TN'),('Arthur City','TX'),('Arthurdale','WV'),('Artie','WV'),('Artois','CA'),('Arvada','CO'),('Arvada','WY'),('Arverne','NY'),('Arvilla','ND'),('Arvin','CA'),('Arvonia','VA'),('Ary','KY'),('Asbury','MO'),('Asbury','NJ'),('Asbury','WV'),('Asbury Park','NJ'),('Ascutney','VT'),('Ash','NC'),('Ash Flat','AR'),('Ash Fork','AZ'),('Ash Grove','MO'),('Ashaway','RI'),('Ashburn','GA'),('Ashburn','MO'),('Ashburn','VA'); + +INSERT INTO [cities] VALUES ('Ashburnham','MA'),('Ashby','MA'),('Ashby','MN'),('Ashby','NE'),('Ashcamp','KY'),('Ashdown','AR'),('Asheboro','NC'),('Asher','KY'),('Asher','OK'),('Asherton','TX'),('Asheville','NC'),('Ashfield','MA'),('Ashfield','PA'),('Ashford','AL'),('Ashford','CT'),('Ashford','WA'),('Ashford','WV'),('Ashippun','WI'),('Ashkum','IL'),('Ashland','AL'),('Ashland','IL'),('Ashland','KS'),('Ashland','KY'),('Ashland','LA'),('Ashland','MA'),('Ashland','ME'),('Ashland','MO'),('Ashland','MS'),('Ashland','MT'),('Ashland','NE'),('Ashland','NH'),('Ashland','NY'),('Ashland','OH'),('Ashland','OR'),('Ashland','PA'),('Ashland','VA'),('Ashland','WI'),('Ashland City','TN'),('Ashley','IL'),('Ashley','IN'),('Ashley','MI'),('Ashley','ND'),('Ashley','OH'),('Ashley Falls','MA'),('Ashmore','IL'),('Ashtabula','OH'),('Ashton','IA'),('Ashton','ID'),('Ashton','IL'),('Ashton','MD'),('Ashton','NE'),('Ashton','SD'),('Ashton','WV'),('Ashuelot','NH'),('Ashville','AL'),('Ashville','NY'),('Ashville','OH'),('Ashville','PA'),('Ashwood','OR'),('Askov','MN'),('Asotin','WA'),('Aspen','CO'),('Aspermont','TX'),('Aspers','PA'),('Aspinwall','IA'),('Assaria','KS'),('Assawoman','VA'),('Assonet','MA'),('Assumption','IL'),('Astatula','FL'),('Aston','PA'),('Astor','FL'),('Astoria','IL'),('Astoria','NY'),('Astoria','OR'),('Astoria','SD'),('Atalissa','IA'),('Atascadero','CA'),('Atascosa','TX'),('Atchison','KS'),('Atco','NJ'),('Atglen','PA'),('Athelstane','WI'),('Athena','OR'),('Athens','AL'),('Athens','GA'),('Athens','IL'),('Athens','IN'),('Athens','LA'),('Athens','ME'),('Athens','MI'),('Athens','NY'),('Athens','OH'),('Athens','PA'),('Athens','TN'),('Athens','TX'),('Athens','WI'),('Athens','WV'),('Atherton','CA'),('Athol','ID'),('Athol','KS'),('Athol','KY'),('Athol','MA'),('Athol','NY'),('Athol Springs','NY'),('Atka','AK'),('Atkins','AR'),('Atkins','IA'),('Atkins','VA'),('Atkinson','IL'),('Atkinson','NC'),('Atkinson','NE'),('Atkinson','NH'),('Atlanta','GA'),('Atlanta','ID'),('Atlanta','IL'),('Atlanta','IN'),('Atlanta','KS'),('Atlanta','LA'),('Atlanta','MI'),('Atlanta','MO'),('Atlanta','NE'),('Atlanta','NY'),('Atlanta','TX'),('Atlantic','IA'),('Atlantic','NC'),('Atlantic','PA'),('Atlantic','VA'),('Atlantic Beach','FL'),('Atlantic Beach','NC'),('Atlantic Beach','NY'),('Atlantic City','NJ'),('Atlantic Highlands','NJ'),('Atlantic Mine','MI'),('Atlas','MI'),('Atlasburg','PA'),('Atmore','AL'),('Atoka','OK'),('Atoka','TN'),('Atomic City','ID'),('Atqasuk','AK'),('Attalla','AL'),('Attapulgus','GA'),('Attica','IN'),('Attica','KS'),('Attica','MI'),('Attica','NY'),('Attica','OH'),('Attleboro','MA'),('Attleboro Falls','MA'),('Atwater','CA'),('Atwater','MN'),('Atwater','OH'),('Atwood','CA'),('Atwood','CO'),('Atwood','IL'),('Atwood','IN'),('Atwood','KS'),('Atwood','OK'),('Atwood','TN'),('Au Gres','MI'),('Au Sable Forks','NY'),('Au Train','MI'),('Auberry','CA'),('Aubrey','AR'),('Aubrey','TX'),('Auburn','AL'),('Auburn','CA'),('Auburn','GA'),('Auburn','IA'),('Auburn','IL'),('Auburn','IN'),('Auburn','KS'),('Auburn','KY'),('Auburn','MA'),('Auburn','ME'),('Auburn','MI'),('Auburn','NE'),('Auburn','NH'),('Auburn','NY'),('Auburn','PA'),('Auburn','WA'),('Auburn','WV'),('Auburn','WY'),('Auburn Hills','MI'),('Auburn University','AL'),('Auburndale','FL'),('Auburndale','MA'),('Auburndale','WI'),('Auburntown','TN'),('Audubon','IA'),('Audubon','MN'),('Audubon','NJ'),('Audubon','PA'),('Augusta','AR'),('Augusta','GA'),('Augusta','IL'),('Augusta','KS'),('Augusta','KY'),('Augusta','ME'),('Augusta','MI'),('Augusta','MO'),('Augusta','MT'),('Augusta','NJ'),('Augusta','OH'),('Augusta','WI'),('Augusta','WV'),('Augusta Springs','VA'),('Auke Bay','AK'),('Aulander','NC'),('Ault','CO'),('Aultman','PA'),('Aumsville','OR'),('Aurelia','IA'),('Auriesville','NY'),('Aurora','CO'),('Aurora','IA'),('Aurora','IL'),('Aurora','IN'),('Aurora','KS'),('Aurora','ME'),('Aurora','MN'),('Aurora','MO'),('Aurora','NC'),('Aurora','NE'),('Aurora','NY'),('Aurora','OH'),('Aurora','OR'),('Aurora','SD'),('Aurora','UT'),('Aurora','WV'),('Austell','GA'),('Austerlitz','NY'),('Austin','AR'),('Austin','CO'),('Austin','IN'),('Austin','KY'),('Austin','MN'),('Austin','NV'),('Austin','PA'),('Austin','TX'),('Austinburg','OH'),('Austinville','IA'),('Austinville','VA'),('Austwell','TX'),('Autaugaville','AL'),('Autryville','NC'),('Auxier','KY'),('Auxvasse','MO'),('Ava','IL'),('Ava','MO'),('Ava','NY'),('Ava','OH'),('Avalon','CA'),('Avalon','NJ'),('Avalon','TX'),('Avalon','WI'),('Avant','OK'),('Avawam','KY'),('Avella','PA'),('Avenal','CA'),('Avenel','NJ'),('Avenue','MD'),('Avera','GA'),('Averill','VT'),('Averill Park','NY'),('Avery','CA'),('Avery','ID'),('Avery','TX'),('Avery Island','LA'),('Avila Beach','CA'),('Avilla','IN'),('Avilla','MO'),('Avinger','TX'),('Avis','PA'),('Aviston','IL'),('Avoca','AR'),('Avoca','IA'),('Avoca','IN'),('Avoca','MI'),('Avoca','MN'),('Avoca','NE'),('Avoca','NY'),('Avoca','TX'),('Avoca','WI'),('Avon','CO'),('Avon','CT'),('Avon','IL'),('Avon','IN'),('Avon','MA'),('Avon','MN'),('Avon','MS'),('Avon','MT'),('Avon','NC'),('Avon','NY'),('Avon','OH'),('Avon','SD'),('Avon By The Sea','NJ'),('Avon Lake','OH'),('Avon Park','FL'),('Avondale','AZ'),('Avondale','CO'),('Avondale','PA'),('Avondale','WV'),('Avondale Estates','GA'),('Avonmore','PA'),('Awendaw','SC'),('Axis','AL'),('Axson','GA'),('Axtell','KS'),('Axtell','NE'),('Axtell','TX'),('Axtell','UT'),('Axton','VA'),('Ayden','NC'),('Aydlett','NC'),('Ayer','MA'),('Aylett','VA'),('Aynor','SC'),('Ayr','ND'),('Ayr','NE'),('Ayrshire','IA'),('Azalea','OR'),('Azalia','MI'),('Azle','TX'),('Aztec','NM'),('Azusa','CA'),('Babb','MT'),('Babbitt','MN'),('Babcock','WI'),('Babson Park','FL'),('Babson Park','MA'),('Babylon','NY'),('Backus','MN'),('Bacliff','TX'),('Baconton','GA'),('Bacova','VA'),('Bad Axe','MI'),('Baden','PA'),('Badger','CA'),('Badger','IA'),('Badger','MN'),('Badger','SD'),('Badin','NC'),('Bagdad','AZ'),('Bagdad','FL'),('Bagdad','KY'),('Baggs','WY'),('Bagley','IA'),('Bagley','MN'),('Bagley','WI'),('Bagwell','TX'),('Bahama','NC'),('Bailey','CO'),('Bailey','MI'),('Bailey','MS'),('Bailey','NC'),('Bailey','TX'),('Bailey Island','ME'),('Baileys Harbor','WI'),('Baileyton','AL'),('Baileyville','IL'),('Baileyville','KS'),('Baileyville','ME'),('Bainbridge','GA'),('Bainbridge','IN'),('Bainbridge','NY'),('Bainbridge','OH'),('Bainbridge','PA'),('Bainbridge Island','WA'),('Bainville','MT'),('Baird','TX'),('Bairdford','PA'),('Bairoil','WY'),('Baisden','WV'),('Bajadero','PR'),('Baker','CA'),('Baker','FL'),('Baker','LA'),('Baker','MT'),('Baker','NV'),('Baker','WV'),('Baker City','OR'),('Bakers Mills','NY'),('Bakersfield','CA'),('Bakersfield','MO'),('Bakersfield','VT'),('Bakerstown','PA'),('Bakersville','NC'),('Bakersville','OH'),('Bakerton','WV'),('Bakewell','TN'),('Bala Cynwyd','PA'),('Balaton','MN'),('Bald Knob','AR'),('Baldwin','GA'),('Baldwin','IA'),('Baldwin','IL'),('Baldwin','LA'),('Baldwin','MD'),('Baldwin','MI'),('Baldwin','ND'),('Baldwin','NY'),('Baldwin','WI'),('Baldwin City','KS'),('Baldwin Park','CA'),('Baldwin Place','NY'),('Baldwinsville','NY'),('Baldwinville','MA'),('Baldwyn','MS'),('Balfour','ND'),('Balko','OK'),('Ball','LA'),('Ball Ground','GA'),('Ballantine','MT'),('Ballard','WV'),('Ballentine','SC'),('Ballico','CA'),('Ballinger','TX'),('Ballouville','CT'),('Ballston Lake','NY'),('Ballston Spa','NY'),('Ballwin','MO'),('Bally','PA'),('Balm','FL'),('Balmorhea','TX'),('Balsam','NC'),('Balsam Grove','NC'),('Balsam Lake','WI'),('Balta','ND'),('Baltic','CT'),('Baltic','OH'),('Baltic','SD'),('Baltimore','MD'),('Baltimore','OH'),('Bamberg','SC'),('Banco','VA'),('Bancroft','IA'),('Bancroft','ID'),('Bancroft','MI'),('Bancroft','NE'),('Bancroft','WI'),('Bancroft','WV'),('Bandana','KY'),('Bandera','TX'),('Bandon','OR'),('Bandy','VA'),('Bangall','NY'),('Bangor','CA'),('Bangor','ME'),('Bangor','MI'),('Bangor','PA'),('Bangor','WI'),('Bangs','TX'),('Banks','AL'),('Banks','AR'),('Banks','ID'),('Banks','OR'),('Bankston','AL'),('Banner','KY'),('Banner','MS'),('Banner','WY'),('Banner Elk','NC'),('Banning','CA'),('Bannister','MI'),('Bannock','OH'),('Banquete','TX'),('Bantam','CT'),('Bantry','ND'),('Bapchule','AZ'),('Baptistown','NJ'),('Bar Harbor','ME'),('Bar Mills','ME'),('Baraboo','WI'),('Baraga','MI'),('Barataria','LA'),('Barbeau','MI'),('Barberton','OH'),('Barberville','FL'),('Barboursville','VA'),('Barboursville','WV'),('Barbourville','KY'),('Barceloneta','PR'),('Barclay','MD'),('Barco','NC'),('Bard','CA'),('Bard','NM'),('Bardolph','IL'),('Bardstown','KY'),('Bardwell','KY'),('Bardwell','TX'),('Bargersville','IN'),('Barhamsville','VA'),('Baring','MO'),('Baring','WA'),('Barium Springs','NC'),('Bark River','MI'),('Barker','NY'),('Barker','TX'),('Barkhamsted','CT'); + +INSERT INTO [cities] VALUES ('Barksdale','TX'),('Barksdale Afb','LA'),('Barling','AR'),('Barlow','KY'),('Barlow','OH'),('Barnard','KS'),('Barnard','MO'),('Barnard','SD'),('Barnard','VT'),('Barnardsville','NC'),('Barnegat','NJ'),('Barnegat Light','NJ'),('Barnes','KS'),('Barnes City','IA'),('Barneston','NE'),('Barnesville','GA'),('Barnesville','MD'),('Barnesville','MN'),('Barnesville','NC'),('Barnesville','OH'),('Barnesville','PA'),('Barnet','VT'),('Barnett','MO'),('Barneveld','NY'),('Barneveld','WI'),('Barney','GA'),('Barney','ND'),('Barnhart','MO'),('Barnhart','TX'),('Barnhill','IL'),('Barnsdall','OK'),('Barnstable','MA'),('Barnstead','NH'),('Barnum','IA'),('Barnum','MN'),('Barnwell','SC'),('Baroda','MI'),('Barrackville','WV'),('Barranquitas','PR'),('Barre','MA'),('Barre','VT'),('Barren Springs','VA'),('Barrett','MN'),('Barrington','IL'),('Barrington','NH'),('Barrington','NJ'),('Barrington','RI'),('Barron','WI'),('Barronett','WI'),('Barrow','AK'),('Barry','IL'),('Barry','MN'),('Barry','TX'),('Barryton','MI'),('Barrytown','NY'),('Barryville','NY'),('Barstow','CA'),('Barstow','IL'),('Barstow','MD'),('Barstow','TX'),('Bart','PA'),('Bartelso','IL'),('Bartlesville','OK'),('Bartlett','IL'),('Bartlett','KS'),('Bartlett','NE'),('Bartlett','NH'),('Bartlett','OH'),('Bartlett','TX'),('Bartley','NE'),('Bartley','WV'),('Barto','PA'),('Barton','AR'),('Barton','MD'),('Barton','NY'),('Barton','OH'),('Barton','VT'),('Barton City','MI'),('Bartonsville','PA'),('Bartow','FL'),('Bartow','GA'),('Bartow','WV'),('Barwick','GA'),('Basalt','CO'),('Basalt','ID'),('Basco','IL'),('Bascom','FL'),('Bascom','OH'),('Basehor','KS'),('Basile','LA'),('Basin','MT'),('Basin','WY'),('Baskerville','VA'),('Baskett','KY'),('Baskin','LA'),('Basking Ridge','NJ'),('Basom','NY'),('Bass Harbor','ME'),('Bass Lake','CA'),('Bassett','AR'),('Bassett','NE'),('Bassett','VA'),('Bassett','WI'),('Bassfield','MS'),('Bastian','VA'),('Bastrop','LA'),('Bastrop','TX'),('Basye','VA'),('Bat Cave','NC'),('Batavia','IA'),('Batavia','IL'),('Batavia','NY'),('Batavia','OH'),('Batchelor','LA'),('Batchtown','IL'),('Bates','OR'),('Bates City','MO'),('Batesburg','SC'),('Batesland','SD'),('Batesville','AR'),('Batesville','IN'),('Batesville','MS'),('Batesville','TX'),('Batesville','VA'),('Bath','IL'),('Bath','IN'),('Bath','ME'),('Bath','MI'),('Bath','NC'),('Bath','NH'),('Bath','NY'),('Bath','OH'),('Bath','PA'),('Bath','SC'),('Bath','SD'),('Bath Springs','TN'),('Bathgate','ND'),('Baton Rouge','LA'),('Batson','TX'),('Battery Park','VA'),('Battiest','OK'),('Battle Creek','IA'),('Battle Creek','MI'),('Battle Creek','NE'),('Battle Ground','IN'),('Battle Ground','WA'),('Battle Lake','MN'),('Battle Mountain','NV'),('Battleboro','NC'),('Battlement Mesa','CO'),('Battletown','KY'),('Baudette','MN'),('Bausman','PA'),('Bauxite','AR'),('Baxley','GA'),('Baxter','IA'),('Baxter','KY'),('Baxter','MN'),('Baxter','TN'),('Baxter','WV'),('Baxter Springs','KS'),('Bay','AR'),('Bay Center','WA'),('Bay City','MI'),('Bay City','OR'),('Bay City','TX'),('Bay City','WI'),('Bay Minette','AL'),('Bay Pines','FL'),('Bay Port','MI'),('Bay Saint Louis','MS'),('Bay Shore','MI'),('Bay Shore','NY'),('Bay Springs','MS'),('Bay Village','OH'),('Bayamon','PR'),('Bayard','IA'),('Bayard','NE'),('Bayard','NM'),('Bayard','WV'),('Bayboro','NC'),('Bayfield','CO'),('Bayfield','WI'),('Baylis','IL'),('Bayonne','NJ'),('Bayou La Batre','AL'),('Bayport','MN'),('Bayport','NY'),('Bays','KY'),('Bayside','CA'),('Bayside','NY'),('Bayside','TX'),('Baytown','TX'),('Bayview','ID'),('Bayville','NJ'),('Bayville','NY'),('Bazine','KS'),('Beach','ND'),('Beach City','OH'),('Beach Haven','NJ'),('Beach Haven','PA'),('Beach Lake','PA'),('Beachwood','NJ'),('Beachwood','OH'),('Beacon','IA'),('Beacon','NY'),('Beacon Falls','CT'),('Beale Afb','CA'),('Bealeton','VA'),('Beallsville','MD'),('Beallsville','OH'),('Beallsville','PA'),('Beals','ME'),('Beaman','IA'),('Bean Station','TN'),('Bear','DE'),('Bear Branch','KY'),('Bear Creek','AL'),('Bear Creek','NC'),('Bear Creek','PA'),('Bear Creek','WI'),('Bear Lake','MI'),('Bear Lake','PA'),('Bear Mountain','NY'),('Bear River City','UT'),('Bearcreek','MT'),('Bearden','AR'),('Beardsley','MN'),('Beardstown','IL'),('Bearsville','NY'),('Beasley','TX'),('Beason','IL'),('Beatrice','AL'),('Beatrice','NE'),('Beattie','KS'),('Beatty','NV'),('Beatty','OR'),('Beattyville','KY'),('Beaufort','MO'),('Beaufort','NC'),('Beaufort','SC'),('Beaumont','CA'),('Beaumont','KS'),('Beaumont','KY'),('Beaumont','MS'),('Beaumont','TX'),('Beaumont','VA'),('Beauty','KY'),('Beaver','AK'),('Beaver','AR'),('Beaver','IA'),('Beaver','KY'),('Beaver','OH'),('Beaver','OK'),('Beaver','OR'),('Beaver','PA'),('Beaver','UT'),('Beaver','WA'),('Beaver','WV'),('Beaver Bay','MN'),('Beaver City','NE'),('Beaver Creek','MN'),('Beaver Crossing','NE'),('Beaver Dam','KY'),('Beaver Dam','WI'),('Beaver Dams','NY'),('Beaver Falls','NY'),('Beaver Falls','PA'),('Beaver Island','MI'),('Beaver Meadows','PA'),('Beaver Springs','PA'),('Beavercreek','OR'),('Beaverdale','PA'),('Beaverdam','OH'),('Beaverdam','VA'),('Beaverton','AL'),('Beaverton','MI'),('Beaverton','OR'),('Beavertown','PA'),('Beaverville','IL'),('Beccaria','PA'),('Bechtelsville','PA'),('Beckemeyer','IL'),('Becker','MN'),('Becker','MS'),('Becket','MA'),('Beckley','WV'),('Beckville','TX'),('Beckwourth','CA'),('Bedford','IA'),('Bedford','IN'),('Bedford','KY'),('Bedford','MA'),('Bedford','MI'),('Bedford','NH'),('Bedford','NY'),('Bedford','OH'),('Bedford','PA'),('Bedford','TX'),('Bedford','VA'),('Bedford','WY'),('Bedford Hills','NY'),('Bedford Park','IL'),('Bedias','TX'),('Bedminster','NJ'),('Bedminster','PA'),('Bedrock','CO'),('Bee','NE'),('Bee','VA'),('Bee Branch','AR'),('Bee Spring','KY'),('Beebe','AR'),('Beebe Plain','VT'),('Beech Bluff','TN'),('Beech Bottom','WV'),('Beech Creek','KY'),('Beech Creek','PA'),('Beech Grove','AR'),('Beech Grove','IN'),('Beech Grove','KY'),('Beech Island','SC'),('Beecher','IL'),('Beecher City','IL'),('Beecher Falls','VT'),('Beechgrove','TN'),('Beechmont','KY'),('Beedeville','AR'),('Beeler','KS'),('Beemer','NE'),('Beersheba Springs','TN'),('Beeson','WV'),('Beetown','WI'),('Beeville','TX'),('Beggs','OK'),('Beirne','AR'),('Bejou','MN'),('Bel Air','MD'),('Bel Alton','MD'),('Belcamp','MD'),('Belcher','KY'),('Belcher','LA'),('Belchertown','MA'),('Belcourt','ND'),('Belden','CA'),('Belden','MS'),('Belden','NE'),('Beldenville','WI'),('Belding','MI'),('Belen','MS'),('Belen','NM'),('Belews Creek','NC'),('Belfair','WA'),('Belfast','ME'),('Belfast','NY'),('Belfast','TN'),('Belfield','ND'),('Belford','NJ'),('Belfry','KY'),('Belfry','MT'),('Belgium','WI'),('Belgrade','ME'),('Belgrade','MN'),('Belgrade','MO'),('Belgrade','MT'),('Belgrade','NE'),('Belgrade Lakes','ME'),('Belhaven','NC'),('Belington','WV'),('Belk','AL'),('Belknap','IL'),('Bell','CA'),('Bell','FL'),('Bell Buckle','TN'),('Bell City','LA'),('Bell City','MO'),('Bell Gardens','CA'),('Bella Vista','AR'),('Bella Vista','CA'),('Bellaire','MI'),('Bellaire','OH'),('Bellaire','TX'),('Bellamy','AL'),('Bellarthur','NC'),('Bellbrook','OH'),('Belle','MO'),('Belle','WV'),('Belle Center','OH'),('Belle Chasse','LA'),('Belle Fourche','SD'),('Belle Glade','FL'),('Belle Haven','VA'),('Belle Mead','NJ'),('Belle Mina','AL'),('Belle Plaine','IA'),('Belle Plaine','KS'),('Belle Plaine','MN'),('Belle Rive','IL'),('Belle Rose','LA'),('Belle Valley','OH'),('Belle Vernon','PA'),('Belleair Beach','FL'),('Bellefontaine','MS'),('Bellefontaine','OH'),('Bellefonte','PA'),('Bellemont','AZ'),('Bellerose','NY'),('Belleview','FL'),('Belleview','MO'),('Belleville','AR'),('Belleville','IL'),('Belleville','KS'),('Belleville','MI'),('Belleville','NJ'),('Belleville','NY'),('Belleville','PA'),('Belleville','WI'),('Belleville','WV'),('Bellevue','IA'),('Bellevue','ID'),('Bellevue','KY'),('Bellevue','MI'),('Bellevue','NE'),('Bellevue','OH'),('Bellevue','TX'),('Bellevue','WA'),('Bellflower','CA'),('Bellflower','IL'),('Bellflower','MO'),('Bellingham','MA'),('Bellingham','MN'),('Bellingham','WA'),('Bellmawr','NJ'),('Bellmont','IL'),('Bellmore','IN'),('Bellmore','NY'),('Bellona','NY'),('Bellows Falls','VT'),('Bellport','NY'),('Bells','TN'),('Bells','TX'),('Bellvale','NY'),('Bellville','GA'),('Bellville','OH'),('Bellville','TX'),('Bellvue','CO'),('Bellwood','AL'),('Bellwood','IL'),('Bellwood','NE'),('Bellwood','PA'),('Belmar','NJ'),('Belmond','IA'),('Belmont','CA'),('Belmont','LA'),('Belmont','MA'),('Belmont','MI'),('Belmont','MS'),('Belmont','NC'),('Belmont','NH'),('Belmont','NY'),('Belmont','OH'),('Belmont','TX'),('Belmont','VT'),('Belmont','WA'),('Belmont','WI'),('Belmont','WV'),('Belmore','OH'),('Beloit','KS'),('Beloit','OH'),('Beloit','WI'),('Belpre','KS'),('Belpre','OH'),('Belsano','PA'),('Belspring','VA'),('Belt','MT'),('Belton','KY'),('Belton','MO'),('Belton','SC'),('Belton','TX'),('Beltrami','MN'),('Beltsville','MD'),('Belva','WV'),('Belvedere Tiburon','CA'),('Belvidere','IL'),('Belvidere','NC'),('Belvidere','NE'),('Belvidere','NJ'),('Belvidere','SD'),('Belvidere','TN'),('Belvidere Center','VT'),('Belview','MN'),('Belvue','KS'),('Belzoni','MS'),('Bement','IL'),('Bemidji','MN'),('Bemus Point','NY'),('Ben Bolt','TX'),('Ben Franklin','TX'),('Ben Hur','VA'),('Ben Lomond','AR'),('Ben Lomond','CA'); + +INSERT INTO [cities] VALUES ('Ben Wheeler','TX'),('Bena','MN'),('Bena','VA'),('Benavides','TX'),('Bend','OR'),('Bend','TX'),('Bendena','KS'),('Bendersville','PA'),('Benedict','KS'),('Benedict','MD'),('Benedict','MN'),('Benedict','ND'),('Benedict','NE'),('Benedicta','ME'),('Benet Lake','WI'),('Benezett','PA'),('Benge','WA'),('Benham','KY'),('Benicia','CA'),('Benjamin','TX'),('Benkelman','NE'),('Benld','IL'),('Bennet','NE'),('Bennett','CO'),('Bennett','IA'),('Bennett','NC'),('Bennettsville','SC'),('Bennington','IN'),('Bennington','KS'),('Bennington','NE'),('Bennington','NH'),('Bennington','OK'),('Bennington','VT'),('Benoit','MS'),('Benoit','WI'),('Bensalem','PA'),('Bensenville','IL'),('Benson','AZ'),('Benson','IL'),('Benson','MD'),('Benson','MN'),('Benson','NC'),('Benson','VT'),('Bent','NM'),('Bent Mountain','VA'),('Bentley','KS'),('Bentley','LA'),('Bentley','MI'),('Bentleyville','PA'),('Benton','AR'),('Benton','CA'),('Benton','IA'),('Benton','IL'),('Benton','KS'),('Benton','KY'),('Benton','LA'),('Benton','MO'),('Benton','MS'),('Benton','PA'),('Benton','TN'),('Benton','WI'),('Benton City','MO'),('Benton City','WA'),('Benton Harbor','MI'),('Benton Ridge','OH'),('Bentonia','MS'),('Bentonville','AR'),('Bentonville','IN'),('Bentonville','OH'),('Bentonville','VA'),('Benwood','WV'),('Benzonia','MI'),('Berclair','TX'),('Berea','KY'),('Berea','OH'),('Berea','WV'),('Beresford','SD'),('Bergen','NY'),('Bergenfield','NJ'),('Berger','MO'),('Bergheim','TX'),('Bergholz','OH'),('Bergland','MI'),('Bergman','AR'),('Bergoo','WV'),('Bergton','VA'),('Berino','NM'),('Berkeley','CA'),('Berkeley','IL'),('Berkeley Heights','NJ'),('Berkeley Springs','WV'),('Berkey','OH'),('Berkley','MA'),('Berkley','MI'),('Berkshire','MA'),('Berkshire','NY'),('Berlin','CT'),('Berlin','GA'),('Berlin','MA'),('Berlin','MD'),('Berlin','ND'),('Berlin','NH'),('Berlin','NJ'),('Berlin','NY'),('Berlin','OH'),('Berlin','PA'),('Berlin','WI'),('Berlin Center','OH'),('Berlin Heights','OH'),('Bern','ID'),('Bern','KS'),('Bernalillo','NM'),('Bernard','IA'),('Bernard','ME'),('Bernardston','MA'),('Bernardsville','NJ'),('Berne','IN'),('Berne','NY'),('Bernhards Bay','NY'),('Bernice','LA'),('Bernie','MO'),('Bernville','PA'),('Berrien Center','MI'),('Berrien Springs','MI'),('Berry','AL'),('Berry','KY'),('Berry Creek','CA'),('Berrysburg','PA'),('Berryton','KS'),('Berryville','AR'),('Berryville','VA'),('Bertha','MN'),('Berthold','ND'),('Berthoud','CO'),('Bertram','TX'),('Bertrand','MO'),('Bertrand','NE'),('Berwick','IA'),('Berwick','IL'),('Berwick','LA'),('Berwick','ME'),('Berwick','PA'),('Berwind','WV'),('Berwyn','IL'),('Berwyn','PA'),('Beryl','UT'),('Bessemer','AL'),('Bessemer','MI'),('Bessemer','PA'),('Bessemer City','NC'),('Bessie','OK'),('Bethalto','IL'),('Bethania','NC'),('Bethany','CT'),('Bethany','IL'),('Bethany','KY'),('Bethany','LA'),('Bethany','MO'),('Bethany','OK'),('Bethany','WV'),('Bethany Beach','DE'),('Bethel','AK'),('Bethel','CT'),('Bethel','DE'),('Bethel','ME'),('Bethel','MN'),('Bethel','MO'),('Bethel','NC'),('Bethel','NY'),('Bethel','OH'),('Bethel','OK'),('Bethel','PA'),('Bethel','VT'),('Bethel Island','CA'),('Bethel Park','PA'),('Bethel Springs','TN'),('Bethelridge','KY'),('Bethera','SC'),('Bethesda','MD'),('Bethesda','OH'),('Bethlehem','CT'),('Bethlehem','GA'),('Bethlehem','IN'),('Bethlehem','KY'),('Bethlehem','MD'),('Bethlehem','NH'),('Bethlehem','PA'),('Bethpage','NY'),('Bethpage','TN'),('Bethune','CO'),('Bethune','SC'),('Betsy Layne','KY'),('Bettendorf','IA'),('Betterton','MD'),('Bettles Field','AK'),('Bettsville','OH'),('Beulah','CO'),('Beulah','MI'),('Beulah','MO'),('Beulah','MS'),('Beulah','ND'),('Beulah','WY'),('Beulaville','NC'),('Beverly','KS'),('Beverly','KY'),('Beverly','MA'),('Beverly','NJ'),('Beverly','OH'),('Beverly','WA'),('Beverly','WV'),('Beverly Hills','CA'),('Beverly Hills','FL'),('Beverly Shores','IN'),('Bevier','MO'),('Bevington','IA'),('Bevinsville','KY'),('Bexar','AR'),('Beyer','PA'),('Bible School Park','NY'),('Bickleton','WA'),('Bickmore','WV'),('Bicknell','IN'),('Bicknell','UT'),('Biddeford','ME'),('Biddeford Pool','ME'),('Biddle','MT'),('Bidwell','OH'),('Bieber','CA'),('Bienville','LA'),('Big Arm','MT'),('Big Bar','CA'),('Big Bay','MI'),('Big Bear City','CA'),('Big Bear Lake','CA'),('Big Bend','CA'),('Big Bend','WI'),('Big Bend','WV'),('Big Bend National Park','TX'),('Big Cabin','OK'),('Big Clifty','KY'),('Big Cove Tannery','PA'),('Big Creek','CA'),('Big Creek','KY'),('Big Creek','MS'),('Big Creek','WV'),('Big Falls','MN'),('Big Falls','WI'),('Big Flat','AR'),('Big Flats','NY'),('Big Horn','WY'),('Big Indian','NY'),('Big Island','VA'),('Big Lake','AK'),('Big Lake','MN'),('Big Lake','TX'),('Big Laurel','KY'),('Big Oak Flat','CA'),('Big Pine','CA'),('Big Pine Key','FL'),('Big Piney','WY'),('Big Pool','MD'),('Big Prairie','OH'),('Big Rapids','MI'),('Big Rock','IL'),('Big Rock','TN'),('Big Rock','VA'),('Big Run','PA'),('Big Run','WV'),('Big Sandy','MT'),('Big Sandy','TN'),('Big Sandy','TX'),('Big Sandy','WV'),('Big Sky','MT'),('Big Spring','TX'),('Big Springs','NE'),('Big Springs','WV'),('Big Stone City','SD'),('Big Stone Gap','VA'),('Big Sur','CA'),('Big Timber','MT'),('Big Wells','TX'),('Bigelow','AR'),('Bigelow','MN'),('Bigfoot','TX'),('Bigfork','MN'),('Bigfork','MT'),('Biggers','AR'),('Biggs','CA'),('Biggsville','IL'),('Bighill','KY'),('Bighorn','MT'),('Bigler','PA'),('Biglerville','PA'),('Billerica','MA'),('Billings','MO'),('Billings','MT'),('Billings','NY'),('Billings','OK'),('Billingsley','AL'),('Biloxi','MS'),('Bim','WV'),('Bimble','KY'),('Binford','ND'),('Bingen','WA'),('Binger','OK'),('Bingham','IL'),('Bingham','ME'),('Bingham','NE'),('Bingham Canyon','UT'),('Bingham Lake','MN'),('Binghamton','NY'),('Biola','CA'),('Bippus','IN'),('Birch Harbor','ME'),('Birch River','WV'),('Birch Run','MI'),('Birch Tree','MO'),('Birchdale','MN'),('Birchleaf','VA'),('Birchrunville','PA'),('Birchwood','TN'),('Birchwood','WI'),('Bird City','KS'),('Bird In Hand','PA'),('Bird Island','MN'),('Birds Landing','CA'),('Birdsboro','PA'),('Birdseye','IN'),('Birdsnest','VA'),('Birmingham','AL'),('Birmingham','IA'),('Birmingham','MI'),('Birmingham','NJ'),('Birmingham','OH'),('Birnamwood','WI'),('Birney','MT'),('Bisbee','AZ'),('Bisbee','ND'),('Biscoe','AR'),('Biscoe','NC'),('Bishop','CA'),('Bishop','GA'),('Bishop','TX'),('Bishop','VA'),('Bishop Hill','IL'),('Bishopville','MD'),('Bishopville','SC'),('Bismarck','AR'),('Bismarck','IL'),('Bismarck','MO'),('Bismarck','ND'),('Bison','KS'),('Bison','OK'),('Bison','SD'),('Bitely','MI'),('Bittinger','MD'),('Bivalve','MD'),('Bivins','TX'),('Biwabik','MN'),('Bixby','MO'),('Bixby','OK'),('Blachly','OR'),('Black','AL'),('Black','MO'),('Black Canyon City','AZ'),('Black Creek','NC'),('Black Creek','NY'),('Black Creek','WI'),('Black Diamond','WA'),('Black Eagle','MT'),('Black Earth','WI'),('Black Hawk','CO'),('Black Hawk','SD'),('Black Lick','PA'),('Black Mountain','NC'),('Black Oak','AR'),('Black River','MI'),('Black River','NY'),('Black River Falls','WI'),('Black Rock','AR'),('Blackburn','MO'),('Blackduck','MN'),('Blackey','KY'),('Blackfoot','ID'),('Blackford','KY'),('Blacklick','OH'),('Blacksburg','SC'),('Blacksburg','VA'),('Blackshear','GA'),('Blackstock','SC'),('Blackstone','IL'),('Blackstone','MA'),('Blackstone','VA'),('Blacksville','WV'),('Blackville','SC'),('Blackwater','MO'),('Blackwater','VA'),('Blackwell','MO'),('Blackwell','OK'),('Blackwell','TX'),('Blackwood','NJ'),('Bladen','NE'),('Bladenboro','NC'),('Bladensburg','MD'),('Bladensburg','OH'),('Blain','PA'),('Blaine','KY'),('Blaine','ME'),('Blaine','OH'),('Blaine','TN'),('Blaine','WA'),('Blair','NE'),('Blair','OK'),('Blair','SC'),('Blair','WI'),('Blair','WV'),('Blairs','VA'),('Blairs Mills','PA'),('Blairsburg','IA'),('Blairsden-graeagle','CA'),('Blairstown','IA'),('Blairstown','MO'),('Blairstown','NJ'),('Blairsville','GA'),('Blairsville','PA'),('Blakely','GA'),('Blakely Island','WA'),('Blakesburg','IA'),('Blakeslee','OH'),('Blakeslee','PA'),('Blanca','CO'),('Blanch','NC'),('Blanchard','IA'),('Blanchard','ID'),('Blanchard','LA'),('Blanchard','MI'),('Blanchard','ND'),('Blanchard','OK'),('Blanchard','PA'),('Blanchardville','WI'),('Blanchester','OH'),('Blanco','NM'),('Blanco','OK'),('Blanco','TX'),('Bland','MO'),('Bland','VA'),('Blandburg','PA'),('Blandford','MA'),('Blanding','UT'),('Blandinsville','IL'),('Blandon','PA'),('Blanford','IN'),('Blanket','TX'),('Blauvelt','NY'),('Blawenburg','NJ'),('Bledsoe','KY'),('Bledsoe','TX'),('Bleiblerville','TX'),('Blencoe','IA'),('Blenheim','SC'),('Blenker','WI'),('Blessing','TX'),('Blevins','AR'),('Bliss','ID'),('Bliss','NY'),('Blissfield','MI'),('Blissfield','OH'),('Block Island','RI'),('Blocker','OK'),('Blocksburg','CA'),('Blockton','IA'),('Blodgett','MO'),('Blodgett','OR'),('Blodgett Mills','NY'),('Blomkest','MN'),('Bloomburg','TX'),('Bloomdale','OH'),('Bloomer','WI'),('Bloomery','WV'),('Bloomfield','CT'),('Bloomfield','IA'),('Bloomfield','IN'),('Bloomfield','KY'),('Bloomfield','MO'),('Bloomfield','MT'),('Bloomfield','NE'),('Bloomfield','NJ'),('Bloomfield','NM'),('Bloomfield','NY'),('Bloomfield Hills','MI'),('Blooming Glen','PA'),('Blooming Grove','NY'),('Blooming Grove','TX'),('Blooming Prairie','MN'),('Bloomingburg','NY'),('Bloomingburg','OH'),('Bloomingdale','GA'),('Bloomingdale','IL'),('Bloomingdale','IN'),('Bloomingdale','MI'); + +INSERT INTO [cities] VALUES ('Bloomingdale','NJ'),('Bloomingdale','NY'),('Bloomingdale','OH'),('Bloomingrose','WV'),('Bloomington','CA'),('Bloomington','ID'),('Bloomington','IL'),('Bloomington','IN'),('Bloomington','MD'),('Bloomington','NE'),('Bloomington','NY'),('Bloomington','TX'),('Bloomington','WI'),('Bloomington Springs','TN'),('Bloomsburg','PA'),('Bloomsbury','NJ'),('Bloomsdale','MO'),('Bloomville','NY'),('Bloomville','OH'),('Blossburg','PA'),('Blossom','TX'),('Blossvale','NY'),('Blount','WV'),('Blounts Creek','NC'),('Blountstown','FL'),('Blountsville','AL'),('Blountville','TN'),('Blowing Rock','NC'),('Bloxom','VA'),('Blue','AZ'),('Blue Ball','PA'),('Blue Bell','PA'),('Blue Creek','OH'),('Blue Creek','WV'),('Blue Diamond','NV'),('Blue Earth','MN'),('Blue Eye','MO'),('Blue Gap','AZ'),('Blue Grass','IA'),('Blue Grass','VA'),('Blue Hill','ME'),('Blue Hill','NE'),('Blue Island','IL'),('Blue Jay','CA'),('Blue Jay','WV'),('Blue Lake','CA'),('Blue Mound','IL'),('Blue Mound','KS'),('Blue Mounds','WI'),('Blue Mountain','AR'),('Blue Mountain','MS'),('Blue Mountain Lake','NY'),('Blue Point','NY'),('Blue Rapids','KS'),('Blue Ridge','GA'),('Blue Ridge','TX'),('Blue Ridge','VA'),('Blue Ridge Summit','PA'),('Blue River','KY'),('Blue River','OR'),('Blue River','WI'),('Blue Rock','OH'),('Blue Springs','MO'),('Blue Springs','MS'),('Blue Springs','NE'),('Bluebell','UT'),('Bluefield','VA'),('Bluefield','WV'),('Bluegrove','TX'),('Bluejacket','OK'),('Bluemont','VA'),('Bluewater','NM'),('Bluff','UT'),('Bluff City','AR'),('Bluff City','KS'),('Bluff City','TN'),('Bluff Dale','TX'),('Bluff Springs','IL'),('Bluffs','IL'),('Bluffton','AR'),('Bluffton','GA'),('Bluffton','IN'),('Bluffton','MN'),('Bluffton','OH'),('Bluffton','SC'),('Bluffton','TX'),('Bluford','IL'),('Blum','TX'),('Blunt','SD'),('Bly','OR'),('Blythe','CA'),('Blythe','GA'),('Blythedale','MO'),('Blytheville','AR'),('Blythewood','SC'),('Boalsburg','PA'),('Board Camp','AR'),('Boardman','OR'),('Boaz','AL'),('Boaz','KY'),('Bob White','WV'),('Bobtown','PA'),('Boca Grande','FL'),('Boca Raton','FL'),('Bock','MN'),('Bode','IA'),('Bodega','CA'),('Bodega Bay','CA'),('Bodfish','CA'),('Boelus','NE'),('Boerne','TX'),('Bogalusa','LA'),('Bogard','MO'),('Bogart','GA'),('Bogata','TX'),('Boggstown','IN'),('Bogota','NJ'),('Bogota','TN'),('Bogue','KS'),('Bogue Chitto','MS'),('Bohannon','VA'),('Bohemia','NY'),('Boiceville','NY'),('Boiling Springs','NC'),('Boiling Springs','PA'),('Boiling Springs','SC'),('Bois D Arc','MO'),('Boise','ID'),('Boise City','OK'),('Boissevain','VA'),('Bokchito','OK'),('Bokeelia','FL'),('Bokoshe','OK'),('Bolckow','MO'),('Boles','AR'),('Boles','IL'),('Boley','OK'),('Boligee','AL'),('Bolinas','CA'),('Boling','TX'),('Bolingbroke','GA'),('Bolingbrook','IL'),('Bolivar','MO'),('Bolivar','NY'),('Bolivar','OH'),('Bolivar','PA'),('Bolivar','TN'),('Bolivia','NC'),('Bolt','WV'),('Bolton','CT'),('Bolton','MA'),('Bolton','MS'),('Bolton','NC'),('Bolton Landing','NY'),('Bombay','NY'),('Bomont','WV'),('Bomoseen','VT'),('Bon Air','AL'),('Bon Aqua','TN'),('Bon Secour','AL'),('Bon Wier','TX'),('Bonaire','GA'),('Bonanza','OR'),('Bonanza','UT'),('Bonaparte','IA'),('Boncarbo','CO'),('Bond','CO'),('Bondsville','MA'),('Bonduel','WI'),('Bondurant','IA'),('Bondurant','WY'),('Bondville','IL'),('Bondville','VT'),('Bone Gap','IL'),('Bonesteel','SD'),('Boneville','GA'),('Bonfield','IL'),('Bonham','TX'),('Bonifay','FL'),('Bonita','CA'),('Bonita','LA'),('Bonita Springs','FL'),('Bonlee','NC'),('Bonne Terre','MO'),('Bonneau','SC'),('Bonner','MT'),('Bonner Springs','KS'),('Bonnerdale','AR'),('Bonners Ferry','ID'),('Bonney Lake','WA'),('Bonnie','IL'),('Bonnieville','KY'),('Bonnots Mill','MO'),('Bonnyman','KY'),('Bono','AR'),('Bonsall','CA'),('Boody','IL'),('Booker','TX'),('Boomer','NC'),('Boomer','WV'),('Boon','MI'),('Boone','CO'),('Boone','IA'),('Boone','NC'),('Boone Grove','IN'),('Boones Mill','VA'),('Booneville','AR'),('Booneville','IA'),('Booneville','KY'),('Booneville','MS'),('Boons Camp','KY'),('Boonsboro','MD'),('Boonton','NJ'),('Boonville','CA'),('Boonville','IN'),('Boonville','MO'),('Boonville','NC'),('Boonville','NY'),('Booth','AL'),('Boothbay','ME'),('Boothbay Harbor','ME'),('Boothville','LA'),('Boqueron','PR'),('Bordelonville','LA'),('Borden','IN'),('Bordentown','NJ'),('Borderland','WV'),('Borger','TX'),('Boring','MD'),('Boring','OR'),('Boron','CA'),('Borrego Springs','CA'),('Borup','MN'),('Boscobel','WI'),('Bosler','WY'),('Bosque','NM'),('Bosque Farms','NM'),('Boss','MO'),('Bossier City','LA'),('Bostic','NC'),('Boston','GA'),('Boston','IN'),('Boston','KY'),('Boston','MA'),('Boston','NY'),('Boston','VA'),('Bostwick','FL'),('Bostwick','GA'),('Boswell','IN'),('Boswell','OK'),('Boswell','PA'),('Bosworth','MO'),('Bothell','WA'),('Botkins','OH'),('Botsford','CT'),('Bottineau','ND'),('Bouckville','NY'),('Boulder','CO'),('Boulder','MT'),('Boulder','UT'),('Boulder','WY'),('Boulder City','NV'),('Boulder Creek','CA'),('Boulder Junction','WI'),('Boulevard','CA'),('Bound Brook','NJ'),('Bountiful','UT'),('Bourbon','IN'),('Bourbon','MO'),('Bourbonnais','IL'),('Bourg','LA'),('Bourneville','OH'),('Bouse','AZ'),('Bouton','IA'),('Boutte','LA'),('Bovard','PA'),('Bovey','MN'),('Bovill','ID'),('Bovina','TX'),('Bovina Center','NY'),('Bow','NH'),('Bow','WA'),('Bowbells','ND'),('Bowden','WV'),('Bowdle','SD'),('Bowdoin','ME'),('Bowdoinham','ME'),('Bowdon','GA'),('Bowdon','ND'),('Bowdon Junction','GA'),('Bowen','IL'),('Bowers','PA'),('Bowerston','OH'),('Bowersville','GA'),('Bowersville','OH'),('Bowie','AZ'),('Bowie','MD'),('Bowie','TX'),('Bowlegs','OK'),('Bowler','WI'),('Bowling Green','FL'),('Bowling Green','IN'),('Bowling Green','KY'),('Bowling Green','MO'),('Bowling Green','OH'),('Bowling Green','SC'),('Bowling Green','VA'),('Bowlus','MN'),('Bowman','GA'),('Bowman','ND'),('Bowman','SC'),('Bowmansdale','PA'),('Bowmanstown','PA'),('Bowmansville','NY'),('Bowmansville','PA'),('Bowstring','MN'),('Box Elder','MT'),('Box Elder','SD'),('Box Springs','GA'),('Boxborough','MA'),('Boxford','MA'),('Boxholm','IA'),('Boyce','LA'),('Boyce','VA'),('Boyceville','WI'),('Boyd','MN'),('Boyd','MT'),('Boyd','TX'),('Boyd','WI'),('Boyden','IA'),('Boyds','MD'),('Boyds','WA'),('Boydton','VA'),('Boyers','PA'),('Boyertown','PA'),('Boyes','MT'),('Boyes Hot Springs','CA'),('Boykin','AL'),('Boykins','VA'),('Boyle','MS'),('Boylston','MA'),('Boyne City','MI'),('Boyne Falls','MI'),('Boynton','OK'),('Boynton','PA'),('Boynton Beach','FL'),('Boys Ranch','TX'),('Boys Town','NE'),('Bozeman','MT'),('Bozman','MD'),('Bozrah','CT'),('Braceville','IL'),('Bracey','VA'),('Brackenridge','PA'),('Brackettville','TX'),('Brackney','PA'),('Braddock','ND'),('Braddock','PA'),('Braddock Heights','MD'),('Braddyville','IA'),('Braden','TN'),('Bradenton','FL'),('Bradenton Beach','FL'),('Bradenville','PA'),('Bradford','AR'),('Bradford','IA'),('Bradford','IL'),('Bradford','IN'),('Bradford','ME'),('Bradford','NH'),('Bradford','NY'),('Bradford','OH'),('Bradford','PA'),('Bradford','RI'),('Bradford','TN'),('Bradford','VT'),('Bradfordsville','KY'),('Bradfordwoods','PA'),('Bradgate','IA'),('Bradley','AR'),('Bradley','CA'),('Bradley','FL'),('Bradley','IL'),('Bradley','ME'),('Bradley','MI'),('Bradley','OK'),('Bradley','SC'),('Bradley','SD'),('Bradley','WV'),('Bradley Beach','NJ'),('Bradleyville','MO'),('Bradner','OH'),('Bradshaw','NE'),('Bradshaw','WV'),('Brady','MT'),('Brady','NE'),('Brady','TX'),('Brady Lake','OH'),('Bradyville','TN'),('Bragg City','MO'),('Braggadocio','MO'),('Braggs','OK'),('Braham','MN'),('Braidwood','IL'),('Brainard','NE'),('Brainard','NY'),('Brainardsville','NY'),('Brainerd','MN'),('Braintree','MA'),('Braithwaite','LA'),('Braman','OK'),('Bramwell','WV'),('Branch','AR'),('Branch','LA'),('Branch','MI'),('Branchdale','PA'),('Branchland','WV'),('Branchport','NY'),('Branchton','PA'),('Branchville','IN'),('Branchville','NJ'),('Branchville','SC'),('Branchville','VA'),('Brandamore','PA'),('Brandeis','CA'),('Brandenburg','KY'),('Brandon','FL'),('Brandon','IA'),('Brandon','MN'),('Brandon','MS'),('Brandon','SD'),('Brandon','TX'),('Brandon','VT'),('Brandon','WI'),('Brandsville','MO'),('Brandt','SD'),('Brandy Camp','PA'),('Brandy Station','VA'),('Brandywine','MD'),('Brandywine','WV'),('Branford','CT'),('Branford','FL'),('Branscomb','CA'),('Branson','CO'),('Branson','MO'),('Brant','MI'),('Brant','NY'),('Brant Lake','NY'),('Brant Rock','MA'),('Brantingham','NY'),('Brantley','AL'),('Brantwood','WI'),('Braselton','GA'),('Brashear','MO'),('Brashear','TX'),('Brasher Falls','NY'),('Brasstown','NC'),('Brattleboro','VT'),('Brave','PA'),('Brawley','CA'),('Braxton','MS'),('Braymer','MO'),('Brayton','IA'),('Brazeau','MO'),('Brazil','IN'),('Brazoria','TX'),('Brea','CA'),('Breaks','VA'),('Breaux Bridge','LA'),('Breckenridge','CO'),('Breckenridge','MI'),('Breckenridge','MN'),('Breckenridge','MO'),('Breckenridge','TX'),('Brecksville','OH'),('Breda','IA'),('Breeden','WV'),('Breeding','KY'),('Breedsville','MI'),('Breese','IL'),('Breesport','NY'),('Breezewood','PA'),('Breezy Point','NY'),('Breinigsville','PA'),('Bremen','AL'),('Bremen','GA'),('Bremen','IN'),('Bremen','KS'),('Bremen','KY'),('Bremen','ME'),('Bremen','ND'),('Bremerton','WA'),('Bremo Bluff','VA'),('Bremond','TX'),('Brenham','TX'),('Brent','AL'),('Brentford','SD'),('Brenton','WV'),('Brentwood','CA'),('Brentwood','MD'); + +INSERT INTO [cities] VALUES ('Brentwood','NY'),('Brentwood','TN'),('Brethren','MI'),('Bretton Woods','NH'),('Bretz','WV'),('Brevard','NC'),('Brevig Mission','AK'),('Brewer','ME'),('Brewerton','NY'),('Brewster','KS'),('Brewster','MA'),('Brewster','MN'),('Brewster','NE'),('Brewster','NY'),('Brewster','OH'),('Brewster','WA'),('Brewton','AL'),('Brian Head','UT'),('Briar','MO'),('Briarcliff Manor','NY'),('Brice','OH'),('Bricelyn','MN'),('Briceville','TN'),('Brick','NJ'),('Brickeys','AR'),('Bridal Veil','OR'),('Bridge City','TX'),('Bridgehampton','NY'),('Bridgeport','AL'),('Bridgeport','CA'),('Bridgeport','CT'),('Bridgeport','IL'),('Bridgeport','MI'),('Bridgeport','NE'),('Bridgeport','NJ'),('Bridgeport','NY'),('Bridgeport','OH'),('Bridgeport','OR'),('Bridgeport','PA'),('Bridgeport','TX'),('Bridgeport','WA'),('Bridgeport','WV'),('Bridger','MT'),('Bridgeton','IN'),('Bridgeton','MO'),('Bridgeton','NC'),('Bridgeton','NJ'),('Bridgeview','IL'),('Bridgeville','CA'),('Bridgeville','DE'),('Bridgeville','PA'),('Bridgewater','CT'),('Bridgewater','IA'),('Bridgewater','MA'),('Bridgewater','ME'),('Bridgewater','MI'),('Bridgewater','NJ'),('Bridgewater','NY'),('Bridgewater','SD'),('Bridgewater','VA'),('Bridgewater','VT'),('Bridgewater Corners','VT'),('Bridgman','MI'),('Bridgton','ME'),('Bridport','VT'),('Brielle','NJ'),('Brier Hill','NY'),('Brier Hill','PA'),('Brierfield','AL'),('Brigantine','NJ'),('Briggs','TX'),('Briggsdale','CO'),('Briggsville','AR'),('Briggsville','WI'),('Brigham City','UT'),('Brighton','CO'),('Brighton','IA'),('Brighton','IL'),('Brighton','MA'),('Brighton','MI'),('Brighton','MO'),('Brighton','TN'),('Brightwaters','NY'),('Brightwood','OR'),('Brightwood','VA'),('Brill','WI'),('Brilliant','AL'),('Brilliant','OH'),('Brillion','WI'),('Brimfield','IL'),('Brimfield','MA'),('Brimhall','NM'),('Brimley','MI'),('Brimson','MN'),('Bringhurst','IN'),('Brinkhaven','OH'),('Brinkley','AR'),('Brinklow','MD'),('Brinktown','MO'),('Brinnon','WA'),('Brinson','GA'),('Brisbane','CA'),('Brisbin','PA'),('Briscoe','TX'),('Bristol','CT'),('Bristol','FL'),('Bristol','GA'),('Bristol','IL'),('Bristol','IN'),('Bristol','ME'),('Bristol','NH'),('Bristol','PA'),('Bristol','RI'),('Bristol','SD'),('Bristol','TN'),('Bristol','VA'),('Bristol','VT'),('Bristol','WI'),('Bristolville','OH'),('Bristow','IA'),('Bristow','IN'),('Bristow','NE'),('Bristow','OK'),('Bristow','VA'),('Britt','IA'),('Britt','MN'),('Brittany','LA'),('Britton','MI'),('Britton','SD'),('Brixey','MO'),('Broad Brook','CT'),('Broad Run','VA'),('Broad Top','PA'),('Broadalbin','NY'),('Broadbent','OR'),('Broaddus','TX'),('Broadford','VA'),('Broadlands','IL'),('Broadus','MT'),('Broadview','IL'),('Broadview','MT'),('Broadview','NM'),('Broadview Heights','OH'),('Broadwater','NE'),('Broadway','NC'),('Broadway','NJ'),('Broadway','OH'),('Broadway','VA'),('Brock','NE'),('Brocket','ND'),('Brockport','NY'),('Brockport','PA'),('Brockton','MA'),('Brockton','MT'),('Brockton','PA'),('Brockway','MT'),('Brockway','PA'),('Brockwell','AR'),('Brocton','IL'),('Brocton','NY'),('Brodhead','KY'),('Brodhead','WI'),('Brodheadsville','PA'),('Brodnax','VA'),('Brogan','OR'),('Brogue','PA'),('Brohard','WV'),('Brohman','MI'),('Brokaw','WI'),('Broken Arrow','OK'),('Broken Bow','NE'),('Broken Bow','OK'),('Bromide','OK'),('Bronaugh','MO'),('Bronson','FL'),('Bronson','IA'),('Bronson','KS'),('Bronson','MI'),('Bronson','TX'),('Bronston','KY'),('Bronte','TX'),('Bronwood','GA'),('Bronx','NY'),('Bronxville','NY'),('Brook','IN'),('Brook Park','MN'),('Brook Park','OH'),('Brookdale','CA'),('Brooke','VA'),('Brookeland','TX'),('Brooker','FL'),('Brookesmith','TX'),('Brookeville','MD'),('Brookfield','CT'),('Brookfield','GA'),('Brookfield','IL'),('Brookfield','MA'),('Brookfield','MO'),('Brookfield','NY'),('Brookfield','OH'),('Brookfield','VT'),('Brookfield','WI'),('Brookhaven','MS'),('Brookhaven','NY'),('Brookhaven','PA'),('Brookings','OR'),('Brookings','SD'),('Brookland','AR'),('Brooklandville','MD'),('Brooklet','GA'),('Brooklin','ME'),('Brookline','MA'),('Brookline','MO'),('Brookline','NH'),('Brookline Village','MA'),('Brooklyn','AL'),('Brooklyn','CT'),('Brooklyn','IA'),('Brooklyn','IN'),('Brooklyn','MD'),('Brooklyn','MI'),('Brooklyn','MS'),('Brooklyn','NY'),('Brooklyn','PA'),('Brooklyn','WI'),('Brookneal','VA'),('Brookport','IL'),('Brooks','CA'),('Brooks','GA'),('Brooks','KY'),('Brooks','ME'),('Brooks','MN'),('Brookshire','TX'),('Brookside','AL'),('Brookside','NJ'),('Brookston','IN'),('Brookston','MN'),('Brookston','TX'),('Brooksville','FL'),('Brooksville','KY'),('Brooksville','ME'),('Brooksville','MS'),('Brookton','ME'),('Brooktondale','NY'),('Brookville','IN'),('Brookville','KS'),('Brookville','OH'),('Brookville','PA'),('Brookwood','AL'),('Broomall','PA'),('Broomes Island','MD'),('Broomfield','CO'),('Brooten','MN'),('Broseley','MO'),('Brothers','OR'),('Broughton','IL'),('Broussard','LA'),('Browder','KY'),('Browerville','MN'),('Brown City','MI'),('Brownell','KS'),('Brownfield','ME'),('Brownfield','PA'),('Brownfield','TX'),('Browning','IL'),('Browning','MO'),('Browning','MT'),('Browns','IL'),('Browns Mills','NJ'),('Browns Summit','NC'),('Browns Valley','CA'),('Browns Valley','MN'),('Brownsboro','AL'),('Brownsboro','TX'),('Brownsburg','IN'),('Brownsburg','VA'),('Brownsdale','MN'),('Brownstown','IL'),('Brownstown','IN'),('Brownstown','PA'),('Brownstown','WA'),('Brownsville','CA'),('Brownsville','IN'),('Brownsville','KY'),('Brownsville','MD'),('Brownsville','MN'),('Brownsville','OH'),('Brownsville','OR'),('Brownsville','PA'),('Brownsville','TN'),('Brownsville','TX'),('Brownsville','VT'),('Brownsville','WI'),('Brownton','MN'),('Browntown','WI'),('Brownville','ME'),('Brownville','NE'),('Brownville','NY'),('Brownville Junction','ME'),('Brownwood','MO'),('Brownwood','TX'),('Broxton','GA'),('Bruce','MS'),('Bruce','SD'),('Bruce','WI'),('Bruce Crossing','MI'),('Bruceton','TN'),('Bruceton Mills','WV'),('Brucetown','VA'),('Bruceville','IN'),('Bruceville','TX'),('Bruin','PA'),('Bruington','VA'),('Brule','NE'),('Brule','WI'),('Brumley','MO'),('Brundidge','AL'),('Bruneau','ID'),('Bruner','MO'),('Bruni','TX'),('Bruning','NE'),('Bruno','MN'),('Bruno','NE'),('Bruno','WV'),('Brunson','SC'),('Brunsville','IA'),('Brunswick','GA'),('Brunswick','MD'),('Brunswick','ME'),('Brunswick','MO'),('Brunswick','NC'),('Brunswick','NE'),('Brunswick','OH'),('Brunswick','TN'),('Brusett','MT'),('Brush','CO'),('Brush Creek','TN'),('Brush Prairie','WA'),('Brush Valley','PA'),('Brushton','NY'),('Brusly','LA'),('Brussels','IL'),('Brussels','WI'),('Brutus','MI'),('Bryan','OH'),('Bryan','TX'),('Bryans Road','MD'),('Bryant','AL'),('Bryant','AR'),('Bryant','FL'),('Bryant','IA'),('Bryant','IL'),('Bryant','IN'),('Bryant','SD'),('Bryant','WI'),('Bryant Pond','ME'),('Bryantown','MD'),('Bryants Store','KY'),('Bryantsville','KY'),('Bryantville','MA'),('Bryce','UT'),('Bryceville','FL'),('Bryn Athyn','PA'),('Bryn Mawr','CA'),('Bryn Mawr','PA'),('Bryson','TX'),('Bryson City','NC'),('Buchanan','GA'),('Buchanan','MI'),('Buchanan','ND'),('Buchanan','NY'),('Buchanan','TN'),('Buchanan','VA'),('Buchanan Dam','TX'),('Buchtel','OH'),('Buck Creek','IN'),('Buck Hill Falls','PA'),('Buckatunna','MS'),('Buckeye','AZ'),('Buckeye','IA'),('Buckeye','WV'),('Buckeye Lake','OH'),('Buckeystown','MD'),('Buckfield','ME'),('Buckhannon','WV'),('Buckhead','GA'),('Buckholts','TX'),('Buckhorn','KY'),('Buckhorn','NM'),('Buckingham','IA'),('Buckingham','IL'),('Buckingham','PA'),('Buckingham','VA'),('Buckland','AK'),('Buckland','MA'),('Buckland','OH'),('Buckley','IL'),('Buckley','MI'),('Buckley','WA'),('Bucklin','KS'),('Bucklin','MO'),('Buckman','MN'),('Buckner','AR'),('Buckner','IL'),('Buckner','KY'),('Buckner','MO'),('Bucks','AL'),('Buckskin','IN'),('Bucksport','ME'),('Bucoda','WA'),('Bucyrus','KS'),('Bucyrus','MO'),('Bucyrus','OH'),('Bud','WV'),('Buda','IL'),('Buda','TX'),('Budd Lake','NJ'),('Bude','MS'),('Buellton','CA'),('Buena','NJ'),('Buena','WA'),('Buena Park','CA'),('Buena Vista','CO'),('Buena Vista','GA'),('Buena Vista','NM'),('Buena Vista','PA'),('Buena Vista','TN'),('Buena Vista','VA'),('Buffalo','IA'),('Buffalo','IL'),('Buffalo','IN'),('Buffalo','KS'),('Buffalo','KY'),('Buffalo','MN'),('Buffalo','MO'),('Buffalo','MT'),('Buffalo','ND'),('Buffalo','NY'),('Buffalo','OH'),('Buffalo','OK'),('Buffalo','SC'),('Buffalo','SD'),('Buffalo','TX'),('Buffalo','WV'),('Buffalo','WY'),('Buffalo Center','IA'),('Buffalo Creek','CO'),('Buffalo Gap','SD'),('Buffalo Gap','TX'),('Buffalo Grove','IL'),('Buffalo Junction','VA'),('Buffalo Lake','MN'),('Buffalo Mills','PA'),('Buffalo Prairie','IL'),('Buffalo Valley','TN'),('Buford','GA'),('Buford','OH'),('Buford','WY'),('Buhl','AL'),('Buhl','ID'),('Buhl','MN'),('Buhler','KS'),('Buies Creek','NC'),('Bula','TX'),('Bulan','KY'),('Bulger','PA'),('Bull Shoals','AR'),('Bullard','TX'),('Bullhead','SD'),('Bullhead City','AZ'),('Bullock','NC'),('Bulls Gap','TN'),('Bullville','NY'),('Bulpitt','IL'),('Bulverde','TX'),('Bumpass','VA'),('Bumpus Mills','TN'),('Buna','TX'),('Bunceton','MO'),('Bunch','OK'),('Buncombe','IL'),('Bunker','MO'),('Bunker Hill','IL'),('Bunker Hill','IN'),('Bunker Hill','KS'),('Bunker Hill','WV'),('Bunkerville','NV'),('Bunkie','LA'),('Bunn','NC'),('Bunnell','FL'),('Bunnlevel','NC'),('Bunola','PA'),('Buras','LA'),('Burbank','CA'),('Burbank','IL'),('Burbank','OH'),('Burbank','OK'),('Burbank','SD'),('Burbank','WA'),('Burchard','NE'); + +INSERT INTO [cities] VALUES ('Burden','KS'),('Burdett','KS'),('Burdett','NY'),('Burdette','AR'),('Burdick','KS'),('Burdine','KY'),('Bureau','IL'),('Burfordville','MO'),('Burgaw','NC'),('Burgess','VA'),('Burgettstown','PA'),('Burghill','OH'),('Burgin','KY'),('Burgoon','OH'),('Burkburnett','TX'),('Burke','NY'),('Burke','SD'),('Burke','VA'),('Burkes Garden','VA'),('Burkesville','KY'),('Burket','IN'),('Burkett','TX'),('Burkettsville','OH'),('Burkeville','TX'),('Burkeville','VA'),('Burkittsville','MD'),('Burleson','TX'),('Burley','ID'),('Burley','WA'),('Burlingame','CA'),('Burlingame','KS'),('Burlingham','NY'),('Burlington','CO'),('Burlington','CT'),('Burlington','IA'),('Burlington','IL'),('Burlington','IN'),('Burlington','KS'),('Burlington','KY'),('Burlington','MA'),('Burlington','ME'),('Burlington','MI'),('Burlington','NC'),('Burlington','ND'),('Burlington','NJ'),('Burlington','OK'),('Burlington','PA'),('Burlington','TX'),('Burlington','VT'),('Burlington','WA'),('Burlington','WI'),('Burlington','WV'),('Burlington','WY'),('Burlington Flats','NY'),('Burlington Junction','MO'),('Burlison','TN'),('Burna','KY'),('Burnet','TX'),('Burnett','WI'),('Burnettsville','IN'),('Burney','CA'),('Burneyville','OK'),('Burnham','ME'),('Burnham','PA'),('Burnips','MI'),('Burns','CO'),('Burns','KS'),('Burns','OR'),('Burns','TN'),('Burns','WY'),('Burns Flat','OK'),('Burnside','IA'),('Burnside','KY'),('Burnside','LA'),('Burnside','PA'),('Burnsville','MN'),('Burnsville','MS'),('Burnsville','NC'),('Burnsville','WV'),('Burnt Cabins','PA'),('Burnt Corn','AL'),('Burnt Hills','NY'),('Burnt Prairie','IL'),('Burnt Ranch','CA'),('Burnwell','AL'),('Burr','NE'),('Burr Hill','VA'),('Burr Oak','KS'),('Burr Oak','MI'),('Burrel','CA'),('Burrows','IN'),('Burrton','KS'),('Burson','CA'),('Burt','IA'),('Burt','MI'),('Burt','NY'),('Burt Lake','MI'),('Burton','MI'),('Burton','OH'),('Burton','TX'),('Burton','WA'),('Burton','WV'),('Burtonsville','MD'),('Burtrum','MN'),('Burwell','NE'),('Busby','MT'),('Bush','KY'),('Bush','LA'),('Bushkill','PA'),('Bushland','TX'),('Bushnell','FL'),('Bushnell','IL'),('Bushnell','NE'),('Bushton','KS'),('Bushwood','MD'),('Buskirk','NY'),('Bussey','IA'),('Bustins Island','ME'),('Busy','KY'),('Butler','AL'),('Butler','GA'),('Butler','IL'),('Butler','IN'),('Butler','KY'),('Butler','MD'),('Butler','MO'),('Butler','NJ'),('Butler','OH'),('Butler','OK'),('Butler','PA'),('Butler','TN'),('Butler','WI'),('Butlerville','IN'),('Butner','NC'),('Butte','MT'),('Butte','ND'),('Butte','NE'),('Butte City','CA'),('Butte Des Morts','WI'),('Butte Falls','OR'),('Butterfield','MN'),('Butterfield','MO'),('Butternut','WI'),('Buttonwillow','CA'),('Buttzville','NJ'),('Buxton','ME'),('Buxton','NC'),('Buxton','ND'),('Buxton','OR'),('Buzzards Bay','MA'),('Byars','OK'),('Bybee','TN'),('Byers','CO'),('Byers','KS'),('Byers','TX'),('Byesville','OH'),('Byfield','MA'),('Byhalia','MS'),('Bylas','AZ'),('Bynum','AL'),('Bynum','MT'),('Bynum','NC'),('Bynum','TX'),('Bypro','KY'),('Byram','MS'),('Byrdstown','TN'),('Byrnedale','PA'),('Byromville','GA'),('Byron','CA'),('Byron','GA'),('Byron','IL'),('Byron','MI'),('Byron','MN'),('Byron','NE'),('Byron','NY'),('Byron','WY'),('Byron Center','MI'),('Caballo','NM'),('Cabazon','CA'),('Cabery','IL'),('Cabin Creek','WV'),('Cabin John','MD'),('Cabins','WV'),('Cable','OH'),('Cable','WI'),('Cabo Rojo','PR'),('Cabool','MO'),('Cabot','AR'),('Cabot','PA'),('Cabot','VT'),('Cache','OK'),('Cache Junction','UT'),('Cactus','TX'),('Caddo','OK'),('Caddo','TX'),('Caddo Gap','AR'),('Caddo Mills','TX'),('Cade','LA'),('Cades','SC'),('Cadet','MO'),('Cadillac','MI'),('Cadiz','KY'),('Cadiz','OH'),('Cadogan','PA'),('Cadott','WI'),('Cadwell','GA'),('Cadyville','NY'),('Caguas','PR'),('Cahone','CO'),('Cainsville','MO'),('Cairnbrook','PA'),('Cairo','GA'),('Cairo','IL'),('Cairo','MO'),('Cairo','NE'),('Cairo','NY'),('Cairo','OH'),('Cairo','WV'),('Cal Nev Ari','NV'),('Calabasas','CA'),('Calabash','NC'),('Calais','ME'),('Calais','VT'),('Calamus','IA'),('Calcium','NY'),('Calder','ID'),('Caldwell','AR'),('Caldwell','ID'),('Caldwell','KS'),('Caldwell','NJ'),('Caldwell','OH'),('Caldwell','TX'),('Caldwell','WV'),('Cale','AR'),('Caledonia','IL'),('Caledonia','MI'),('Caledonia','MN'),('Caledonia','MO'),('Caledonia','MS'),('Caledonia','ND'),('Caledonia','NY'),('Caledonia','OH'),('Caledonia','WI'),('Calera','AL'),('Calera','OK'),('Calexico','CA'),('Calhan','CO'),('Calhoun','GA'),('Calhoun','IL'),('Calhoun','KY'),('Calhoun','LA'),('Calhoun','MO'),('Calhoun','TN'),('Calhoun City','MS'),('Calhoun Falls','SC'),('Calico Rock','AR'),('Caliente','CA'),('Caliente','NV'),('Califon','NJ'),('California','KY'),('California','MD'),('California','MO'),('California','PA'),('California City','CA'),('California Hot Springs','CA'),('Calimesa','CA'),('Calion','AR'),('Calipatria','CA'),('Calistoga','CA'),('Call','TX'),('Callahan','CA'),('Callahan','FL'),('Callands','VA'),('Callao','MO'),('Callao','VA'),('Callaway','MD'),('Callaway','MN'),('Callaway','NE'),('Callaway','VA'),('Callender','IA'),('Callensburg','PA'),('Callery','PA'),('Callicoon','NY'),('Callicoon Center','NY'),('Calliham','TX'),('Calmar','IA'),('Calpella','CA'),('Calpine','CA'),('Calumet','IA'),('Calumet','MI'),('Calumet','MN'),('Calumet','OK'),('Calumet','PA'),('Calumet City','IL'),('Calvary','GA'),('Calvert','AL'),('Calvert','TX'),('Calvert City','KY'),('Calverton','NY'),('Calverton','VA'),('Calvin','KY'),('Calvin','LA'),('Calvin','ND'),('Calvin','OK'),('Calvin','PA'),('Calvin','WV'),('Calypso','NC'),('Camak','GA'),('Camanche','IA'),('Camano Island','WA'),('Camargo','IL'),('Camargo','OK'),('Camarillo','CA'),('Camas','WA'),('Camas Valley','OR'),('Cambra','PA'),('Cambria','CA'),('Cambria','IL'),('Cambria','WI'),('Cambria Heights','NY'),('Cambridge','IA'),('Cambridge','ID'),('Cambridge','IL'),('Cambridge','KS'),('Cambridge','MA'),('Cambridge','MD'),('Cambridge','ME'),('Cambridge','MN'),('Cambridge','NE'),('Cambridge','NY'),('Cambridge','OH'),('Cambridge','VT'),('Cambridge','WI'),('Cambridge City','IN'),('Cambridge Springs','PA'),('Cambridgeport','VT'),('Camby','IN'),('Camden','AL'),('Camden','AR'),('Camden','IL'),('Camden','IN'),('Camden','ME'),('Camden','MI'),('Camden','MO'),('Camden','MS'),('Camden','NC'),('Camden','NJ'),('Camden','NY'),('Camden','OH'),('Camden','SC'),('Camden','TN'),('Camden','TX'),('Camden','WV'),('Camden On Gauley','WV'),('Camden Point','MO'),('Camden Wyoming','DE'),('Camdenton','MO'),('Cameron','AZ'),('Cameron','IL'),('Cameron','LA'),('Cameron','MO'),('Cameron','MT'),('Cameron','NC'),('Cameron','NY'),('Cameron','OH'),('Cameron','OK'),('Cameron','SC'),('Cameron','TX'),('Cameron','WI'),('Cameron','WV'),('Cameron Mills','NY'),('Camilla','GA'),('Camillus','NY'),('Camino','CA'),('Cammal','PA'),('Camp','AR'),('Camp Creek','WV'),('Camp Crook','SD'),('Camp Dennison','OH'),('Camp Douglas','WI'),('Camp Grove','IL'),('Camp H M Smith','HI'),('Camp Hill','AL'),('Camp Hill','PA'),('Camp Lake','WI'),('Camp Lejeune','NC'),('Camp Meeker','CA'),('Camp Murray','WA'),('Camp Nelson','CA'),('Camp Pendleton','CA'),('Camp Point','IL'),('Camp Sherman','OR'),('Camp Verde','AZ'),('Camp Wood','TX'),('Campaign','TN'),('Campbell','AL'),('Campbell','CA'),('Campbell','MN'),('Campbell','MO'),('Campbell','NE'),('Campbell','NY'),('Campbell','OH'),('Campbell','TX'),('Campbell Hall','NY'),('Campbell Hill','IL'),('Campbellsburg','IN'),('Campbellsburg','KY'),('Campbellsport','WI'),('Campbellsville','KY'),('Campbellton','FL'),('Campbellton','TX'),('Campbelltown','PA'),('Campo','CA'),('Campo','CO'),('Campo Seco','CA'),('Campobello','SC'),('Campti','LA'),('Campton','KY'),('Campton','NH'),('Camptonville','CA'),('Camptown','PA'),('Campus','IL'),('Camuy','PR'),('Cana','VA'),('Canaan','CT'),('Canaan','IN'),('Canaan','ME'),('Canaan','NH'),('Canaan','NY'),('Canaan','VT'),('Canada','KY'),('Canadensis','PA'),('Canadian','OK'),('Canadian','TX'),('Canadys','SC'),('Canajoharie','NY'),('Canal Fulton','OH'),('Canal Point','FL'),('Canal Winchester','OH'),('Canalou','MO'),('Canandaigua','NY'),('Canaseraga','NY'),('Canastota','NY'),('Canby','CA'),('Canby','MN'),('Canby','OR'),('Candia','NH'),('Candler','FL'),('Candler','NC'),('Cando','ND'),('Candor','NC'),('Candor','NY'),('Cane Valley','KY'),('Caneadea','NY'),('Canehill','AR'),('Caney','KS'),('Caney','OK'),('Caneyville','KY'),('Canfield','OH'),('Canisteo','NY'),('Canistota','SD'),('Canjilon','NM'),('Canmer','KY'),('Cannel City','KY'),('Cannelburg','IN'),('Cannelton','IN'),('Cannelton','WV'),('Cannon','KY'),('Cannon Afb','NM'),('Cannon Ball','ND'),('Cannon Beach','OR'),('Cannon Falls','MN'),('Cannonsburg','MI'),('Cannonville','UT'),('Canoga Park','CA'),('Canon','GA'),('Canon City','CO'),('Canones','NM'),('Canonsburg','PA'),('Canova','SD'),('Canovanas','PR'),('Canterbury','CT'),('Canterbury','NH'),('Cantil','CA'),('Canton','CT'),('Canton','GA'),('Canton','IL'),('Canton','KS'),('Canton','MA'),('Canton','ME'),('Canton','MI'),('Canton','MN'),('Canton','MO'),('Canton','MS'),('Canton','NC'),('Canton','NY'),('Canton','OH'),('Canton','OK'),('Canton','PA'),('Canton','SD'); + +INSERT INTO [cities] VALUES ('Canton','TX'),('Canton Center','CT'),('Cantonment','FL'),('Cantrall','IL'),('Cantril','IA'),('Cantua Creek','CA'),('Cantwell','AK'),('Canute','OK'),('Canutillo','TX'),('Canvas','WV'),('Canyon','CA'),('Canyon','MN'),('Canyon','TX'),('Canyon City','OR'),('Canyon Country','CA'),('Canyon Creek','MT'),('Canyon Dam','CA'),('Canyon Lake','TX'),('Canyonville','OR'),('Capac','MI'),('Capay','CA'),('Cape Canaveral','FL'),('Cape Charles','VA'),('Cape Coral','FL'),('Cape Elizabeth','ME'),('Cape Fair','MO'),('Cape Girardeau','MO'),('Cape May','NJ'),('Cape May Court House','NJ'),('Cape May Point','NJ'),('Cape Neddick','ME'),('Cape Porpoise','ME'),('Cape Vincent','NY'),('Capeville','VA'),('Capistrano Beach','CA'),('Capitan','NM'),('Capitol','MT'),('Capitol Heights','MD'),('Capitola','CA'),('Caplinger Mills','MO'),('Capon Bridge','WV'),('Capon Springs','WV'),('Caprock','NM'),('Capron','IL'),('Capron','VA'),('Capshaw','AL'),('Captain Cook','HI'),('Captiva','FL'),('Capulin','CO'),('Capulin','NM'),('Caputa','SD'),('Caratunk','ME'),('Caraway','AR'),('Carbon','IA'),('Carbon','IN'),('Carbon','TX'),('Carbon Cliff','IL'),('Carbon Hill','AL'),('Carbon Hill','OH'),('Carbonado','WA'),('Carbondale','CO'),('Carbondale','IL'),('Carbondale','KS'),('Carbondale','OH'),('Carbondale','PA'),('Cardale','PA'),('Cardiff','AL'),('Cardiff By The Sea','CA'),('Cardin','OK'),('Cardinal','VA'),('Cardington','OH'),('Cardwell','MO'),('Cardwell','MT'),('Carefree','AZ'),('Carencro','LA'),('Caret','VA'),('Carey','ID'),('Carey','OH'),('Careywood','ID'),('Caribou','ME'),('Carl Junction','MO'),('Carle Place','NY'),('Carleton','MI'),('Carleton','NE'),('Carlin','NV'),('Carlinville','IL'),('Carlisle','AR'),('Carlisle','IA'),('Carlisle','IN'),('Carlisle','KY'),('Carlisle','MA'),('Carlisle','NY'),('Carlisle','PA'),('Carlisle','SC'),('Carlock','IL'),('Carlos','MN'),('Carlotta','CA'),('Carlsbad','CA'),('Carlsbad','NM'),('Carlsbad','TX'),('Carlsborg','WA'),('Carlstadt','NJ'),('Carlton','AL'),('Carlton','GA'),('Carlton','MN'),('Carlton','OR'),('Carlton','PA'),('Carlton','TX'),('Carlton','WA'),('Carlyle','IL'),('Carman','IL'),('Carmel','CA'),('Carmel','IN'),('Carmel','ME'),('Carmel','NY'),('Carmel By The Sea','CA'),('Carmel Valley','CA'),('Carmen','ID'),('Carmen','OK'),('Carmi','IL'),('Carmichael','CA'),('Carmichaels','PA'),('Carmine','TX'),('Carnation','WA'),('Carnegie','OK'),('Carnegie','PA'),('Carnelian Bay','CA'),('Carnesville','GA'),('Carney','MI'),('Carney','OK'),('Caro','MI'),('Caroga Lake','NY'),('Carol Stream','IL'),('Caroleen','NC'),('Carolina','PR'),('Carolina','RI'),('Carolina','WV'),('Carolina Beach','NC'),('Caroline','WI'),('Carp Lake','MI'),('Carpenter','IA'),('Carpenter','SD'),('Carpenter','WY'),('Carpentersville','IL'),('Carpinteria','CA'),('Carpio','ND'),('Carr','CO'),('Carrabelle','FL'),('Carrboro','NC'),('Carrie','KY'),('Carrier','OK'),('Carrier Mills','IL'),('Carriere','MS'),('Carrington','ND'),('Carrizo Springs','TX'),('Carrizozo','NM'),('Carroll','IA'),('Carroll','NE'),('Carroll','OH'),('Carrolls','WA'),('Carrollton','AL'),('Carrollton','GA'),('Carrollton','IL'),('Carrollton','KY'),('Carrollton','MI'),('Carrollton','MO'),('Carrollton','MS'),('Carrollton','OH'),('Carrollton','TX'),('Carrollton','VA'),('Carrolltown','PA'),('Carrsville','VA'),('Carson','CA'),('Carson','IA'),('Carson','MS'),('Carson','ND'),('Carson','NM'),('Carson','VA'),('Carson','WA'),('Carson City','MI'),('Carson City','NV'),('Carsonville','MI'),('Carter','KY'),('Carter','MT'),('Carter','OK'),('Carter Lake','IA'),('Carteret','NJ'),('Cartersville','GA'),('Cartersville','VA'),('Carterville','IL'),('Carterville','MO'),('Carthage','AR'),('Carthage','IL'),('Carthage','IN'),('Carthage','MO'),('Carthage','MS'),('Carthage','NC'),('Carthage','NY'),('Carthage','SD'),('Carthage','TN'),('Carthage','TX'),('Cartwright','ND'),('Cartwright','OK'),('Caruthers','CA'),('Caruthersville','MO'),('Carver','MA'),('Carver','MN'),('Carversville','PA'),('Carville','LA'),('Cary','IL'),('Cary','MS'),('Cary','NC'),('Caryville','FL'),('Caryville','TN'),('Casa','AR'),('Casa Blanca','NM'),('Casa Grande','AZ'),('Casanova','VA'),('Casar','NC'),('Cascade','CO'),('Cascade','IA'),('Cascade','ID'),('Cascade','MD'),('Cascade','MO'),('Cascade','MT'),('Cascade','VA'),('Cascade','WI'),('Cascade Locks','OR'),('Cascadia','OR'),('Cascilla','MS'),('Casco','ME'),('Casco','MI'),('Casco','WI'),('Caseville','MI'),('Casey','IA'),('Casey','IL'),('Caseyville','IL'),('Cash','AR'),('Cashiers','NC'),('Cashion','AZ'),('Cashion','OK'),('Cashmere','WA'),('Cashton','WI'),('Cashtown','PA'),('Casmalia','CA'),('Casnovia','MI'),('Cason','TX'),('Caspar','CA'),('Casper','WY'),('Caspian','MI'),('Cass','WV'),('Cass City','MI'),('Cass Lake','MN'),('Cassadaga','FL'),('Cassadaga','NY'),('Cassandra','PA'),('Cassatt','SC'),('Casscoe','AR'),('Cassel','CA'),('Casselberry','FL'),('Casselton','ND'),('Cassoday','KS'),('Cassopolis','MI'),('Casstown','OH'),('Cassville','GA'),('Cassville','MO'),('Cassville','NY'),('Cassville','PA'),('Cassville','WI'),('Cassville','WV'),('Castaic','CA'),('Castalia','IA'),('Castalia','NC'),('Castalia','OH'),('Castalian Springs','TN'),('Castana','IA'),('Castanea','PA'),('Castaner','PR'),('Castell','TX'),('Castella','CA'),('Castile','NY'),('Castine','ME'),('Castle','OK'),('Castle Creek','NY'),('Castle Dale','UT'),('Castle Hayne','NC'),('Castle Point','NY'),('Castle Rock','CO'),('Castle Rock','MN'),('Castle Rock','WA'),('Castleberry','AL'),('Castleford','ID'),('Castleton','IL'),('Castleton','VA'),('Castleton','VT'),('Castleton On Hudson','NY'),('Castlewood','SD'),('Castlewood','VA'),('Castor','LA'),('Castorland','NY'),('Castro Valley','CA'),('Castroville','CA'),('Castroville','TX'),('Cat Spring','TX'),('Cataldo','ID'),('Catalina','AZ'),('Catano','PR'),('Cataract','WI'),('Catarina','TX'),('Catasauqua','PA'),('Cataula','GA'),('Cataumet','MA'),('Catawba','NC'),('Catawba','OH'),('Catawba','SC'),('Catawba','VA'),('Catawba','WI'),('Catawissa','MO'),('Catawissa','PA'),('Catharine','KS'),('Catharpin','VA'),('Cathay','ND'),('Cathedral City','CA'),('Catherine','AL'),('Catheys Valley','CA'),('Cathlamet','WA'),('Catlett','VA'),('Catlettsburg','KY'),('Catlin','IL'),('Cato','NY'),('Catonsville','MD'),('Catoosa','OK'),('Catron','MO'),('Catskill','NY'),('Cattaraugus','NY'),('Caulfield','MO'),('Causey','NM'),('Cavalier','ND'),('Cave City','AR'),('Cave City','KY'),('Cave Creek','AZ'),('Cave In Rock','IL'),('Cave Junction','OR'),('Cave Spring','GA'),('Cave Springs','AR'),('Cavendish','VT'),('Cavetown','MD'),('Cavour','SD'),('Cawker City','KS'),('Cawood','KY'),('Cayce','SC'),('Cayey','PR'),('Cayucos','CA'),('Cayuga','IN'),('Cayuga','ND'),('Cayuga','NY'),('Cayuga','TX'),('Cayuta','NY'),('Cazadero','CA'),('Cazenovia','NY'),('Cazenovia','WI'),('Cebolla','NM'),('Cecil','AL'),('Cecil','AR'),('Cecil','GA'),('Cecil','OH'),('Cecil','PA'),('Cecil','WI'),('Cecilia','KY'),('Cecilia','LA'),('Cecilton','MD'),('Cedar','IA'),('Cedar','KS'),('Cedar','MI'),('Cedar','MN'),('Cedar Bluff','AL'),('Cedar Bluff','VA'),('Cedar Bluffs','NE'),('Cedar Brook','NJ'),('Cedar City','UT'),('Cedar Creek','NE'),('Cedar Creek','TX'),('Cedar Crest','NM'),('Cedar Falls','IA'),('Cedar Falls','NC'),('Cedar Glen','CA'),('Cedar Grove','IN'),('Cedar Grove','NC'),('Cedar Grove','NJ'),('Cedar Grove','TN'),('Cedar Grove','WI'),('Cedar Grove','WV'),('Cedar Hill','MO'),('Cedar Hill','TN'),('Cedar Hill','TX'),('Cedar Island','NC'),('Cedar Key','FL'),('Cedar Knolls','NJ'),('Cedar Lake','IN'),('Cedar Lake','MI'),('Cedar Lane','TX'),('Cedar Mountain','NC'),('Cedar Park','TX'),('Cedar Point','IL'),('Cedar Point','KS'),('Cedar Rapids','IA'),('Cedar Rapids','NE'),('Cedar Ridge','CA'),('Cedar Run','PA'),('Cedar Springs','GA'),('Cedar Springs','MI'),('Cedar Vale','KS'),('Cedar Valley','UT'),('Cedarbluff','MS'),('Cedarburg','WI'),('Cedarcreek','MO'),('Cedaredge','CO'),('Cedarhurst','NY'),('Cedarpines Park','CA'),('Cedars','PA'),('Cedartown','GA'),('Cedarvale','NM'),('Cedarville','AR'),('Cedarville','CA'),('Cedarville','IL'),('Cedarville','MI'),('Cedarville','NJ'),('Cedarville','OH'),('Cedarville','WV'),('Cee Vee','TX'),('Ceiba','PR'),('Celeste','TX'),('Celestine','IN'),('Celina','OH'),('Celina','TN'),('Celina','TX'),('Celoron','NY'),('Cement','OK'),('Cement City','MI'),('Centenary','SC'),('Centennial','WY'),('Center','CO'),('Center','KY'),('Center','MO'),('Center','ND'),('Center','NE'),('Center','TX'),('Center Barnstead','NH'),('Center City','MN'),('Center Cross','VA'),('Center Harbor','NH'),('Center Hill','FL'),('Center Junction','IA'),('Center Line','MI'),('Center Lovell','ME'),('Center Moriches','NY'),('Center Ossipee','NH'),('Center Point','IA'),('Center Point','LA'),('Center Point','TX'),('Center Point','WV'),('Center Ridge','AR'),('Center Rutland','VT'),('Center Sandwich','NH'),('Center Strafford','NH'),('Center Tuftonboro','NH'),('Center Valley','PA'),('Centerbrook','CT'),('Centerburg','OH'),('Centereach','NY'),('Centerfield','UT'),('Centerpoint','IN'),('Centerport','NY'),('Centerport','PA'),('Centerton','AR'),('Centertown','KY'),('Centertown','MO'),('Centerview','MO'),('Centerville','AR'),('Centerville','GA'),('Centerville','IA'),('Centerville','IN'),('Centerville','KS'),('Centerville','LA'),('Centerville','MA'),('Centerville','MO'),('Centerville','NY'),('Centerville','PA'),('Centerville','SD'),('Centerville','TN'),('Centerville','TX'),('Centerville','UT'),('Centerville','WA'),('Centrahoma','OK'),('Central','AK'),('Central','AZ'),('Central','IN'),('Central','SC'),('Central','UT'),('Central Bridge','NY'); + +INSERT INTO [cities] VALUES ('Central City','CO'),('Central City','IA'),('Central City','KY'),('Central City','NE'),('Central City','PA'),('Central Falls','RI'),('Central Islip','NY'),('Central Lake','MI'),('Central Point','OR'),('Central Square','NY'),('Central Valley','NY'),('Central Village','CT'),('Centralia','IL'),('Centralia','KS'),('Centralia','MO'),('Centralia','TX'),('Centralia','WA'),('Centre','AL'),('Centre Hall','PA'),('Centreville','AL'),('Centreville','MD'),('Centreville','MI'),('Centreville','MS'),('Centreville','VA'),('Centuria','WI'),('Century','FL'),('Ceredo','WV'),('Ceres','CA'),('Ceres','NY'),('Ceres','VA'),('Ceresco','MI'),('Ceresco','NE'),('Cerrillos','NM'),('Cerritos','CA'),('Cerro','NM'),('Cerro Gordo','IL'),('Cerro Gordo','NC'),('Cerulean','KY'),('Ceylon','MN'),('Chacon','NM'),('Chadbourn','NC'),('Chadds Ford','PA'),('Chadron','NE'),('Chadwick','IL'),('Chadwick','MO'),('Chadwicks','NY'),('Chaffee','MO'),('Chaffee','NY'),('Chagrin Falls','OH'),('Chalfont','PA'),('Chalk Hill','PA'),('Chalkyitsik','AK'),('Challenge','CA'),('Challis','ID'),('Chalmers','IN'),('Chalmette','LA'),('Chama','CO'),('Chama','NM'),('Chamberino','NM'),('Chamberlain','ME'),('Chamberlain','SD'),('Chambers','AZ'),('Chambers','NE'),('Chambersburg','IL'),('Chambersburg','PA'),('Chambersville','PA'),('Chamisal','NM'),('Chamois','MO'),('Champaign','IL'),('Champion','MI'),('Champion','NE'),('Champion','PA'),('Champlain','NY'),('Champlain','VA'),('Champlin','MN'),('Chana','IL'),('Chancellor','AL'),('Chancellor','SD'),('Chandler','AZ'),('Chandler','IN'),('Chandler','MN'),('Chandler','OK'),('Chandler','TX'),('Chandler Heights','AZ'),('Chandlers Valley','PA'),('Chandlersville','OH'),('Chandlerville','IL'),('Changewater','NJ'),('Chanhassen','MN'),('Channahon','IL'),('Channelview','TX'),('Channing','MI'),('Channing','TX'),('Chantilly','VA'),('Chanute','KS'),('Chaparral','NM'),('Chapel Hill','NC'),('Chapel Hill','TN'),('Chapin','IA'),('Chapin','IL'),('Chapin','SC'),('Chaplin','CT'),('Chaplin','KY'),('Chapman','AL'),('Chapman','KS'),('Chapman','NE'),('Chapman Ranch','TX'),('Chapmansboro','TN'),('Chapmanville','WV'),('Chappaqua','NY'),('Chappell','KY'),('Chappell','NE'),('Chappell Hill','TX'),('Chappells','SC'),('Chaptico','MD'),('Chardon','OH'),('Charenton','LA'),('Chariton','IA'),('Charlemont','MA'),('Charleroi','PA'),('Charles City','IA'),('Charles City','VA'),('Charles Town','WV'),('Charleston','AR'),('Charleston','IL'),('Charleston','ME'),('Charleston','MO'),('Charleston','MS'),('Charleston','SC'),('Charleston','TN'),('Charleston','WV'),('Charleston Afb','SC'),('Charlestown','IN'),('Charlestown','MA'),('Charlestown','MD'),('Charlestown','NH'),('Charlestown','RI'),('Charlevoix','MI'),('Charlo','MT'),('Charlotte','AR'),('Charlotte','IA'),('Charlotte','MI'),('Charlotte','NC'),('Charlotte','TN'),('Charlotte','TX'),('Charlotte','VT'),('Charlotte Court House','VA'),('Charlotte Hall','MD'),('Charlottesville','IN'),('Charlottesville','VA'),('Charlotteville','NY'),('Charlton','MA'),('Charlton City','MA'),('Charlton Depot','MA'),('Charlton Heights','WV'),('Charm','OH'),('Charmco','WV'),('Charter Oak','IA'),('Chartley','MA'),('Chase','KS'),('Chase','LA'),('Chase','MD'),('Chase','MI'),('Chase City','VA'),('Chase Mills','NY'),('Chaseburg','WI'),('Chaseley','ND'),('Chaska','MN'),('Chassell','MI'),('Chataignier','LA'),('Chatawa','MS'),('Chateaugay','NY'),('Chatfield','MN'),('Chatfield','OH'),('Chatfield','TX'),('Chatham','IL'),('Chatham','LA'),('Chatham','MA'),('Chatham','MI'),('Chatham','MS'),('Chatham','NH'),('Chatham','NJ'),('Chatham','NY'),('Chatham','PA'),('Chatham','VA'),('Chatom','AL'),('Chatsworth','CA'),('Chatsworth','GA'),('Chatsworth','IA'),('Chatsworth','IL'),('Chatsworth','NJ'),('Chattahoochee','FL'),('Chattanooga','OK'),('Chattanooga','TN'),('Chattaroy','WA'),('Chattaroy','WV'),('Chaumont','NY'),('Chauncey','GA'),('Chauncey','OH'),('Chauncey','WV'),('Chautauqua','KS'),('Chautauqua','NY'),('Chauvin','LA'),('Chavies','KY'),('Chazy','NY'),('Chebanse','IL'),('Chebeague Island','ME'),('Cheboygan','MI'),('Check','VA'),('Checotah','OK'),('Chefornak','AK'),('Chehalis','WA'),('Chelan','WA'),('Chelan Falls','WA'),('Chelmsford','MA'),('Chelsea','AL'),('Chelsea','IA'),('Chelsea','MA'),('Chelsea','MI'),('Chelsea','NY'),('Chelsea','OK'),('Chelsea','VT'),('Cheltenham','MD'),('Cheltenham','PA'),('Chemult','OR'),('Chemung','NY'),('Chenango Bridge','NY'),('Chenango Forks','NY'),('Cheney','KS'),('Cheney','WA'),('Cheneyville','LA'),('Chenoa','IL'),('Chepachet','RI'),('Cheraw','CO'),('Cheraw','SC'),('Cheriton','VA'),('Cherokee','AL'),('Cherokee','IA'),('Cherokee','KS'),('Cherokee','NC'),('Cherokee','OK'),('Cherokee','TX'),('Cherokee Village','AR'),('Cherry','IL'),('Cherry Creek','NY'),('Cherry Creek','SD'),('Cherry Fork','OH'),('Cherry Hill','NJ'),('Cherry Plain','NY'),('Cherry Point','NC'),('Cherry Tree','PA'),('Cherry Valley','AR'),('Cherry Valley','IL'),('Cherry Valley','MA'),('Cherry Valley','NY'),('Cherryfield','ME'),('Cherrylog','GA'),('Cherryvale','KS'),('Cherryville','MO'),('Cherryville','NC'),('Cherryville','PA'),('Chesaning','MI'),('Chesapeake','OH'),('Chesapeake','VA'),('Chesapeake Beach','MD'),('Chesapeake City','MD'),('Cheshire','CT'),('Cheshire','MA'),('Cheshire','OH'),('Cheshire','OR'),('Chesnee','SC'),('Chest Springs','PA'),('Chester','AR'),('Chester','CA'),('Chester','CT'),('Chester','GA'),('Chester','IA'),('Chester','ID'),('Chester','IL'),('Chester','MA'),('Chester','MD'),('Chester','MT'),('Chester','NE'),('Chester','NH'),('Chester','NJ'),('Chester','NY'),('Chester','OH'),('Chester','OK'),('Chester','PA'),('Chester','SC'),('Chester','SD'),('Chester','TX'),('Chester','UT'),('Chester','VA'),('Chester','VT'),('Chester','WV'),('Chester Depot','VT'),('Chester Gap','VA'),('Chester Heights','PA'),('Chester Springs','PA'),('Chesterfield','IL'),('Chesterfield','MA'),('Chesterfield','MO'),('Chesterfield','NH'),('Chesterfield','NJ'),('Chesterfield','SC'),('Chesterfield','VA'),('Chesterhill','OH'),('Chesterland','OH'),('Chesterton','IN'),('Chestertown','MD'),('Chestertown','NY'),('Chesterville','OH'),('Chestnut','IL'),('Chestnut Hill','MA'),('Chestnut Mound','TN'),('Chestnut Mountain','GA'),('Chestnut Ridge','PA'),('Chestnutridge','MO'),('Cheswick','PA'),('Cheswold','DE'),('Chetek','WI'),('Chetopa','KS'),('Chevak','AK'),('Chevy Chase','MD'),('Chewalla','TN'),('Chewelah','WA'),('Chewsville','MD'),('Cheyenne','OK'),('Cheyenne','WY'),('Cheyenne Wells','CO'),('Cheyney','PA'),('Chicago','IL'),('Chicago Heights','IL'),('Chicago Park','CA'),('Chicago Ridge','IL'),('Chichester','NH'),('Chichester','NY'),('Chickamauga','GA'),('Chickasaw','OH'),('Chickasha','OK'),('Chicken','AK'),('Chico','CA'),('Chico','TX'),('Chicopee','MA'),('Chicora','PA'),('Chicota','TX'),('Chidester','AR'),('Chiefland','FL'),('Chignik','AK'),('Chignik Lagoon','AK'),('Chignik Lake','AK'),('Chilcoot','CA'),('Childersburg','AL'),('Childress','TX'),('Childs','MD'),('Childwold','NY'),('Chilhowee','MO'),('Chilhowie','VA'),('Chili','WI'),('Chillicothe','IA'),('Chillicothe','IL'),('Chillicothe','MO'),('Chillicothe','OH'),('Chillicothe','TX'),('Chilmark','MA'),('Chilo','OH'),('Chiloquin','OR'),('Chilton','TX'),('Chilton','WI'),('Chimacum','WA'),('Chimayo','NM'),('Chimney Rock','CO'),('Chimney Rock','NC'),('China','TX'),('China Grove','NC'),('China Spring','TX'),('China Village','ME'),('Chinchilla','PA'),('Chincoteague Island','VA'),('Chinese Camp','CA'),('Chinle','AZ'),('Chino','CA'),('Chino Hills','CA'),('Chino Valley','AZ'),('Chinook','MT'),('Chinook','WA'),('Chinquapin','NC'),('Chipley','FL'),('Chippewa Bay','NY'),('Chippewa Falls','WI'),('Chippewa Lake','MI'),('Chippewa Lake','OH'),('Chireno','TX'),('Chisago City','MN'),('Chisholm','MN'),('Chitina','AK'),('Chittenango','NY'),('Chittenden','VT'),('Chloe','WV'),('Chloride','AZ'),('Choccolocco','AL'),('Chocorua','NH'),('Chocowinity','NC'),('Choctaw','AR'),('Choctaw','OK'),('Chokio','MN'),('Chokoloskee','FL'),('Choteau','MT'),('Choudrant','LA'),('Chouteau','OK'),('Chowchilla','CA'),('Chriesman','TX'),('Chrisman','IL'),('Chrisney','IN'),('Christchurch','VA'),('Christiana','PA'),('Christiana','TN'),('Christiansburg','OH'),('Christiansburg','VA'),('Christine','ND'),('Christine','TX'),('Christmas','FL'),('Christmas Valley','OR'),('Christopher','IL'),('Christoval','TX'),('Chromo','CO'),('Chualar','CA'),('Chuckey','TN'),('Chugiak','AK'),('Chugwater','WY'),('Chula','GA'),('Chula','MO'),('Chula Vista','CA'),('Chunchula','AL'),('Chunky','MS'),('Church Creek','MD'),('Church Hill','MD'),('Church Hill','TN'),('Church Point','LA'),('Church Road','VA'),('Church Rock','NM'),('Church View','VA'),('Churchs Ferry','ND'),('Churchton','MD'),('Churchville','MD'),('Churchville','NY'),('Churchville','VA'),('Churdan','IA'),('Churubusco','IN'),('Churubusco','NY'),('Ciales','PR'),('Cibecue','AZ'),('Cibola','AZ'),('Cibolo','TX'),('Cicero','IL'),('Cicero','IN'),('Cicero','NY'),('Cidra','PR'),('Cima','CA'),('Cimarron','CO'),('Cimarron','KS'),('Cimarron','NM'),('Cincinnati','IA'),('Cincinnati','OH'),('Cincinnatus','NY'),('Cinebar','WA'),('Circle','AK'),('Circle','MT'),('Circle Pines','MN'),('Circleville','KS'),('Circleville','NY'),('Circleville','OH'),('Circleville','UT'),('Circleville','WV'),('Cisco','GA'),('Cisco','IL'),('Cisco','TX'),('Cisco','UT'),('Cisne','IL'),('Cissna Park','IL'),('Citra','FL'),('Citronelle','AL'),('Citrus Heights','CA'),('City Of Industry','CA'),('Clackamas','OR'),('Claflin','KS'),('Claiborne','MD'),('Claire City','SD'),('Clairfield','TN'),('Clairton','PA'),('Clallam Bay','WA'),('Clam Gulch','AK'),('Clam Lake','WI'),('Clancy','MT'),('Clanton','AL'),('Clara','MS'),('Clara City','MN'),('Clarcona','FL'),('Clare','IA'); + +INSERT INTO [cities] VALUES ('Clare','IL'),('Clare','MI'),('Claremont','CA'),('Claremont','IL'),('Claremont','MN'),('Claremont','NC'),('Claremont','NH'),('Claremont','SD'),('Claremont','VA'),('Claremore','OK'),('Clarence','IA'),('Clarence','LA'),('Clarence','MO'),('Clarence','NY'),('Clarence','PA'),('Clarence Center','NY'),('Clarendon','AR'),('Clarendon','NC'),('Clarendon','NY'),('Clarendon','PA'),('Clarendon','TX'),('Clarendon Hills','IL'),('Claridge','PA'),('Clarinda','IA'),('Clarington','OH'),('Clarington','PA'),('Clarion','IA'),('Clarion','PA'),('Clarissa','MN'),('Clarita','OK'),('Clark','CO'),('Clark','MO'),('Clark','NJ'),('Clark','PA'),('Clark','SD'),('Clark Fork','ID'),('Clark Mills','NY'),('Clarkdale','AZ'),('Clarkdale','GA'),('Clarkedale','AR'),('Clarkesville','GA'),('Clarkfield','MN'),('Clarkia','ID'),('Clarklake','MI'),('Clarkrange','TN'),('Clarkridge','AR'),('Clarks','LA'),('Clarks','NE'),('Clarks Grove','MN'),('Clarks Hill','IN'),('Clarks Hill','SC'),('Clarks Mills','PA'),('Clarks Point','AK'),('Clarks Summit','PA'),('Clarksboro','NJ'),('Clarksburg','CA'),('Clarksburg','IN'),('Clarksburg','MD'),('Clarksburg','MO'),('Clarksburg','OH'),('Clarksburg','PA'),('Clarksburg','TN'),('Clarksburg','WV'),('Clarksdale','MO'),('Clarksdale','MS'),('Clarkson','KY'),('Clarkson','NE'),('Clarkson','NY'),('Clarkston','GA'),('Clarkston','MI'),('Clarkston','UT'),('Clarkston','WA'),('Clarksville','AR'),('Clarksville','FL'),('Clarksville','IA'),('Clarksville','IN'),('Clarksville','MD'),('Clarksville','MI'),('Clarksville','MO'),('Clarksville','NY'),('Clarksville','OH'),('Clarksville','PA'),('Clarksville','TN'),('Clarksville','TX'),('Clarksville','VA'),('Clarkton','MO'),('Clarkton','NC'),('Claryville','NY'),('Clatonia','NE'),('Clatskanie','OR'),('Claude','TX'),('Claudville','VA'),('Claunch','NM'),('Claverack','NY'),('Clawson','MI'),('Clawson','UT'),('Claxton','GA'),('Clay','AL'),('Clay','KY'),('Clay','NY'),('Clay','WV'),('Clay Center','KS'),('Clay Center','NE'),('Clay Center','OH'),('Clay City','IL'),('Clay City','IN'),('Clay City','KY'),('Clay Springs','AZ'),('Clayhole','KY'),('Claymont','DE'),('Claypool','AZ'),('Claypool','IN'),('Claysburg','PA'),('Claysville','PA'),('Clayton','AL'),('Clayton','CA'),('Clayton','DE'),('Clayton','GA'),('Clayton','ID'),('Clayton','IL'),('Clayton','IN'),('Clayton','KS'),('Clayton','LA'),('Clayton','MI'),('Clayton','NC'),('Clayton','NJ'),('Clayton','NM'),('Clayton','NY'),('Clayton','OH'),('Clayton','OK'),('Clayton','TX'),('Clayton','WA'),('Clayton','WI'),('Clayton Lake','ME'),('Claytonville','IL'),('Clayville','NY'),('Clayville','RI'),('Cle Elum','WA'),('Clear','AK'),('Clear Brook','VA'),('Clear Creek','IN'),('Clear Creek','WV'),('Clear Fork','WV'),('Clear Lake','IA'),('Clear Lake','MN'),('Clear Lake','SD'),('Clear Lake','WI'),('Clear Spring','MD'),('Clearbrook','MN'),('Clearfield','IA'),('Clearfield','KY'),('Clearfield','PA'),('Clearfield','UT'),('Clearlake','CA'),('Clearlake','WA'),('Clearlake Oaks','CA'),('Clearlake Park','CA'),('Clearmont','MO'),('Clearmont','WY'),('Clearview City','KS'),('Clearville','PA'),('Clearwater','FL'),('Clearwater','KS'),('Clearwater','MN'),('Clearwater','NE'),('Clearwater','SC'),('Clearwater Beach','FL'),('Cleaton','KY'),('Cleburne','TX'),('Cleghorn','IA'),('Clementon','NJ'),('Clements','CA'),('Clements','MD'),('Clements','MN'),('Clemmons','NC'),('Clemons','IA'),('Clemons','NY'),('Clemson','SC'),('Clendenin','WV'),('Cleo Springs','OK'),('Clermont','FL'),('Clermont','GA'),('Clermont','IA'),('Clermont','KY'),('Cleveland','AL'),('Cleveland','AR'),('Cleveland','GA'),('Cleveland','MN'),('Cleveland','MO'),('Cleveland','MS'),('Cleveland','NC'),('Cleveland','ND'),('Cleveland','NM'),('Cleveland','NY'),('Cleveland','OH'),('Cleveland','OK'),('Cleveland','SC'),('Cleveland','TN'),('Cleveland','TX'),('Cleveland','UT'),('Cleveland','VA'),('Cleveland','WI'),('Cleveland','WV'),('Clever','MO'),('Cleverdale','NY'),('Cleves','OH'),('Clewiston','FL'),('Cliff','NM'),('Cliff Island','ME'),('Clifford','IN'),('Clifford','MI'),('Clifford','ND'),('Clifford','PA'),('Clifford','VA'),('Cliffside','NC'),('Cliffside Park','NJ'),('Cliffwood','NJ'),('Clifton','AZ'),('Clifton','CO'),('Clifton','ID'),('Clifton','IL'),('Clifton','KS'),('Clifton','NJ'),('Clifton','OH'),('Clifton','SC'),('Clifton','TN'),('Clifton','TX'),('Clifton','VA'),('Clifton Forge','VA'),('Clifton Heights','PA'),('Clifton Hill','MO'),('Clifton Park','NY'),('Clifton Springs','NY'),('Clifty','KY'),('Climax','CO'),('Climax','GA'),('Climax','MI'),('Climax','MN'),('Climax','NC'),('Climax','NY'),('Climax Springs','MO'),('Climbing Hill','IA'),('Clinchco','VA'),('Clinchfield','GA'),('Clines Corners','NM'),('Clint','TX'),('Clinton','AL'),('Clinton','AR'),('Clinton','CT'),('Clinton','IA'),('Clinton','IL'),('Clinton','IN'),('Clinton','KY'),('Clinton','LA'),('Clinton','MA'),('Clinton','MD'),('Clinton','ME'),('Clinton','MI'),('Clinton','MN'),('Clinton','MO'),('Clinton','MS'),('Clinton','MT'),('Clinton','NC'),('Clinton','NJ'),('Clinton','NY'),('Clinton','OH'),('Clinton','OK'),('Clinton','PA'),('Clinton','SC'),('Clinton','TN'),('Clinton','WA'),('Clinton','WI'),('Clinton Corners','NY'),('Clinton Township','MI'),('Clintondale','NY'),('Clintonville','PA'),('Clintonville','WI'),('Clintwood','VA'),('Clio','AL'),('Clio','CA'),('Clio','IA'),('Clio','MI'),('Clio','SC'),('Clipper Mills','CA'),('Clitherall','MN'),('Clive','IA'),('Clockville','NY'),('Clontarf','MN'),('Clopton','AL'),('Cloquet','MN'),('Closplint','KY'),('Closter','NJ'),('Clothier','WV'),('Cloudcroft','NM'),('Cloutierville','LA'),('Clover','SC'),('Clover','VA'),('Cloverdale','AL'),('Cloverdale','CA'),('Cloverdale','IN'),('Cloverdale','MI'),('Cloverdale','OH'),('Cloverdale','OR'),('Cloverdale','VA'),('Cloverport','KY'),('Clovis','CA'),('Clovis','NM'),('Clubb','MO'),('Clune','PA'),('Cluster Springs','VA'),('Clute','TX'),('Clutier','IA'),('Clyde','KS'),('Clyde','MO'),('Clyde','NC'),('Clyde','NY'),('Clyde','OH'),('Clyde','TX'),('Clyde Park','MT'),('Clyman','WI'),('Clymer','NY'),('Clymer','PA'),('Clyo','GA'),('Coachella','CA'),('Coahoma','MS'),('Coahoma','TX'),('Coal Center','PA'),('Coal City','IL'),('Coal City','IN'),('Coal City','WV'),('Coal Creek','CO'),('Coal Hill','AR'),('Coal Mountain','WV'),('Coal Run','OH'),('Coal Township','PA'),('Coal Valley','IL'),('Coaldale','CO'),('Coaldale','PA'),('Coalfield','TN'),('Coalgate','OK'),('Coalgood','KY'),('Coaling','AL'),('Coalinga','CA'),('Coalmont','CO'),('Coalmont','IN'),('Coalmont','TN'),('Coalport','PA'),('Coalton','OH'),('Coalton','WV'),('Coalville','UT'),('Coalwood','WV'),('Coamo','PR'),('Coarsegold','CA'),('Coatesville','IN'),('Coatesville','PA'),('Coats','KS'),('Coats','NC'),('Coatsburg','IL'),('Coatsville','MO'),('Cobalt','CT'),('Cobalt','ID'),('Cobb','CA'),('Cobb','GA'),('Cobb','WI'),('Cobb Island','MD'),('Cobbs Creek','VA'),('Cobbtown','GA'),('Cobden','IL'),('Cobleskill','NY'),('Coburn','PA'),('Cochecton','NY'),('Cochecton Center','NY'),('Cochise','AZ'),('Cochiti Lake','NM'),('Cochiti Pueblo','NM'),('Cochran','GA'),('Cochrane','WI'),('Cochranton','PA'),('Cochranville','PA'),('Cockeysville','MD'),('Cocoa','FL'),('Cocoa Beach','FL'),('Cocolalla','ID'),('Cocolamus','PA'),('Coconut Creek','FL'),('Coden','AL'),('Codorus','PA'),('Cody','NE'),('Cody','WY'),('Coeburn','VA'),('Coello','IL'),('Coeur D Alene','ID'),('Coeymans','NY'),('Coeymans Hollow','NY'),('Coffee Creek','MT'),('Coffee Springs','AL'),('Coffeen','IL'),('Coffeeville','AL'),('Coffeeville','MS'),('Coffey','MO'),('Coffeyville','KS'),('Coffman Cove','AK'),('Cofield','NC'),('Cogan Station','PA'),('Coggon','IA'),('Cogswell','ND'),('Cohagen','MT'),('Cohasset','MA'),('Cohasset','MN'),('Cohoctah','MI'),('Cohocton','NY'),('Cohoes','NY'),('Cohutta','GA'),('Coila','MS'),('Coin','IA'),('Coinjock','NC'),('Cokato','MN'),('Cokeburg','PA'),('Coker','AL'),('Cokercreek','TN'),('Cokeville','WY'),('Colbert','GA'),('Colbert','OK'),('Colbert','WA'),('Colburn','ID'),('Colby','KS'),('Colby','WI'),('Colchester','CT'),('Colchester','IL'),('Colchester','VT'),('Colcord','OK'),('Colcord','WV'),('Cold Bay','AK'),('Cold Brook','NY'),('Cold Spring','MN'),('Cold Spring','NY'),('Cold Spring Harbor','NY'),('Colden','NY'),('Coldiron','KY'),('Coldspring','TX'),('Coldwater','KS'),('Coldwater','MI'),('Coldwater','MS'),('Coldwater','OH'),('Cole Camp','MO'),('Colebrook','CT'),('Colebrook','NH'),('Coleharbor','ND'),('Coleman','FL'),('Coleman','GA'),('Coleman','MI'),('Coleman','OK'),('Coleman','TX'),('Coleman','WI'),('Coleman Falls','VA'),('Colerain','NC'),('Colerain','OH'),('Coleraine','MN'),('Coleridge','NE'),('Coles Point','VA'),('Colesburg','IA'),('Coleville','CA'),('Colfax','CA'),('Colfax','IA'),('Colfax','IL'),('Colfax','IN'),('Colfax','LA'),('Colfax','NC'),('Colfax','ND'),('Colfax','WA'),('Colfax','WI'),('Colfax','WV'),('Colgate','WI'),('Collbran','CO'),('College Corner','OH'),('College Grove','TN'),('College Park','MD'),('College Place','WA'),('College Point','NY'),('College Springs','IA'),('College Station','AR'),('College Station','TX'),('Collegedale','TN'),('Collegeport','TX'),('Collegeville','MN'),('Collegeville','PA'),('Collettsville','NC'),('Colleyville','TX'),('Colliers','WV'),('Colliersville','NY'),('Collierville','TN'),('Collins','GA'),('Collins','IA'),('Collins','MO'),('Collins','MS'),('Collins','NY'),('Collins','OH'),('Collins','WI'),('Collins Center','NY'),('Collinston','LA'); + +INSERT INTO [cities] VALUES ('Collinston','UT'),('Collinsville','AL'),('Collinsville','CT'),('Collinsville','IL'),('Collinsville','MS'),('Collinsville','OH'),('Collinsville','OK'),('Collinsville','TX'),('Collinsville','VA'),('Collinwood','TN'),('Collison','IL'),('Collyer','KS'),('Colman','SD'),('Colmar','PA'),('Colmesneil','TX'),('Colo','IA'),('Cologne','MN'),('Cologne','NJ'),('Coloma','CA'),('Coloma','MI'),('Coloma','WI'),('Colome','SD'),('Colon','MI'),('Colon','NE'),('Colona','IL'),('Colonia','NJ'),('Colonial Beach','VA'),('Colonial Heights','VA'),('Colony','KS'),('Colony','OK'),('Colora','MD'),('Colorado City','AZ'),('Colorado City','CO'),('Colorado City','TX'),('Colorado Springs','CO'),('Colp','IL'),('Colquitt','GA'),('Colrain','MA'),('Colstrip','MT'),('Colt','AR'),('Colton','CA'),('Colton','NY'),('Colton','OH'),('Colton','OR'),('Colton','SD'),('Colton','WA'),('Coltons Point','MD'),('Colts Neck','NJ'),('Columbia','AL'),('Columbia','CA'),('Columbia','CT'),('Columbia','IA'),('Columbia','IL'),('Columbia','KY'),('Columbia','LA'),('Columbia','MD'),('Columbia','MO'),('Columbia','MS'),('Columbia','NC'),('Columbia','NJ'),('Columbia','PA'),('Columbia','SC'),('Columbia','SD'),('Columbia','TN'),('Columbia','VA'),('Columbia City','IN'),('Columbia City','OR'),('Columbia Cross Roads','PA'),('Columbia Falls','ME'),('Columbia Falls','MT'),('Columbia Station','OH'),('Columbiana','AL'),('Columbiana','OH'),('Columbiaville','MI'),('Columbiaville','NY'),('Columbus','AR'),('Columbus','GA'),('Columbus','IN'),('Columbus','KS'),('Columbus','KY'),('Columbus','MI'),('Columbus','MS'),('Columbus','MT'),('Columbus','NC'),('Columbus','ND'),('Columbus','NE'),('Columbus','NJ'),('Columbus','NM'),('Columbus','OH'),('Columbus','PA'),('Columbus','TX'),('Columbus','WI'),('Columbus City','IA'),('Columbus Grove','OH'),('Columbus Junction','IA'),('Colusa','CA'),('Colusa','IL'),('Colver','PA'),('Colville','WA'),('Colwell','IA'),('Colwich','KS'),('Comanche','OK'),('Comanche','TX'),('Combes','TX'),('Combined Locks','WI'),('Combs','AR'),('Combs','KY'),('Comer','GA'),('Comerio','PR'),('Comfort','NC'),('Comfort','TX'),('Comfort','WV'),('Comfrey','MN'),('Comins','MI'),('Commack','NY'),('Commerce','GA'),('Commerce','MO'),('Commerce','OK'),('Commerce','TX'),('Commerce City','CO'),('Commerce Township','MI'),('Commercial Point','OH'),('Commiskey','IN'),('Commodore','PA'),('Como','CO'),('Como','MS'),('Como','NC'),('Como','TN'),('Como','TX'),('Comptche','CA'),('Compton','AR'),('Compton','CA'),('Compton','IL'),('Compton','MD'),('Comstock','MI'),('Comstock','MN'),('Comstock','NE'),('Comstock','NY'),('Comstock','TX'),('Comstock','WI'),('Comstock Park','MI'),('Conasauga','TN'),('Concan','TX'),('Concepcion','TX'),('Conception','MO'),('Conception Junction','MO'),('Conchas Dam','NM'),('Concho','AZ'),('Concho','OK'),('Conconully','WA'),('Concord','AR'),('Concord','CA'),('Concord','GA'),('Concord','IL'),('Concord','MA'),('Concord','MI'),('Concord','NC'),('Concord','NE'),('Concord','NH'),('Concord','PA'),('Concord','TX'),('Concord','VA'),('Concord','VT'),('Concordia','KS'),('Concordia','MO'),('Concordville','PA'),('Concrete','WA'),('Conda','ID'),('Conde','SD'),('Condon','MT'),('Condon','OR'),('Conehatta','MS'),('Conejos','CO'),('Conestee','SC'),('Conestoga','PA'),('Conesus','NY'),('Conesville','IA'),('Conesville','OH'),('Conetoe','NC'),('Conewango Valley','NY'),('Confluence','PA'),('Conger','MN'),('Congers','NY'),('Congerville','IL'),('Congress','AZ'),('Conifer','CO'),('Conklin','MI'),('Conklin','NY'),('Conley','GA'),('Conneaut','OH'),('Conneaut Lake','PA'),('Conneautville','PA'),('Connell','WA'),('Connellsville','PA'),('Connelly','NY'),('Connellys Springs','NC'),('Conner','MT'),('Connersville','IN'),('Connerville','OK'),('Connoquenessing','PA'),('Conover','NC'),('Conover','OH'),('Conover','WI'),('Conowingo','MD'),('Conrad','IA'),('Conrad','MT'),('Conrath','WI'),('Conroe','TX'),('Conroy','IA'),('Conshohocken','PA'),('Constable','NY'),('Constableville','NY'),('Constantia','NY'),('Constantine','MI'),('Continental','OH'),('Continental Divide','NM'),('Contoocook','NH'),('Convent','LA'),('Converse','IN'),('Converse','LA'),('Converse','SC'),('Converse','TX'),('Convoy','OH'),('Conway','AR'),('Conway','MA'),('Conway','MI'),('Conway','MO'),('Conway','NC'),('Conway','NH'),('Conway','PA'),('Conway','SC'),('Conway','WA'),('Conway Springs','KS'),('Conyers','GA'),('Conyngham','PA'),('Cook','MN'),('Cook','MO'),('Cook','NE'),('Cook Springs','AL'),('Cooke City','MT'),('Cookeville','TN'),('Cooks','MI'),('Cooksburg','PA'),('Cookson','OK'),('Cookstown','NJ'),('Cooksville','IL'),('Cooksville','MD'),('Cookville','TX'),('Cool','CA'),('Cool Ridge','WV'),('Cooleemee','NC'),('Coolidge','AZ'),('Coolidge','GA'),('Coolidge','KS'),('Coolidge','TX'),('Coolin','ID'),('Coolspring','PA'),('Coolville','OH'),('Coon Rapids','IA'),('Coon Valley','WI'),('Cooper','IA'),('Cooper','TX'),('Cooper Landing','AK'),('Coopers Mills','ME'),('Coopers Plains','NY'),('Coopersburg','PA'),('Cooperstown','ND'),('Cooperstown','NY'),('Cooperstown','PA'),('Coopersville','MI'),('Coos Bay','OR'),('Coosa','GA'),('Coosada','AL'),('Coosawhatchie','SC'),('Cooter','MO'),('Copake','NY'),('Copake Falls','NY'),('Copalis Beach','WA'),('Copalis Crossing','WA'),('Copan','OK'),('Cope','CO'),('Cope','SC'),('Copeland','FL'),('Copeland','KS'),('Copemish','MI'),('Copen','WV'),('Copenhagen','NY'),('Copeville','TX'),('Copiague','NY'),('Coplay','PA'),('Coppell','TX'),('Copper Center','AK'),('Copper City','MI'),('Copper Harbor','MI'),('Copper Hill','VA'),('Copperas Cove','TX'),('Copperhill','TN'),('Copperopolis','CA'),('Coquille','OR'),('Cora','WV'),('Cora','WY'),('Coral','MI'),('Coral','PA'),('Coralville','IA'),('Coram','MT'),('Coram','NY'),('Coraopolis','PA'),('Corapeake','NC'),('Corbett','OR'),('Corbettsville','NY'),('Corbin','KY'),('Corbin','VA'),('Corcoran','CA'),('Cord','AR'),('Cordele','GA'),('Cordell','OK'),('Corder','MO'),('Cordesville','SC'),('Cordova','AK'),('Cordova','AL'),('Cordova','IL'),('Cordova','MD'),('Cordova','NC'),('Cordova','NE'),('Cordova','NM'),('Cordova','SC'),('Cordova','TN'),('Corea','ME'),('Corfu','NY'),('Corinna','ME'),('Corinne','UT'),('Corinne','WV'),('Corinth','KY'),('Corinth','ME'),('Corinth','MS'),('Corinth','NY'),('Corinth','VT'),('Corn','OK'),('Cornelia','GA'),('Cornelius','NC'),('Cornelius','OR'),('Cornell','IL'),('Cornell','MI'),('Cornell','WI'),('Cornersville','TN'),('Cornettsville','KY'),('Corning','AR'),('Corning','CA'),('Corning','IA'),('Corning','KS'),('Corning','NY'),('Corning','OH'),('Cornish','ME'),('Cornish','NH'),('Cornish','UT'),('Cornish Flat','NH'),('Cornland','IL'),('Cornucopia','WI'),('Cornville','AZ'),('Cornwall','CT'),('Cornwall','NY'),('Cornwall','PA'),('Cornwall Bridge','CT'),('Cornwall On Hudson','NY'),('Cornwallville','NY'),('Corolla','NC'),('Corona','CA'),('Corona','NM'),('Corona','NY'),('Corona','SD'),('Corona Del Mar','CA'),('Coronado','CA'),('Corozal','PR'),('Corpus Christi','TX'),('Corral','ID'),('Corrales','NM'),('Correctionville','IA'),('Correll','MN'),('Corrigan','TX'),('Corriganville','MD'),('Corry','PA'),('Corryton','TN'),('Corsica','PA'),('Corsica','SD'),('Corsicana','TX'),('Cortaro','AZ'),('Corte Madera','CA'),('Cortez','CO'),('Cortez','FL'),('Cortland','IL'),('Cortland','IN'),('Cortland','NE'),('Cortland','NY'),('Cortland','OH'),('Cortlandt Manor','NY'),('Corunna','IN'),('Corunna','MI'),('Corvallis','MT'),('Corvallis','OR'),('Corwith','IA'),('Cory','CO'),('Cory','IN'),('Corydon','IA'),('Corydon','IN'),('Corydon','KY'),('Cos Cob','CT'),('Cosby','MO'),('Cosby','TN'),('Coshocton','OH'),('Cosmopolis','WA'),('Cosmos','MN'),('Cossayuna','NY'),('Cost','TX'),('Costa','WV'),('Costa Mesa','CA'),('Costilla','NM'),('Cotati','CA'),('Coto Laurel','PR'),('Cotopaxi','CO'),('Cottage Grove','MN'),('Cottage Grove','OR'),('Cottage Grove','TN'),('Cottage Grove','WI'),('Cottage Hills','IL'),('Cottageville','SC'),('Cottageville','WV'),('Cottekill','NY'),('Cotter','AR'),('Cottleville','MO'),('Cotton','GA'),('Cotton','MN'),('Cotton Center','TX'),('Cotton Plant','AR'),('Cotton Valley','LA'),('Cottondale','AL'),('Cottondale','FL'),('Cottonport','LA'),('Cottonton','AL'),('Cottontown','TN'),('Cottonwood','AL'),('Cottonwood','AZ'),('Cottonwood','CA'),('Cottonwood','ID'),('Cottonwood','MN'),('Cottonwood Falls','KS'),('Cotuit','MA'),('Cotulla','TX'),('Couch','MO'),('Couderay','WI'),('Coudersport','PA'),('Cougar','WA'),('Coulee City','WA'),('Coulee Dam','WA'),('Coulter','IA'),('Coulters','PA'),('Coulterville','CA'),('Coulterville','IL'),('Counce','TN'),('Council','ID'),('Council','NC'),('Council Bluffs','IA'),('Council Grove','KS'),('Council Hill','OK'),('Counselor','NM'),('Country Club Hills','IL'),('Countyline','OK'),('Coupeville','WA'),('Coupland','TX'),('Coupon','PA'),('Courtenay','ND'),('Courtland','AL'),('Courtland','CA'),('Courtland','KS'),('Courtland','MN'),('Courtland','MS'),('Courtland','VA'),('Coushatta','LA'),('Cove','AR'),('Cove','OR'),('Cove City','NC'),('Covel','WV'),('Covelo','CA'),('Coventry','CT'),('Coventry','RI'),('Coventry','VT'),('Covert','MI'),('Covesville','VA'),('Covina','CA'),('Covington','GA'),('Covington','IN'),('Covington','KY'),('Covington','LA'),('Covington','MI'),('Covington','OH'),('Covington','OK'),('Covington','PA'),('Covington','TN'),('Covington','TX'),('Covington','VA'),('Cowan','TN'); + +INSERT INTO [cities] VALUES ('Cowanesque','PA'),('Cowansville','PA'),('Coward','SC'),('Cowarts','AL'),('Cowden','IL'),('Cowdrey','CO'),('Cowen','WV'),('Coweta','OK'),('Cowgill','MO'),('Cowiche','WA'),('Cowlesville','NY'),('Cowley','WY'),('Cowpens','SC'),('Coxs Creek','KY'),('Coxs Mills','WV'),('Coxsackie','NY'),('Coy','AL'),('Coy','AR'),('Coyanosa','TX'),('Coyle','OK'),('Coyote','CA'),('Coyote','NM'),('Cozad','NE'),('Crab Orchard','KY'),('Crab Orchard','NE'),('Crab Orchard','TN'),('Crab Orchard','WV'),('Crabtree','OR'),('Crabtree','PA'),('Craddockville','VA'),('Craftsbury','VT'),('Craftsbury Common','VT'),('Cragford','AL'),('Cragsmoor','NY'),('Craig','AK'),('Craig','CO'),('Craig','MO'),('Craig','NE'),('Craigmont','ID'),('Craigsville','VA'),('Craigsville','WV'),('Craigville','IN'),('Craley','PA'),('Cramerton','NC'),('Cranberry','PA'),('Cranberry Isles','ME'),('Cranberry Lake','NY'),('Cranberry Twp','PA'),('Cranbury','NJ'),('Crandall','GA'),('Crandall','IN'),('Crandall','TX'),('Crandon','WI'),('Crane','IN'),('Crane','MO'),('Crane','MT'),('Crane','OR'),('Crane','TX'),('Crane Hill','AL'),('Crane Lake','MN'),('Cranesville','PA'),('Cranfills Gap','TX'),('Cranford','NJ'),('Cranks','KY'),('Cranston','RI'),('Crapo','MD'),('Crary','ND'),('Craryville','NY'),('Crater Lake','OR'),('Crawford','CO'),('Crawford','GA'),('Crawford','MS'),('Crawford','NE'),('Crawford','OK'),('Crawford','TN'),('Crawford','TX'),('Crawford','WV'),('Crawfordsville','AR'),('Crawfordsville','IA'),('Crawfordsville','IN'),('Crawfordsville','OR'),('Crawfordville','FL'),('Crawfordville','GA'),('Crawley','WV'),('Crayne','KY'),('Creal Springs','IL'),('Cream Ridge','NJ'),('Creamery','PA'),('Creede','CO'),('Creedmoor','NC'),('Creekside','PA'),('Creighton','MO'),('Creighton','NE'),('Creighton','PA'),('Crenshaw','MS'),('Creola','AL'),('Creola','OH'),('Creole','LA'),('Cresbard','SD'),('Crescent','GA'),('Crescent','IA'),('Crescent','OK'),('Crescent','OR'),('Crescent','PA'),('Crescent City','CA'),('Crescent City','FL'),('Crescent City','IL'),('Crescent Lake','OR'),('Crescent Mills','CA'),('Crescent Valley','NV'),('Cresco','IA'),('Cresco','PA'),('Cressey','CA'),('Cresskill','NJ'),('Cresson','PA'),('Cresson','TX'),('Cressona','PA'),('Crest Hill','IL'),('Crest Park','CA'),('Crested Butte','CO'),('Crestline','CA'),('Crestline','KS'),('Crestline','OH'),('Creston','CA'),('Creston','IA'),('Creston','IL'),('Creston','NC'),('Creston','NE'),('Creston','OH'),('Creston','WA'),('Creston','WV'),('Crestone','CO'),('Crestview','FL'),('Crestwood','KY'),('Creswell','NC'),('Creswell','OR'),('Crete','IL'),('Crete','NE'),('Creve Coeur','IL'),('Crewe','VA'),('Criders','VA'),('Crimora','VA'),('Cripple Creek','CO'),('Cripple Creek','VA'),('Crisfield','MD'),('Crittenden','KY'),('Crittenden','NY'),('Critz','VA'),('Crivitz','WI'),('Crocheron','MD'),('Crocker','MO'),('Crockett','CA'),('Crockett','KY'),('Crockett','TX'),('Crockett','VA'),('Crockett Mills','TN'),('Crocketts Bluff','AR'),('Crocketville','SC'),('Crofton','KY'),('Crofton','MD'),('Crofton','NE'),('Croghan','NY'),('Cromona','KY'),('Crompond','NY'),('Cromwell','CT'),('Cromwell','IA'),('Cromwell','IN'),('Cromwell','KY'),('Cromwell','MN'),('Cromwell','OK'),('Crook','CO'),('Crooked Creek','AK'),('Crooks','SD'),('Crookston','MN'),('Crookston','NE'),('Crooksville','OH'),('Cropsey','IL'),('Cropseyville','NY'),('Cropwell','AL'),('Crosby','MN'),('Crosby','MS'),('Crosby','ND'),('Crosby','PA'),('Crosby','TX'),('Crosbyton','TX'),('Cross','SC'),('Cross Anchor','SC'),('Cross City','FL'),('Cross Fork','PA'),('Cross Hill','SC'),('Cross Junction','VA'),('Cross Plains','IN'),('Cross Plains','TN'),('Cross Plains','TX'),('Cross Plains','WI'),('Cross River','NY'),('Cross Timbers','MO'),('Cross Village','MI'),('Crossett','AR'),('Crosslake','MN'),('Crossnore','NC'),('Crossroads','NM'),('Crossville','AL'),('Crossville','IL'),('Crossville','TN'),('Croswell','MI'),('Crothersville','IN'),('Croton','OH'),('Croton Falls','NY'),('Croton On Hudson','NY'),('Crouse','NC'),('Crouseville','ME'),('Crow Agency','MT'),('Crowder','MS'),('Crowder','OK'),('Crowell','TX'),('Crowheart','WY'),('Crowley','CO'),('Crowley','LA'),('Crowley','TX'),('Crown','PA'),('Crown City','OH'),('Crown King','AZ'),('Crown Point','IN'),('Crown Point','NY'),('Crownpoint','NM'),('Crownsville','MD'),('Crows Landing','CA'),('Crowville','LA'),('Croydon','PA'),('Croydon','UT'),('Crozet','VA'),('Crozier','VA'),('Crp Christi','TX'),('Crucible','PA'),('Cruger','MS'),('Crum','WV'),('Crum Lynne','PA'),('Crump','TN'),('Crumpler','NC'),('Crumpton','MD'),('Crumrod','AR'),('Crystal','MI'),('Crystal','ND'),('Crystal Bay','MN'),('Crystal Bay','NV'),('Crystal Beach','FL'),('Crystal City','MO'),('Crystal City','TX'),('Crystal Falls','MI'),('Crystal Hill','VA'),('Crystal Lake','IA'),('Crystal Lake','IL'),('Crystal River','FL'),('Crystal Spring','PA'),('Crystal Springs','FL'),('Crystal Springs','MS'),('Cub Run','KY'),('Cuba','AL'),('Cuba','IL'),('Cuba','KS'),('Cuba','MO'),('Cuba','NM'),('Cuba','NY'),('Cuba','OH'),('Cuba City','WI'),('Cubero','NM'),('Cucumber','WV'),('Cudahy','WI'),('Cuddebackville','NY'),('Cuddy','PA'),('Cuero','TX'),('Cuervo','NM'),('Culberson','NC'),('Culbertson','MT'),('Culbertson','NE'),('Culdesac','ID'),('Culebra','PR'),('Cullen','LA'),('Cullen','VA'),('Culleoka','TN'),('Cullman','AL'),('Culloden','GA'),('Culloden','WV'),('Cullom','IL'),('Cullowhee','NC'),('Culp Creek','OR'),('Culpeper','VA'),('Culver','IN'),('Culver','OR'),('Culver City','CA'),('Cumberland','IA'),('Cumberland','KY'),('Cumberland','MD'),('Cumberland','NC'),('Cumberland','OH'),('Cumberland','RI'),('Cumberland','VA'),('Cumberland','WI'),('Cumberland Center','ME'),('Cumberland City','TN'),('Cumberland Foreside','ME'),('Cumberland Furnace','TN'),('Cumberland Gap','TN'),('Cumbola','PA'),('Cumby','TX'),('Cummaquid','MA'),('Cumming','GA'),('Cumming','IA'),('Cummings','KS'),('Cummings','ND'),('Cummington','MA'),('Cumnock','NC'),('Cuney','TX'),('Cunningham','KS'),('Cunningham','KY'),('Cunningham','TN'),('Cunningham','TX'),('Cupertino','CA'),('Curdsville','KY'),('Curlew','IA'),('Curlew','WA'),('Curllsville','PA'),('Curran','MI'),('Currie','MN'),('Currie','NC'),('Currituck','NC'),('Curryville','MO'),('Curryville','PA'),('Curtice','OH'),('Curtin','OR'),('Curtis','AR'),('Curtis','MI'),('Curtis','NE'),('Curtis','WA'),('Curtis Bay','MD'),('Curtiss','WI'),('Curtisville','PA'),('Curwensville','PA'),('Cushing','IA'),('Cushing','ME'),('Cushing','MN'),('Cushing','OK'),('Cushing','TX'),('Cushing','WI'),('Cushman','AR'),('Cusick','WA'),('Cusseta','AL'),('Cusseta','GA'),('Custar','OH'),('Custer','KY'),('Custer','MI'),('Custer','MT'),('Custer','SD'),('Custer','WA'),('Custer','WI'),('Custer City','OK'),('Custer City','PA'),('Cut Bank','MT'),('Cut Off','LA'),('Cutchogue','NY'),('Cuthbert','GA'),('Cutler','CA'),('Cutler','IL'),('Cutler','IN'),('Cutler','ME'),('Cutler','OH'),('Cutten','CA'),('Cuttingsville','VT'),('Cuttyhunk','MA'),('Cuyahoga Falls','OH'),('Cyclone','PA'),('Cyclone','WV'),('Cygnet','OH'),('Cylinder','IA'),('Cynthiana','IN'),('Cynthiana','KY'),('Cynthiana','OH'),('Cypress','CA'),('Cypress','FL'),('Cypress','IL'),('Cypress','TX'),('Cypress Inn','TN'),('Cyril','OK'),('Cyrus','MN'),('D Hanis','TX'),('D Lo','MS'),('Dacoma','OK'),('Dacono','CO'),('Dacula','GA'),('Dade City','FL'),('Dadeville','AL'),('Dadeville','MO'),('Dafter','MI'),('Daggett','CA'),('Daggett','MI'),('Dagmar','MT'),('Dagsboro','DE'),('Dagus Mines','PA'),('Dahinda','IL'),('Dahlen','ND'),('Dahlgren','IL'),('Dahlgren','VA'),('Dahlonega','GA'),('Dailey','WV'),('Daingerfield','TX'),('Dairy','OR'),('Daisetta','TX'),('Daisy','GA'),('Daisy','MO'),('Daisy','OK'),('Daisytown','PA'),('Dakota','IL'),('Dakota','MN'),('Dakota City','IA'),('Dakota City','NE'),('Dalbo','MN'),('Dale','IL'),('Dale','IN'),('Dale','NY'),('Dale','SC'),('Dale','TX'),('Dale','WI'),('Daleville','AL'),('Daleville','IN'),('Daleville','MS'),('Daleville','VA'),('Dalhart','TX'),('Dallardsville','TX'),('Dallas','GA'),('Dallas','IA'),('Dallas','NC'),('Dallas','OR'),('Dallas','PA'),('Dallas','SD'),('Dallas','TX'),('Dallas','WI'),('Dallas','WV'),('Dallas Center','IA'),('Dallas City','IL'),('Dallastown','PA'),('Dallesport','WA'),('Dalmatia','PA'),('Dalton','GA'),('Dalton','MA'),('Dalton','MN'),('Dalton','MO'),('Dalton','NE'),('Dalton','NY'),('Dalton','OH'),('Dalton','PA'),('Dalton','WI'),('Dalton City','IL'),('Daly City','CA'),('Dalzell','IL'),('Dalzell','SC'),('Damar','KS'),('Damariscotta','ME'),('Damascus','AR'),('Damascus','GA'),('Damascus','MD'),('Damascus','OH'),('Damascus','OR'),('Damascus','PA'),('Damascus','VA'),('Dameron','MD'),('Dammeron Valley','UT'),('Damon','TX'),('Dana','IA'),('Dana','IL'),('Dana','IN'),('Dana','KY'),('Dana','NC'),('Dana Point','CA'),('Danboro','PA'),('Danbury','CT'),('Danbury','IA'),('Danbury','NC'),('Danbury','NE'),('Danbury','NH'),('Danbury','TX'),('Danbury','WI'),('Danby','VT'),('Danciger','TX'),('Dandridge','TN'),('Dane','WI'),('Danese','WV'),('Danevang','TX'),('Danforth','IL'),('Danforth','ME'),('Dania','FL'),('Daniel','WY'),('Daniels','WV'),('Danielson','CT'),('Danielsville','GA'),('Danielsville','PA'),('Dannebrog','NE'),('Dannemora','NY'),('Dansville','MI'),('Dansville','NY'); + +INSERT INTO [cities] VALUES ('Dante','SD'),('Dante','VA'),('Danube','MN'),('Danvers','IL'),('Danvers','MA'),('Danvers','MN'),('Danville','AL'),('Danville','AR'),('Danville','CA'),('Danville','GA'),('Danville','IA'),('Danville','IL'),('Danville','IN'),('Danville','KS'),('Danville','KY'),('Danville','ME'),('Danville','NH'),('Danville','OH'),('Danville','PA'),('Danville','VA'),('Danville','VT'),('Danville','WA'),('Danville','WV'),('Daphne','AL'),('Darby','MT'),('Darby','PA'),('Dardanelle','AR'),('Dardanelle','CA'),('Darden','TN'),('Darfur','MN'),('Darien','CT'),('Darien','GA'),('Darien','IL'),('Darien','WI'),('Darien Center','NY'),('Darling','MS'),('Darlington','IN'),('Darlington','MD'),('Darlington','MO'),('Darlington','PA'),('Darlington','SC'),('Darlington','WI'),('Darragh','PA'),('Darrington','WA'),('Darrouzett','TX'),('Darrow','LA'),('Dartmouth','MA'),('Darwin','CA'),('Darwin','MN'),('Dassel','MN'),('Dateland','AZ'),('Datil','NM'),('Datto','AR'),('Daufuskie Island','SC'),('Dauphin','PA'),('Dauphin Island','AL'),('Davenport','CA'),('Davenport','FL'),('Davenport','IA'),('Davenport','ND'),('Davenport','NE'),('Davenport','NY'),('Davenport','OK'),('Davenport','VA'),('Davenport','WA'),('Davenport Center','NY'),('Davey','NE'),('David','KY'),('David City','NE'),('Davidson','NC'),('Davidson','OK'),('Davidsonville','MD'),('Davidsville','PA'),('Davilla','TX'),('Davin','WV'),('Davis','CA'),('Davis','IL'),('Davis','NC'),('Davis','OK'),('Davis','SD'),('Davis','WV'),('Davis City','IA'),('Davis Creek','CA'),('Davis Junction','IL'),('Davis Station','SC'),('Davis Wharf','VA'),('Davisboro','GA'),('Davisburg','MI'),('Davison','MI'),('Daviston','AL'),('Davisville','MO'),('Davisville','WV'),('Davy','WV'),('Dawes','WV'),('Dawn','MO'),('Dawn','TX'),('Dawson','AL'),('Dawson','GA'),('Dawson','IA'),('Dawson','IL'),('Dawson','MN'),('Dawson','ND'),('Dawson','NE'),('Dawson','PA'),('Dawson','TX'),('Dawson Springs','KY'),('Dawsonville','GA'),('Day','FL'),('Dayhoit','KY'),('Daykin','NE'),('Days Creek','OR'),('Dayton','IA'),('Dayton','ID'),('Dayton','IN'),('Dayton','KY'),('Dayton','MD'),('Dayton','MN'),('Dayton','MT'),('Dayton','NJ'),('Dayton','NV'),('Dayton','NY'),('Dayton','OH'),('Dayton','OR'),('Dayton','PA'),('Dayton','TN'),('Dayton','TX'),('Dayton','VA'),('Dayton','WA'),('Dayton','WY'),('Daytona Beach','FL'),('Dayville','CT'),('Dayville','OR'),('Dazey','ND'),('De Armanville','AL'),('De Beque','CO'),('De Berry','TX'),('De Borgia','MT'),('De Forest','WI'),('De Graff','OH'),('De Kalb','MO'),('De Kalb','MS'),('De Kalb','TX'),('De Kalb Junction','NY'),('De Lancey','PA'),('De Land','IL'),('De Leon','TX'),('De Leon Springs','FL'),('De Mossville','KY'),('De Pere','WI'),('De Peyster','NY'),('De Queen','AR'),('De Ruyter','NY'),('De Smet','SD'),('De Soto','GA'),('De Soto','IA'),('De Soto','IL'),('De Soto','KS'),('De Soto','MO'),('De Soto','WI'),('De Tour Village','MI'),('De Valls Bluff','AR'),('De Witt','AR'),('De Witt','IA'),('De Witt','MO'),('De Witt','NE'),('De Young','PA'),('Deadwood','OR'),('Deadwood','SD'),('Deal','NJ'),('Deal Island','MD'),('Deale','MD'),('Deane','KY'),('Deansboro','NY'),('Deanville','TX'),('Dearborn','MI'),('Dearborn','MO'),('Dearborn Heights','MI'),('Dearing','GA'),('Dearing','KS'),('Deary','ID'),('Death Valley','CA'),('Deatsville','AL'),('Deaver','WY'),('Debary','FL'),('Debord','KY'),('Decatur','AL'),('Decatur','AR'),('Decatur','GA'),('Decatur','IA'),('Decatur','IL'),('Decatur','IN'),('Decatur','MI'),('Decatur','MS'),('Decatur','NE'),('Decatur','OH'),('Decatur','TN'),('Decatur','TX'),('Decaturville','TN'),('Decherd','TN'),('Decker','IN'),('Decker','MI'),('Decker','MT'),('Deckerville','MI'),('Declo','ID'),('Decorah','IA'),('Dedham','IA'),('Dedham','MA'),('Deedsville','IN'),('Deep Gap','NC'),('Deep River','CT'),('Deep River','IA'),('Deep Run','NC'),('Deep Water','WV'),('Deepwater','MO'),('Deepwater','NJ'),('Deer','AR'),('Deer Creek','IL'),('Deer Creek','MN'),('Deer Creek','OK'),('Deer Grove','IL'),('Deer Harbor','WA'),('Deer Island','OR'),('Deer Isle','ME'),('Deer Lodge','MT'),('Deer Lodge','TN'),('Deer Park','AL'),('Deer Park','CA'),('Deer Park','NY'),('Deer Park','TX'),('Deer Park','WA'),('Deer Park','WI'),('Deer River','MN'),('Deer River','NY'),('Deer Trail','CO'),('Deerbrook','WI'),('Deerfield','IL'),('Deerfield','KS'),('Deerfield','MA'),('Deerfield','MI'),('Deerfield','MO'),('Deerfield','NH'),('Deerfield','OH'),('Deerfield','VA'),('Deerfield','WI'),('Deerfield Beach','FL'),('Deerfield Street','NJ'),('Deering','AK'),('Deering','MO'),('Deering','ND'),('Deersville','OH'),('Deerton','MI'),('Deerwood','MN'),('Deeth','NV'),('Deferiet','NY'),('Defiance','IA'),('Defiance','MO'),('Defiance','OH'),('Defiance','PA'),('Deford','MI'),('Defuniak Springs','FL'),('Dekalb','IL'),('Del Mar','CA'),('Del Norte','CO'),('Del Rey','CA'),('Del Rio','TN'),('Del Rio','TX'),('Del Valle','TX'),('Delafield','WI'),('Delancey','NY'),('Deland','FL'),('Delano','CA'),('Delano','MN'),('Delano','PA'),('Delano','TN'),('Delanson','NY'),('Delaplaine','AR'),('Delaplane','VA'),('Delavan','IL'),('Delavan','MN'),('Delavan','WI'),('Delaware','AR'),('Delaware','IA'),('Delaware','NJ'),('Delaware','OH'),('Delaware','OK'),('Delaware City','DE'),('Delaware Water Gap','PA'),('Delbarton','WV'),('Delcambre','LA'),('Delco','NC'),('Delevan','NY'),('Delhi','CA'),('Delhi','IA'),('Delhi','LA'),('Delhi','NY'),('Delia','KS'),('Delight','AR'),('Dell','AR'),('Dell','MT'),('Dell City','TX'),('Dell Rapids','SD'),('Dellrose','TN'),('Dellroy','OH'),('Dellslow','WV'),('Dellwood','WI'),('Delmar','AL'),('Delmar','DE'),('Delmar','IA'),('Delmar','MD'),('Delmar','NY'),('Delmita','TX'),('Delmont','NJ'),('Delmont','PA'),('Delmont','SD'),('Deloit','IA'),('Delong','IN'),('Delphi','IN'),('Delphi Falls','NY'),('Delphia','KY'),('Delphos','KS'),('Delphos','OH'),('Delran','NJ'),('Delray','WV'),('Delray Beach','FL'),('Delta','AL'),('Delta','CO'),('Delta','IA'),('Delta','LA'),('Delta','MO'),('Delta','OH'),('Delta','PA'),('Delta','UT'),('Delta City','MS'),('Delta Junction','AK'),('Deltaville','VA'),('Delton','MI'),('Deltona','FL'),('Dema','KY'),('Demarest','NJ'),('Deming','NM'),('Deming','WA'),('Demopolis','AL'),('Demorest','GA'),('Demotte','IN'),('Denair','CA'),('Denali National Park','AK'),('Denbo','PA'),('Dendron','VA'),('Denham Springs','LA'),('Denhoff','ND'),('Denio','NV'),('Denison','IA'),('Denison','KS'),('Denison','TX'),('Denmark','IA'),('Denmark','ME'),('Denmark','NY'),('Denmark','SC'),('Denmark','TN'),('Denmark','WI'),('Dennard','AR'),('Dennehotso','AZ'),('Dennis','KS'),('Dennis','MA'),('Dennis','MS'),('Dennis','TX'),('Dennis Port','MA'),('Dennison','IL'),('Dennison','MN'),('Dennison','OH'),('Denniston','KY'),('Dennisville','NJ'),('Dennysville','ME'),('Dent','MN'),('Denton','GA'),('Denton','KS'),('Denton','KY'),('Denton','MD'),('Denton','MT'),('Denton','NC'),('Denton','NE'),('Denton','TX'),('Denver','CO'),('Denver','IA'),('Denver','IN'),('Denver','MO'),('Denver','NC'),('Denver','NY'),('Denver','PA'),('Denver City','TX'),('Denville','NJ'),('Depauville','NY'),('Depauw','IN'),('Depew','NY'),('Depew','OK'),('Depoe Bay','OR'),('Deport','TX'),('Deposit','NY'),('Depue','IL'),('Deputy','IN'),('Dequincy','LA'),('Derby','CT'),('Derby','IA'),('Derby','IN'),('Derby','KS'),('Derby','NY'),('Derby','OH'),('Derby','VT'),('Derby Line','VT'),('Deridder','LA'),('Derma','MS'),('Dermott','AR'),('Derrick City','PA'),('Derry','NH'),('Derry','NM'),('Derry','PA'),('Derwent','OH'),('Derwood','MD'),('Des Allemands','LA'),('Des Arc','AR'),('Des Arc','MO'),('Des Lacs','ND'),('Des Moines','IA'),('Des Moines','NM'),('Des Plaines','IL'),('Descanso','CA'),('Desdemona','TX'),('Desert Center','CA'),('Desert Hot Springs','CA'),('Desha','AR'),('Deshler','NE'),('Deshler','OH'),('Desmet','ID'),('Desoto','TX'),('Destin','FL'),('Destrehan','LA'),('Detroit','AL'),('Detroit','ME'),('Detroit','MI'),('Detroit','OR'),('Detroit','TX'),('Detroit Lakes','MN'),('Devault','PA'),('Devens','MA'),('Devers','TX'),('Deville','LA'),('Devils Elbow','MO'),('Devils Lake','ND'),('Devils Tower','WY'),('Devine','TX'),('Devol','OK'),('Devon','PA'),('Dewar','IA'),('Dewar','OK'),('Dewart','PA'),('Deweese','NE'),('Dewey','AZ'),('Dewey','IL'),('Dewey','OK'),('Deweyville','TX'),('Deweyville','UT'),('Dewitt','IL'),('Dewitt','KY'),('Dewitt','MI'),('Dewitt','VA'),('Dewittville','NY'),('Dewy Rose','GA'),('Dexter','GA'),('Dexter','IA'),('Dexter','KS'),('Dexter','KY'),('Dexter','ME'),('Dexter','MI'),('Dexter','MN'),('Dexter','MO'),('Dexter','NM'),('Dexter','NY'),('Dexter','OR'),('Dexter City','OH'),('Diablo','CA'),('Diagonal','IA'),('Diamond','MO'),('Diamond','OH'),('Diamond','OR'),('Diamond Bar','CA'),('Diamond City','AR'),('Diamond Point','NY'),('Diamond Springs','CA'),('Diamondhead','MS'),('Diamondville','WY'),('Diana','TX'),('Diana','WV'),('Diaz','AR'),('Dibble','OK'),('Diberville','MS'),('Diboll','TX'),('Dice','KY'),('Dickens','IA'),('Dickens','NE'),('Dickens','TX'); + +INSERT INTO [cities] VALUES ('Dickerson','MD'),('Dickerson Run','PA'),('Dickey','ND'),('Dickeyville','WI'),('Dickinson','AL'),('Dickinson','ND'),('Dickinson','TX'),('Dickinson Center','NY'),('Dickson','TN'),('Dickson City','PA'),('Dierks','AR'),('Dieterich','IL'),('Dietrich','ID'),('Diggins','MO'),('Diggs','VA'),('Dighton','KS'),('Dighton','MA'),('Dike','IA'),('Dike','TX'),('Dill City','OK'),('Dillard','GA'),('Dillard','OR'),('Dille','WV'),('Diller','NE'),('Dilley','TX'),('Dilliner','PA'),('Dillingham','AK'),('Dillon','CO'),('Dillon','MT'),('Dillon','SC'),('Dillon Beach','CA'),('Dillonvale','OH'),('Dillsboro','IN'),('Dillsboro','NC'),('Dillsburg','PA'),('Dilltown','PA'),('Dillwyn','VA'),('Dilworth','MN'),('Dime Box','TX'),('Dimmitt','TX'),('Dimock','PA'),('Dimock','SD'),('Dimondale','MI'),('Dinero','TX'),('Dingess','WV'),('Dingle','ID'),('Dingmans Ferry','PA'),('Dinosaur','CO'),('Dinuba','CA'),('Dinwiddie','VA'),('Discovery Bay','CA'),('Disney','OK'),('Disputanta','VA'),('Distant','PA'),('District Heights','MD'),('Dittmer','MO'),('Divernon','IL'),('Divide','CO'),('Divide','MT'),('Dividing Creek','NJ'),('Dix','IL'),('Dix','NE'),('Dixfield','ME'),('Dixie','GA'),('Dixie','WA'),('Dixie','WV'),('Dixmont','ME'),('Dixon','CA'),('Dixon','IA'),('Dixon','IL'),('Dixon','KY'),('Dixon','MO'),('Dixon','MT'),('Dixon','NE'),('Dixon','NM'),('Dixon','WY'),('Dixon Springs','TN'),('Dixons Mills','AL'),('Dixonville','PA'),('Dobbin','TX'),('Dobbins','CA'),('Dobbs Ferry','NY'),('Dobson','NC'),('Docena','AL'),('Doctors Inlet','FL'),('Dodd City','TX'),('Doddridge','AR'),('Doddsville','MS'),('Dodge','ND'),('Dodge','NE'),('Dodge','TX'),('Dodge','WI'),('Dodge Center','MN'),('Dodge City','KS'),('Dodgeville','MI'),('Dodgeville','WI'),('Dodson','LA'),('Dodson','MT'),('Dodson','TX'),('Doe Hill','VA'),('Doe Run','MO'),('Doerun','GA'),('Dogue','VA'),('Dola','OH'),('Dolan Springs','AZ'),('Doland','SD'),('Dolgeville','NY'),('Dollar Bay','MI'),('Dolliver','IA'),('Dolomite','AL'),('Dolores','CO'),('Dolph','AR'),('Dolphin','VA'),('Dolton','IL'),('Dona Ana','NM'),('Donahue','IA'),('Donald','OR'),('Donalds','SC'),('Donaldson','AR'),('Donaldson','IN'),('Donaldson','MN'),('Donaldsonville','LA'),('Donalsonville','GA'),('Donegal','PA'),('Dongola','IL'),('Donie','TX'),('Doniphan','MO'),('Doniphan','NE'),('Donna','TX'),('Donnellson','IA'),('Donnellson','IL'),('Donnelly','ID'),('Donnelly','MN'),('Donnelsville','OH'),('Donner','LA'),('Donnybrook','ND'),('Donora','PA'),('Donovan','IL'),('Doole','TX'),('Doon','IA'),('Dora','AL'),('Dora','MO'),('Dora','NM'),('Dorado','PR'),('Doran','VA'),('Dorchester','IA'),('Dorchester','NE'),('Dorchester','NJ'),('Dorchester','SC'),('Dorchester','WI'),('Dorena','OR'),('Dormansville','NY'),('Dornsife','PA'),('Dorothy','NJ'),('Dorothy','WV'),('Dorr','MI'),('Dorrance','KS'),('Dorris','CA'),('Dorset','OH'),('Dorset','VT'),('Dorsey','IL'),('Dorton','KY'),('Dos Palos','CA'),('Dos Rios','CA'),('Doss','TX'),('Doswell','VA'),('Dothan','AL'),('Dothan','WV'),('Doty','WA'),('Double Springs','AL'),('Doucette','TX'),('Douds','IA'),('Dougherty','IA'),('Dougherty','OK'),('Dougherty','TX'),('Douglas','AK'),('Douglas','AL'),('Douglas','AZ'),('Douglas','GA'),('Douglas','MA'),('Douglas','MI'),('Douglas','ND'),('Douglas','NE'),('Douglas','OK'),('Douglas','WY'),('Douglas City','CA'),('Douglas Flat','CA'),('Douglass','KS'),('Douglass','TX'),('Douglassville','PA'),('Douglassville','TX'),('Douglasville','GA'),('Dousman','WI'),('Dove Creek','CO'),('Dover','AR'),('Dover','DE'),('Dover','FL'),('Dover','GA'),('Dover','ID'),('Dover','IL'),('Dover','KS'),('Dover','KY'),('Dover','MA'),('Dover','MN'),('Dover','MO'),('Dover','NC'),('Dover','NH'),('Dover','NJ'),('Dover','OH'),('Dover','OK'),('Dover','PA'),('Dover','TN'),('Dover Afb','DE'),('Dover Foxcroft','ME'),('Dover Plains','NY'),('Dovray','MN'),('Dow','IL'),('Dow City','IA'),('Dowagiac','MI'),('Dowell','IL'),('Dowell','MD'),('Dowelltown','TN'),('Dowling','MI'),('Downers Grove','IL'),('Downey','CA'),('Downey','ID'),('Downieville','CA'),('Downing','MO'),('Downing','WI'),('Downingtown','PA'),('Downs','IL'),('Downs','KS'),('Downsville','LA'),('Downsville','NY'),('Downsville','WI'),('Dows','IA'),('Doyle','CA'),('Doyle','TN'),('Doylesburg','PA'),('Doylestown','OH'),('Doylestown','PA'),('Doylestown','WI'),('Doyline','LA'),('Dozier','AL'),('Dracut','MA'),('Dragoon','AZ'),('Drain','OR'),('Drake','CO'),('Drake','KY'),('Drake','ND'),('Drakes Branch','VA'),('Drakesboro','KY'),('Drakesville','IA'),('Draper','SD'),('Draper','UT'),('Draper','VA'),('Drasco','AR'),('Dravosburg','PA'),('Drayden','MD'),('Drayton','ND'),('Drayton','SC'),('Drayton Plains','MI'),('Drennen','WV'),('Dresden','KS'),('Dresden','ME'),('Dresden','NY'),('Dresden','OH'),('Dresden','TN'),('Dresher','PA'),('Dresser','WI'),('Drew','MS'),('Drewryville','VA'),('Drewsey','OR'),('Drewsville','NH'),('Drexel','MO'),('Drexel','NC'),('Drexel Hill','PA'),('Drift','KY'),('Drifting','PA'),('Drifton','PA'),('Driftwood','PA'),('Driftwood','TX'),('Driggs','ID'),('Dripping Springs','TX'),('Driscoll','ND'),('Driscoll','TX'),('Driver','AR'),('Drummond','MT'),('Drummond','OK'),('Drummond','WI'),('Drummond Island','MI'),('Drummonds','TN'),('Drumore','PA'),('Drumright','OK'),('Drums','PA'),('Drury','MA'),('Drury','MO'),('Dry Branch','GA'),('Dry Creek','LA'),('Dry Creek','WV'),('Dry Fork','VA'),('Dry Prong','LA'),('Dry Ridge','KY'),('Dry Run','PA'),('Drybranch','WV'),('Dryden','ME'),('Dryden','MI'),('Dryden','NY'),('Dryden','TX'),('Dryden','VA'),('Dryden','WA'),('Dryfork','WV'),('Drytown','CA'),('Du Bois','IL'),('Du Bois','NE'),('Du Bois','PA'),('Du Pont','GA'),('Du Quoin','IL'),('Duanesburg','NY'),('Duarte','CA'),('Dubach','LA'),('Dubberly','LA'),('Dublin','CA'),('Dublin','GA'),('Dublin','IN'),('Dublin','MS'),('Dublin','NC'),('Dublin','NH'),('Dublin','OH'),('Dublin','PA'),('Dublin','TX'),('Dublin','VA'),('Dubois','ID'),('Dubois','IN'),('Dubois','WY'),('Dubre','KY'),('Dubuque','IA'),('Duchesne','UT'),('Duck','WV'),('Duck Creek Village','UT'),('Duck Hill','MS'),('Duck River','TN'),('Ducktown','TN'),('Duckwater','NV'),('Ducor','CA'),('Dudley','GA'),('Dudley','MA'),('Dudley','MO'),('Dudley','NC'),('Dudley','PA'),('Due West','SC'),('Duenweg','MO'),('Duff','TN'),('Duffield','VA'),('Dufur','OR'),('Dugger','IN'),('Dugspur','VA'),('Dugway','UT'),('Duke','MO'),('Duke','OK'),('Duke Center','PA'),('Dukedom','TN'),('Dulac','LA'),('Dulce','NM'),('Dulles','VA'),('Duluth','GA'),('Duluth','MN'),('Dulzura','CA'),('Dumas','AR'),('Dumas','MS'),('Dumas','TX'),('Dumfries','VA'),('Dumont','CO'),('Dumont','IA'),('Dumont','MN'),('Dumont','NJ'),('Dunbar','KY'),('Dunbar','NE'),('Dunbar','PA'),('Dunbar','WI'),('Dunbar','WV'),('Dunbarton','NH'),('Dunbridge','OH'),('Duncan','AZ'),('Duncan','MS'),('Duncan','NE'),('Duncan','OK'),('Duncan','SC'),('Duncan Falls','OH'),('Duncannon','PA'),('Duncans Mills','CA'),('Duncansville','PA'),('Duncanville','AL'),('Duncanville','TX'),('Duncombe','IA'),('Dundalk','MD'),('Dundas','IL'),('Dundas','MN'),('Dundas','VA'),('Dundee','FL'),('Dundee','IA'),('Dundee','IL'),('Dundee','KY'),('Dundee','MI'),('Dundee','MS'),('Dundee','NY'),('Dundee','OH'),('Dundee','OR'),('Dunedin','FL'),('Dunellen','NJ'),('Dunfermline','IL'),('Dungannon','VA'),('Dunkerton','IA'),('Dunkirk','IN'),('Dunkirk','MD'),('Dunkirk','NY'),('Dunkirk','OH'),('Dunlap','CA'),('Dunlap','IA'),('Dunlap','IL'),('Dunlap','TN'),('Dunlevy','PA'),('Dunlo','PA'),('Dunlow','WV'),('Dunmor','KY'),('Dunmore','WV'),('Dunn','NC'),('Dunn','TX'),('Dunn Center','ND'),('Dunn Loring','VA'),('Dunnegan','MO'),('Dunnell','MN'),('Dunnellon','FL'),('Dunnigan','CA'),('Dunning','NE'),('Dunnsville','VA'),('Dunnville','KY'),('Dunreith','IN'),('Dunseith','ND'),('Dunsmuir','CA'),('Dunstable','MA'),('Duplessis','LA'),('Dupo','IL'),('Dupont','CO'),('Dupont','IN'),('Dupont','LA'),('Dupont','OH'),('Dupont','WA'),('Dupree','SD'),('Dupuyer','MT'),('Duquesne','PA'),('Durand','IL'),('Durand','MI'),('Durand','WI'),('Durango','CO'),('Durango','IA'),('Durant','FL'),('Durant','IA'),('Durant','MS'),('Durant','OK'),('Durants Neck','NC'),('Durbin','WV'),('Durham','CA'),('Durham','CT'),('Durham','KS'),('Durham','ME'),('Durham','MO'),('Durham','NC'),('Durham','NH'),('Durham','NY'),('Durham','OK'),('Durham','PA'),('Durhamville','NY'),('Durkee','OR'),('Duryea','PA'),('Dushore','PA'),('Duson','LA'),('Dustin','OK'),('Dutch Flat','CA'),('Dutch Harbor','AK'),('Dutch John','UT'),('Dutchtown','MO'),('Dutton','AL'),('Dutton','MT'),('Dutton','VA'),('Dutzow','MO'),('Duvall','WA'),('Duxbury','MA'),('Dwale','KY'),('Dwarf','KY'),('Dwight','IL'),('Dwight','KS'),('Dwight','NE'),('Dycusburg','KY'),('Dyer','AR'),('Dyer','IN'),('Dyer','NV'),('Dyer','TN'),('Dyersburg','TN'),('Dyersville','IA'); + +INSERT INTO [cities] VALUES ('Dyess','AR'),('Dyess Afb','TX'),('Dyke','VA'),('Dysart','IA'),('Dysart','PA'),('Eads','CO'),('Eads','TN'),('Eagan','TN'),('Eagar','AZ'),('Eagarville','IL'),('Eagle','AK'),('Eagle','CO'),('Eagle','ID'),('Eagle','MI'),('Eagle','NE'),('Eagle','WI'),('Eagle Bay','NY'),('Eagle Bend','MN'),('Eagle Bridge','NY'),('Eagle Butte','SD'),('Eagle Creek','OR'),('Eagle Grove','IA'),('Eagle Lake','FL'),('Eagle Lake','ME'),('Eagle Lake','MN'),('Eagle Lake','TX'),('Eagle Mountain','UT'),('Eagle Nest','NM'),('Eagle Pass','TX'),('Eagle Point','OR'),('Eagle River','AK'),('Eagle River','WI'),('Eagle Rock','MO'),('Eagle Rock','VA'),('Eagle Springs','NC'),('Eagles Mere','PA'),('Eagletown','OK'),('Eagleville','CA'),('Eagleville','MO'),('Eagleville','PA'),('Eagleville','TN'),('Eakly','OK'),('Earl','NC'),('Earl Park','IN'),('Earle','AR'),('Earleton','FL'),('Earleville','MD'),('Earlham','IA'),('Earlimart','CA'),('Earling','IA'),('Earlington','KY'),('Earlington','PA'),('Earlsboro','OK'),('Earlton','NY'),('Earlville','IA'),('Earlville','IL'),('Earlville','NY'),('Earlville','PA'),('Early','IA'),('Early','TX'),('Early Branch','SC'),('Earlysville','VA'),('Earp','CA'),('Earth','TX'),('Earth City','MO'),('Easley','SC'),('East Alton','IL'),('East Amherst','NY'),('East Andover','ME'),('East Andover','NH'),('East Arlington','VT'),('East Aurora','NY'),('East Baldwin','ME'),('East Bank','WV'),('East Barre','VT'),('East Bend','NC'),('East Berkshire','VT'),('East Berlin','CT'),('East Berlin','PA'),('East Bernard','TX'),('East Berne','NY'),('East Bernstadt','KY'),('East Bethany','NY'),('East Bloomfield','NY'),('East Blue Hill','ME'),('East Boothbay','ME'),('East Boston','MA'),('East Brady','PA'),('East Branch','NY'),('East Bridgewater','MA'),('East Brookfield','MA'),('East Brunswick','NJ'),('East Burke','VT'),('East Butler','PA'),('East Calais','VT'),('East Canaan','CT'),('East Candia','NH'),('East Canton','OH'),('East Carbon','UT'),('East Carondelet','IL'),('East Charleston','VT'),('East Chatham','NY'),('East Chicago','IN'),('East China','MI'),('East Claridon','OH'),('East Concord','NY'),('East Corinth','VT'),('East Dennis','MA'),('East Derry','NH'),('East Dixfield','ME'),('East Dorset','VT'),('East Dover','VT'),('East Dublin','GA'),('East Dubuque','IL'),('East Durham','NY'),('East Earl','PA'),('East Ellijay','GA'),('East Ellsworth','WI'),('East Elmhurst','NY'),('East Enterprise','IN'),('East Fairfield','VT'),('East Falmouth','MA'),('East Flat Rock','NC'),('East Freedom','PA'),('East Freetown','MA'),('East Fultonham','OH'),('East Galesburg','IL'),('East Glacier Park','MT'),('East Glastonbury','CT'),('East Granby','CT'),('East Grand Forks','MN'),('East Greenbush','NY'),('East Greenville','PA'),('East Greenwich','RI'),('East Haddam','CT'),('East Hampstead','NH'),('East Hampton','CT'),('East Hampton','NY'),('East Hanover','NJ'),('East Hardwick','VT'),('East Hartford','CT'),('East Hartland','CT'),('East Haven','CT'),('East Haven','VT'),('East Helena','MT'),('East Hickory','PA'),('East Homer','NY'),('East Irvine','CA'),('East Islip','NY'),('East Jewett','NY'),('East Jordan','MI'),('East Killingly','CT'),('East Lansing','MI'),('East Leroy','MI'),('East Liberty','OH'),('East Livermore','ME'),('East Liverpool','OH'),('East Longmeadow','MA'),('East Lyme','CT'),('East Lynn','IL'),('East Lynn','WV'),('East Lynne','MO'),('East Machias','ME'),('East Mansfield','MA'),('East Marion','NY'),('East Mc Keesport','PA'),('East Meadow','NY'),('East Meredith','NY'),('East Middlebury','VT'),('East Millinocket','ME'),('East Millsboro','PA'),('East Moline','IL'),('East Montpelier','VT'),('East Moriches','NY'),('East Nassau','NY'),('East New Market','MD'),('East Newport','ME'),('East Northport','NY'),('East Norwich','NY'),('East Olympia','WA'),('East Orange','NJ'),('East Orland','ME'),('East Orleans','MA'),('East Otis','MA'),('East Otto','NY'),('East Palatka','FL'),('East Palestine','OH'),('East Parsonsfield','ME'),('East Pembroke','NY'),('East Peoria','IL'),('East Petersburg','PA'),('East Pharsalia','NY'),('East Pittsburgh','PA'),('East Point','KY'),('East Poland','ME'),('East Poultney','VT'),('East Prairie','MO'),('East Princeton','MA'),('East Prospect','PA'),('East Providence','RI'),('East Quogue','NY'),('East Randolph','NY'),('East Randolph','VT'),('East Rochester','NY'),('East Rochester','OH'),('East Rockaway','NY'),('East Rutherford','NJ'),('East Ryegate','VT'),('East Saint Johnsbury','VT'),('East Saint Louis','IL'),('East Sandwich','MA'),('East Schodack','NY'),('East Setauket','NY'),('East Smethport','PA'),('East Smithfield','PA'),('East Sparta','OH'),('East Spencer','NC'),('East Springfield','NY'),('East Springfield','OH'),('East Springfield','PA'),('East Stone Gap','VA'),('East Stroudsburg','PA'),('East Syracuse','NY'),('East Tallassee','AL'),('East Taunton','MA'),('East Tawas','MI'),('East Templeton','MA'),('East Texas','PA'),('East Thetford','VT'),('East Troy','WI'),('East Vandergrift','PA'),('East Vassalboro','ME'),('East Wakefield','NH'),('East Wallingford','VT'),('East Walpole','MA'),('East Wareham','MA'),('East Waterboro','ME'),('East Waterford','PA'),('East Wenatchee','WA'),('East Weymouth','MA'),('East Williamson','NY'),('East Wilton','ME'),('East Windsor','CT'),('East Windsor Hill','CT'),('East Winthrop','ME'),('East Woodstock','CT'),('East Worcester','NY'),('Eastaboga','AL'),('Eastabuchie','MS'),('Eastanollee','GA'),('Eastchester','NY'),('Eastern','KY'),('Eastford','CT'),('Eastham','MA'),('Easthampton','MA'),('Eastlake','CO'),('Eastlake','MI'),('Eastlake','OH'),('Eastlake Weir','FL'),('Eastland','TX'),('Eastman','GA'),('Eastman','WI'),('Easton','CT'),('Easton','IL'),('Easton','KS'),('Easton','MA'),('Easton','MD'),('Easton','ME'),('Easton','MN'),('Easton','MO'),('Easton','PA'),('Easton','TX'),('Easton','WA'),('Eastover','SC'),('Eastpoint','FL'),('Eastpointe','MI'),('Eastport','ID'),('Eastport','ME'),('Eastport','MI'),('Eastport','NY'),('Eastsound','WA'),('Eastview','KY'),('Eastville','VA'),('Eastwood','KY'),('Eaton','CO'),('Eaton','IN'),('Eaton','NY'),('Eaton','OH'),('Eaton','TN'),('Eaton Center','NH'),('Eaton Park','FL'),('Eaton Rapids','MI'),('Eatonton','GA'),('Eatontown','NJ'),('Eatonville','WA'),('Eau Claire','MI'),('Eau Claire','PA'),('Eau Claire','WI'),('Eau Galle','WI'),('Eben Junction','MI'),('Ebensburg','PA'),('Ebervale','PA'),('Ebony','VA'),('Ebro','FL'),('Eccles','WV'),('Echo','LA'),('Echo','MN'),('Echo','OR'),('Echo','UT'),('Echo Lake','CA'),('Echola','AL'),('Eckerman','MI'),('Eckert','CO'),('Eckerty','IN'),('Eckhart Mines','MD'),('Eckley','CO'),('Eckman','WV'),('Eclectic','AL'),('Ecleto','TX'),('Economy','IN'),('Ecorse','MI'),('Ecru','MS'),('Ector','TX'),('Edcouch','TX'),('Eddington','ME'),('Eddy','TX'),('Eddyville','IA'),('Eddyville','IL'),('Eddyville','KY'),('Eddyville','NE'),('Eddyville','OR'),('Edelstein','IL'),('Eden','AZ'),('Eden','GA'),('Eden','ID'),('Eden','MD'),('Eden','NC'),('Eden','NY'),('Eden','SD'),('Eden','TX'),('Eden','UT'),('Eden','VT'),('Eden','WI'),('Eden Mills','VT'),('Eden Prairie','MN'),('Eden Valley','MN'),('Edenton','NC'),('Edenville','MI'),('Edgar','FL'),('Edgar','MT'),('Edgar','NE'),('Edgar','WI'),('Edgar Springs','MO'),('Edgard','LA'),('Edgarton','WV'),('Edgartown','MA'),('Edgecomb','ME'),('Edgefield','SC'),('Edgeley','ND'),('Edgemont','AR'),('Edgemont','PA'),('Edgemont','SD'),('Edgemoor','SC'),('Edgerton','KS'),('Edgerton','MN'),('Edgerton','MO'),('Edgerton','OH'),('Edgerton','WI'),('Edgerton','WY'),('Edgewater','FL'),('Edgewater','MD'),('Edgewater','NJ'),('Edgewater','WI'),('Edgewood','IA'),('Edgewood','IL'),('Edgewood','MD'),('Edgewood','NM'),('Edgewood','TX'),('Edina','MO'),('Edinboro','PA'),('Edinburg','IL'),('Edinburg','ND'),('Edinburg','PA'),('Edinburg','TX'),('Edinburg','VA'),('Edinburgh','IN'),('Edison','CA'),('Edison','GA'),('Edison','NE'),('Edison','NJ'),('Edison','OH'),('Edisto Island','SC'),('Edmeston','NY'),('Edmond','OK'),('Edmond','WV'),('Edmonds','WA'),('Edmondson','AR'),('Edmonson','TX'),('Edmonton','KY'),('Edmore','MI'),('Edmore','ND'),('Edmund','WI'),('Edna','KS'),('Edna','TX'),('Edneyville','NC'),('Edon','OH'),('Edroy','TX'),('Edson','KS'),('Edwall','WA'),('Edward','NC'),('Edwards','CA'),('Edwards','CO'),('Edwards','IL'),('Edwards','MO'),('Edwards','MS'),('Edwards','NY'),('Edwardsburg','MI'),('Edwardsport','IN'),('Edwardsville','AL'),('Edwardsville','IL'),('Edwardsville','KS'),('Edwardsville','VA'),('Eek','AK'),('Effie','LA'),('Effie','MN'),('Effingham','IL'),('Effingham','KS'),('Effingham','NH'),('Effingham','SC'),('Effort','PA'),('Efland','NC'),('Egan','LA'),('Egan','SD'),('Egegik','AK'),('Egeland','ND'),('Egg Harbor','WI'),('Egg Harbor City','NJ'),('Egg Harbor Township','NJ'),('Eggleston','VA'),('Eglin Afb','FL'),('Eglon','WV'),('Egnar','CO'),('Egypt','AR'),('Egypt','TX'),('Ehrenberg','AZ'),('Ehrhardt','SC'),('Eidson','TN'),('Eielson Afb','AK'),('Eight Mile','AL'),('Eighty Eight','KY'),('Eighty Four','PA'),('Eitzen','MN'),('Ekalaka','MT'),('Ekron','KY'),('Ekwok','AK'),('El Cajon','CA'),('El Campo','TX'),('El Centro','CA'),('El Cerrito','CA'),('El Dorado','AR'),('El Dorado','CA'),('El Dorado','KS'),('El Dorado Hills','CA'),('El Dorado Springs','MO'),('El Granada','CA'),('El Indio','TX'),('El Jobean','FL'),('El Mirage','AZ'),('El Monte','CA'),('El Nido','CA'),('El Paso','AR'),('El Paso','IL'),('El Paso','TX'),('El Portal','CA'),('El Prado','NM'),('El Reno','OK'),('El Rito','NM'),('El Segundo','CA'),('El Sobrante','CA'),('El Toro','CA'),('El Verano','CA'),('Elaine','AR'),('Eland','WI'),('Elba','AL'),('Elba','NE'),('Elba','NY'),('Elbe','WA'),('Elberfeld','IN'),('Elberon','IA'),('Elberon','VA'),('Elbert','CO'),('Elbert','WV'),('Elberta','AL'),('Elberta','MI'),('Elberta','UT'),('Elberton','GA'),('Elbing','KS'),('Elbow Lake','MN'),('Elbridge','NY'),('Elburn','IL'); + +INSERT INTO [cities] VALUES ('Elcho','WI'),('Elco','PA'),('Eldena','IL'),('Elderon','WI'),('Elderton','PA'),('Eldon','IA'),('Eldon','MO'),('Eldora','IA'),('Eldorado','IL'),('Eldorado','OH'),('Eldorado','OK'),('Eldorado','TX'),('Eldorado','WI'),('Eldorado Springs','CO'),('Eldred','IL'),('Eldred','NY'),('Eldred','PA'),('Eldridge','AL'),('Eldridge','CA'),('Eldridge','IA'),('Eldridge','MO'),('Eleanor','WV'),('Electra','TX'),('Electric City','WA'),('Eleele','HI'),('Elephant Butte','NM'),('Eleroy','IL'),('Eleva','WI'),('Elfers','FL'),('Elfin Cove','AK'),('Elfrida','AZ'),('Elgin','AZ'),('Elgin','IA'),('Elgin','IL'),('Elgin','MN'),('Elgin','ND'),('Elgin','NE'),('Elgin','OH'),('Elgin','OK'),('Elgin','OR'),('Elgin','PA'),('Elgin','SC'),('Elgin','TN'),('Elgin','TX'),('Elida','NM'),('Elim','AK'),('Eliot','ME'),('Elizabeth','AR'),('Elizabeth','CO'),('Elizabeth','IL'),('Elizabeth','IN'),('Elizabeth','LA'),('Elizabeth','MN'),('Elizabeth','NJ'),('Elizabeth','PA'),('Elizabeth','WV'),('Elizabeth City','NC'),('Elizabethton','TN'),('Elizabethtown','IL'),('Elizabethtown','IN'),('Elizabethtown','KY'),('Elizabethtown','NC'),('Elizabethtown','NY'),('Elizabethtown','PA'),('Elizabethville','PA'),('Elizaville','KY'),('Elizaville','NY'),('Elk','CA'),('Elk','WA'),('Elk City','ID'),('Elk City','KS'),('Elk City','OK'),('Elk Creek','CA'),('Elk Creek','MO'),('Elk Creek','NE'),('Elk Creek','VA'),('Elk Falls','KS'),('Elk Garden','WV'),('Elk Grove','CA'),('Elk Grove Village','IL'),('Elk Horn','IA'),('Elk Horn','KY'),('Elk Mills','MD'),('Elk Mound','WI'),('Elk Mountain','WY'),('Elk Park','NC'),('Elk Point','SD'),('Elk Rapids','MI'),('Elk River','ID'),('Elk River','MN'),('Elka Park','NY'),('Elkader','IA'),('Elkfork','KY'),('Elkhart','IA'),('Elkhart','IL'),('Elkhart','IN'),('Elkhart','KS'),('Elkhart','TX'),('Elkhart Lake','WI'),('Elkhorn','NE'),('Elkhorn','WI'),('Elkhorn','WV'),('Elkhorn City','KY'),('Elkin','NC'),('Elkins','AR'),('Elkins','NH'),('Elkins','WV'),('Elkins Park','PA'),('Elkland','MO'),('Elkland','PA'),('Elkmont','AL'),('Elko','GA'),('Elko','MN'),('Elko','NV'),('Elko','SC'),('Elkport','IA'),('Elkridge','MD'),('Elkton','FL'),('Elkton','KY'),('Elkton','MD'),('Elkton','MI'),('Elkton','MN'),('Elkton','OH'),('Elkton','OR'),('Elkton','SD'),('Elkton','TN'),('Elkton','VA'),('Elkview','WV'),('Elkville','IL'),('Elkwood','VA'),('Ellabell','GA'),('Ellamore','WV'),('Ellaville','GA'),('Ellenboro','NC'),('Ellenboro','WV'),('Ellenburg','NY'),('Ellenburg Center','NY'),('Ellenburg Depot','NY'),('Ellendale','DE'),('Ellendale','MN'),('Ellendale','ND'),('Ellendale','TN'),('Ellensburg','WA'),('Ellenton','FL'),('Ellenton','GA'),('Ellenville','NY'),('Ellenwood','GA'),('Ellerbe','NC'),('Ellerslie','GA'),('Ellerslie','MD'),('Ellery','IL'),('Ellettsville','IN'),('Ellicott City','MD'),('Ellicottville','NY'),('Ellijay','GA'),('Ellinger','TX'),('Ellington','CT'),('Ellington','MO'),('Ellington','NY'),('Ellinwood','KS'),('Elliott','IA'),('Elliott','IL'),('Elliott','MS'),('Elliott','SC'),('Elliottsburg','PA'),('Elliottville','KY'),('Ellis','ID'),('Ellis','KS'),('Ellis Grove','IL'),('Ellisburg','NY'),('Ellison Bay','WI'),('Elliston','MT'),('Elliston','VA'),('Ellisville','IL'),('Ellisville','MS'),('Elloree','SC'),('Ellsinore','MO'),('Ellston','IA'),('Ellsworth','IA'),('Ellsworth','IL'),('Ellsworth','KS'),('Ellsworth','ME'),('Ellsworth','MI'),('Ellsworth','MN'),('Ellsworth','NE'),('Ellsworth','OH'),('Ellsworth','PA'),('Ellsworth','WI'),('Ellsworth Afb','SD'),('Ellwood City','PA'),('Elm','PA'),('Elm City','NC'),('Elm Creek','NE'),('Elm Grove','LA'),('Elm Grove','WI'),('Elm Hall','MI'),('Elm Mott','TX'),('Elm Springs','AR'),('Elma','IA'),('Elma','NY'),('Elma','WA'),('Elmaton','TX'),('Elmdale','KS'),('Elmendorf','TX'),('Elmendorf Afb','AK'),('Elmer','LA'),('Elmer','MO'),('Elmer','NJ'),('Elmer','OK'),('Elmer City','WA'),('Elmhurst','IL'),('Elmhurst','NY'),('Elmhurst','PA'),('Elmira','CA'),('Elmira','MI'),('Elmira','NY'),('Elmira','OR'),('Elmo','MO'),('Elmo','MT'),('Elmo','TX'),('Elmo','UT'),('Elmont','NY'),('Elmora','PA'),('Elmore','AL'),('Elmore','MN'),('Elmore','OH'),('Elmore City','OK'),('Elmsford','NY'),('Elmwood','IL'),('Elmwood','MA'),('Elmwood','NE'),('Elmwood','TN'),('Elmwood','WI'),('Elmwood Park','IL'),('Elmwood Park','NJ'),('Elnora','IN'),('Elon','NC'),('Elora','TN'),('Eloy','AZ'),('Elrama','PA'),('Elrod','AL'),('Elrosa','MN'),('Elroy','WI'),('Elsa','TX'),('Elsah','IL'),('Elsberry','MO'),('Elsie','MI'),('Elsie','NE'),('Elsinore','UT'),('Elsmere','NE'),('Elsmore','KS'),('Elton','LA'),('Elton','PA'),('Elton','WI'),('Elton','WV'),('Eltopia','WA'),('Elvaston','IL'),('Elverson','PA'),('Elverta','CA'),('Elwell','MI'),('Elwin','IL'),('Elwood','IL'),('Elwood','IN'),('Elwood','KS'),('Elwood','NE'),('Elwood','NJ'),('Ely','IA'),('Ely','MN'),('Ely','NV'),('Elyria','NE'),('Elyria','OH'),('Elysburg','PA'),('Elysian','MN'),('Elysian Fields','TX'),('Embarrass','MN'),('Embarrass','WI'),('Emblem','WY'),('Embudo','NM'),('Emden','IL'),('Emden','MO'),('Emeigh','PA'),('Emelle','AL'),('Emerado','ND'),('Emerald Isle','NC'),('Emerson','AR'),('Emerson','GA'),('Emerson','IA'),('Emerson','KY'),('Emerson','NE'),('Emerson','NJ'),('Emery','SD'),('Emery','UT'),('Emeryville','CA'),('Emigrant','MT'),('Emigrant Gap','CA'),('Emigsville','PA'),('Emily','MN'),('Eminence','IN'),('Eminence','KY'),('Eminence','MO'),('Emington','IL'),('Emlenton','PA'),('Emlyn','KY'),('Emma','IL'),('Emma','MO'),('Emmalena','KY'),('Emmaus','PA'),('Emmet','AR'),('Emmet','NE'),('Emmetsburg','IA'),('Emmett','ID'),('Emmett','KS'),('Emmett','MI'),('Emmitsburg','MD'),('Emmonak','AK'),('Emmons','MN'),('Emory','TX'),('Emory','VA'),('Empire','AL'),('Empire','CA'),('Empire','CO'),('Empire','LA'),('Empire','MI'),('Empire','NV'),('Empire','OH'),('Emporia','KS'),('Emporia','VA'),('Emporium','PA'),('Encampment','WY'),('Encinal','TX'),('Encinitas','CA'),('Encino','CA'),('Encino','NM'),('Encino','TX'),('Endeavor','PA'),('Endeavor','WI'),('Enderlin','ND'),('Enders','NE'),('Endicott','NE'),('Endicott','NY'),('Endicott','WA'),('Endwell','NY'),('Energy','IL'),('Energy','TX'),('Enfield','CT'),('Enfield','IL'),('Enfield','NC'),('Enfield','NH'),('Enfield Center','NH'),('Engadine','MI'),('Engelhard','NC'),('England','AR'),('Englewood','CO'),('Englewood','FL'),('Englewood','KS'),('Englewood','NJ'),('Englewood','OH'),('Englewood','TN'),('Englewood Cliffs','NJ'),('English','IN'),('Englishtown','NJ'),('Enid','MS'),('Enid','OK'),('Enigma','GA'),('Enka','NC'),('Enloe','TX'),('Ennice','NC'),('Enning','SD'),('Ennis','MT'),('Ennis','TX'),('Enochs','TX'),('Enola','AR'),('Enola','PA'),('Enon','OH'),('Enon Valley','PA'),('Enoree','SC'),('Enosburg Falls','VT'),('Ensenada','PR'),('Ensign','KS'),('Enterprise','AL'),('Enterprise','KS'),('Enterprise','LA'),('Enterprise','MS'),('Enterprise','OR'),('Enterprise','UT'),('Enterprise','WV'),('Entiat','WA'),('Entriken','PA'),('Enumclaw','WA'),('Enville','TN'),('Eola','IL'),('Eola','TX'),('Eolia','KY'),('Eolia','MO'),('Epes','AL'),('Ephraim','UT'),('Ephraim','WI'),('Ephrata','PA'),('Ephrata','WA'),('Epping','ND'),('Epping','NH'),('Epps','LA'),('Epsom','NH'),('Epworth','GA'),('Epworth','IA'),('Equality','AL'),('Equality','IL'),('Equinunk','PA'),('Era','TX'),('Erath','LA'),('Erbacon','WV'),('Erhard','MN'),('Erick','OK'),('Ericson','NE'),('Erie','CO'),('Erie','IL'),('Erie','KS'),('Erie','MI'),('Erie','ND'),('Erie','PA'),('Erieville','NY'),('Eriline','KY'),('Erin','NY'),('Erin','TN'),('Erlanger','KY'),('Ermine','KY'),('Ernest','PA'),('Ernul','NC'),('Eros','LA'),('Errol','NH'),('Erskine','MN'),('Erving','MA'),('Erwin','NC'),('Erwin','SD'),('Erwin','TN'),('Erwinna','PA'),('Erwinville','LA'),('Esbon','KS'),('Escalante','UT'),('Escalon','CA'),('Escanaba','MI'),('Escatawpa','MS'),('Escondido','CA'),('Eskdale','WV'),('Esko','MN'),('Eskridge','KS'),('Esmond','IL'),('Esmond','ND'),('Esmont','VA'),('Esom Hill','GA'),('Esopus','NY'),('Espanola','NM'),('Esparto','CA'),('Esperance','NY'),('Essex','CA'),('Essex','CT'),('Essex','IA'),('Essex','IL'),('Essex','MA'),('Essex','MD'),('Essex','MO'),('Essex','MT'),('Essex','NY'),('Essex','VT'),('Essex Fells','NJ'),('Essex Junction','VT'),('Essexville','MI'),('Essie','KY'),('Essig','MN'),('Essington','PA'),('Estacada','OR'),('Estancia','NM'),('Estcourt Station','ME'),('Estell Manor','NJ'),('Estelline','SD'),('Estelline','TX'),('Ester','AK'),('Estero','FL'),('Estes Park','CO'),('Estherville','IA'),('Estherwood','LA'),('Estill','SC'),('Estill Springs','TN'),('Estillfork','AL'),('Ethan','SD'),('Ethel','AR'),('Ethel','LA'),('Ethel','MO'),('Ethel','MS'),('Ethel','WA'),('Ethel','WV'),('Ethelsville','AL'),('Ether','NC'),('Ethridge','MT'),('Ethridge','TN'),('Etlan','VA'),('Etna','CA'),('Etna','ME'),('Etna','NH'),('Etna','NY'); + +INSERT INTO [cities] VALUES ('Etna','OH'),('Etna','WY'),('Etna Green','IN'),('Etoile','KY'),('Etoile','TX'),('Eton','GA'),('Etowah','AR'),('Etowah','NC'),('Etowah','TN'),('Etta','MS'),('Etters','PA'),('Ettrick','WI'),('Eubank','KY'),('Eucha','OK'),('Euclid','MN'),('Euclid','OH'),('Eudora','AR'),('Eudora','KS'),('Eudora','MO'),('Eufaula','AL'),('Eufaula','OK'),('Eugene','MO'),('Eugene','OR'),('Euless','TX'),('Eunice','LA'),('Eunice','MO'),('Eunice','NM'),('Eupora','MS'),('Eure','NC'),('Eureka','CA'),('Eureka','IL'),('Eureka','KS'),('Eureka','MI'),('Eureka','MO'),('Eureka','MT'),('Eureka','NV'),('Eureka','SD'),('Eureka','UT'),('Eureka','WI'),('Eureka Springs','AR'),('Eustace','TX'),('Eustis','FL'),('Eustis','ME'),('Eustis','NE'),('Eutaw','AL'),('Eutawville','SC'),('Eva','AL'),('Eva','TN'),('Evadale','TX'),('Evangeline','LA'),('Evans','CO'),('Evans','GA'),('Evans','LA'),('Evans','WA'),('Evans','WV'),('Evans City','PA'),('Evans Mills','NY'),('Evansdale','IA'),('Evansport','OH'),('Evanston','IL'),('Evanston','IN'),('Evanston','WY'),('Evansville','AR'),('Evansville','IL'),('Evansville','IN'),('Evansville','MN'),('Evansville','WI'),('Evansville','WY'),('Evant','TX'),('Evart','MI'),('Evarts','KY'),('Eveleth','MN'),('Evening Shade','AR'),('Evensville','TN'),('Everest','KS'),('Everett','MA'),('Everett','PA'),('Everett','WA'),('Everetts','NC'),('Everglades City','FL'),('Evergreen','CO'),('Evergreen','LA'),('Evergreen','NC'),('Evergreen','VA'),('Evergreen Park','IL'),('Everly','IA'),('Everson','PA'),('Everson','WA'),('Everton','AR'),('Everton','MO'),('Evington','VA'),('Evinston','FL'),('Ewa Beach','HI'),('Ewan','NJ'),('Ewell','MD'),('Ewen','MI'),('Ewing','IL'),('Ewing','KY'),('Ewing','MO'),('Ewing','NE'),('Ewing','NJ'),('Ewing','VA'),('Excel','AL'),('Excello','MO'),('Excelsior','MN'),('Excelsior Springs','MO'),('Exchange','WV'),('Exeland','WI'),('Exeter','CA'),('Exeter','ME'),('Exeter','MO'),('Exeter','NE'),('Exeter','NH'),('Exeter','RI'),('Exira','IA'),('Exline','IA'),('Exmore','VA'),('Experiment','GA'),('Export','PA'),('Exton','PA'),('Eyota','MN'),('Ezel','KY'),('Fabens','TX'),('Faber','VA'),('Fabius','NY'),('Fabyan','CT'),('Fackler','AL'),('Factoryville','PA'),('Fagus','MO'),('Fair Bluff','NC'),('Fair Grove','MO'),('Fair Haven','MI'),('Fair Haven','NJ'),('Fair Haven','NY'),('Fair Haven','VT'),('Fair Lawn','NJ'),('Fair Oaks','CA'),('Fair Oaks','IN'),('Fair Play','MO'),('Fair Play','SC'),('Fairacres','NM'),('Fairbank','IA'),('Fairbank','PA'),('Fairbanks','AK'),('Fairbanks','IN'),('Fairbanks','LA'),('Fairborn','OH'),('Fairburn','GA'),('Fairburn','SD'),('Fairbury','IL'),('Fairbury','NE'),('Fairchance','PA'),('Fairchild','WI'),('Fairchild Air Force Base','WA'),('Fairdale','KY'),('Fairdale','ND'),('Fairdale','WV'),('Fairdealing','MO'),('Fairfax','CA'),('Fairfax','IA'),('Fairfax','MN'),('Fairfax','MO'),('Fairfax','OK'),('Fairfax','SC'),('Fairfax','SD'),('Fairfax','VA'),('Fairfax','VT'),('Fairfax Station','VA'),('Fairfield','AL'),('Fairfield','CA'),('Fairfield','CT'),('Fairfield','FL'),('Fairfield','IA'),('Fairfield','ID'),('Fairfield','IL'),('Fairfield','KY'),('Fairfield','ME'),('Fairfield','MT'),('Fairfield','NC'),('Fairfield','ND'),('Fairfield','NE'),('Fairfield','NJ'),('Fairfield','OH'),('Fairfield','PA'),('Fairfield','TX'),('Fairfield','VA'),('Fairfield','VT'),('Fairfield','WA'),('Fairfield Bay','AR'),('Fairforest','SC'),('Fairgrove','MI'),('Fairhaven','MA'),('Fairhope','AL'),('Fairhope','PA'),('Fairland','IN'),('Fairland','OK'),('Fairlawn','OH'),('Fairlea','WV'),('Fairlee','VT'),('Fairless Hills','PA'),('Fairmont','MN'),('Fairmont','NC'),('Fairmont','NE'),('Fairmont','OK'),('Fairmont','WV'),('Fairmount','GA'),('Fairmount','IL'),('Fairmount','IN'),('Fairmount','ND'),('Fairmount City','PA'),('Fairplay','CO'),('Fairplay','MD'),('Fairpoint','OH'),('Fairport','MO'),('Fairport','NY'),('Fairton','NJ'),('Fairview','IL'),('Fairview','KS'),('Fairview','KY'),('Fairview','MI'),('Fairview','MO'),('Fairview','MT'),('Fairview','NC'),('Fairview','NJ'),('Fairview','OH'),('Fairview','OK'),('Fairview','OR'),('Fairview','PA'),('Fairview','SD'),('Fairview','TN'),('Fairview','UT'),('Fairview','WV'),('Fairview','WY'),('Fairview Heights','IL'),('Fairview Village','PA'),('Fairwater','WI'),('Faison','NC'),('Faith','NC'),('Faith','SD'),('Fajardo','PR'),('Falcon','KY'),('Falcon','MO'),('Falcon','MS'),('Falcon','NC'),('Falcon Heights','TX'),('Falconer','NY'),('Falfurrias','TX'),('Falkland','NC'),('Falkner','MS'),('Falkville','AL'),('Fall Branch','TN'),('Fall City','WA'),('Fall Creek','OR'),('Fall Creek','WI'),('Fall River','KS'),('Fall River','MA'),('Fall River','WI'),('Fall River Mills','CA'),('Fall Rock','KY'),('Fallbrook','CA'),('Fallentimber','PA'),('Falling Rock','WV'),('Falling Waters','WV'),('Fallon','MT'),('Fallon','NV'),('Falls','PA'),('Falls Church','VA'),('Falls City','NE'),('Falls City','OR'),('Falls City','TX'),('Falls Creek','PA'),('Falls Mills','VA'),('Falls Of Rough','KY'),('Falls Village','CT'),('Fallsburg','NY'),('Fallston','MD'),('Fallston','NC'),('Falmouth','IN'),('Falmouth','KY'),('Falmouth','MA'),('Falmouth','ME'),('Falmouth','MI'),('False Pass','AK'),('Falun','KS'),('Fancher','NY'),('Fancy Farm','KY'),('Fancy Gap','VA'),('Fannettsburg','PA'),('Fannin','TX'),('Fanrock','WV'),('Fanshawe','OK'),('Fanwood','NJ'),('Far Hills','NJ'),('Far Rockaway','NY'),('Farber','MO'),('Fargo','GA'),('Fargo','ND'),('Fargo','OK'),('Faribault','MN'),('Farina','IL'),('Farley','IA'),('Farley','MO'),('Farlington','KS'),('Farmdale','OH'),('Farmer','OH'),('Farmer City','IL'),('Farmers','KY'),('Farmersburg','IA'),('Farmersburg','IN'),('Farmersville','CA'),('Farmersville','IL'),('Farmersville','OH'),('Farmersville','TX'),('Farmersville Station','NY'),('Farmerville','LA'),('Farmingdale','ME'),('Farmingdale','NJ'),('Farmingdale','NY'),('Farmington','AR'),('Farmington','CA'),('Farmington','CT'),('Farmington','GA'),('Farmington','IA'),('Farmington','IL'),('Farmington','KY'),('Farmington','ME'),('Farmington','MI'),('Farmington','MN'),('Farmington','MO'),('Farmington','NH'),('Farmington','NM'),('Farmington','NY'),('Farmington','PA'),('Farmington','UT'),('Farmington','WA'),('Farmington','WV'),('Farmington Falls','ME'),('Farmingville','NY'),('Farmland','IN'),('Farmville','NC'),('Farmville','VA'),('Farnam','NE'),('Farner','TN'),('Farnham','NY'),('Farnham','VA'),('Farnhamville','IA'),('Farnsworth','TX'),('Farragut','IA'),('Farrar','MO'),('Farrell','MS'),('Farrell','PA'),('Farris','OK'),('Farson','WY'),('Farwell','MI'),('Farwell','MN'),('Farwell','NE'),('Farwell','TX'),('Fate','TX'),('Faucett','MO'),('Faulkner','MD'),('Faulkton','SD'),('Faunsdale','AL'),('Fawn Grove','PA'),('Fawnskin','CA'),('Faxon','OK'),('Fay','OK'),('Fayette','AL'),('Fayette','IA'),('Fayette','MO'),('Fayette','MS'),('Fayette','NY'),('Fayette','OH'),('Fayette','UT'),('Fayette City','PA'),('Fayetteville','AR'),('Fayetteville','GA'),('Fayetteville','NC'),('Fayetteville','NY'),('Fayetteville','OH'),('Fayetteville','PA'),('Fayetteville','TN'),('Fayetteville','TX'),('Fayetteville','WV'),('Fayville','MA'),('Faywood','NM'),('Feasterville Trevose','PA'),('Feather Falls','CA'),('Federal Dam','MN'),('Federal Way','WA'),('Federalsburg','MD'),('Fedhaven','FL'),('Fedora','SD'),('Fedscreek','KY'),('Feeding Hills','MA'),('Feesburg','OH'),('Felch','MI'),('Felda','FL'),('Felicity','OH'),('Fellows','CA'),('Fellsmere','FL'),('Felt','ID'),('Felt','OK'),('Felton','CA'),('Felton','DE'),('Felton','GA'),('Felton','MN'),('Felton','PA'),('Felts Mills','NY'),('Fence','WI'),('Fence Lake','NM'),('Fenelton','PA'),('Fenn','ID'),('Fennimore','WI'),('Fennville','MI'),('Fenton','IA'),('Fenton','IL'),('Fenton','LA'),('Fenton','MI'),('Fenton','MO'),('Fentress','TX'),('Fenwick','MI'),('Fenwick','WV'),('Fenwick Island','DE'),('Ferdinand','ID'),('Ferdinand','IN'),('Fergus Falls','MN'),('Ferguson','IA'),('Ferguson','KY'),('Ferguson','NC'),('Fernandina Beach','FL'),('Ferndale','CA'),('Ferndale','FL'),('Ferndale','MI'),('Ferndale','NY'),('Ferndale','PA'),('Ferndale','WA'),('Ferney','SD'),('Fernley','NV'),('Fernwood','ID'),('Fernwood','MS'),('Ferriday','LA'),('Ferris','IL'),('Ferris','TX'),('Ferrisburgh','VT'),('Ferron','UT'),('Ferrum','VA'),('Ferrysburg','MI'),('Ferryville','WI'),('Fertile','IA'),('Fertile','MN'),('Fessenden','ND'),('Festus','MO'),('Feura Bush','NY'),('Fiatt','IL'),('Fiddletown','CA'),('Fidelity','IL'),('Fieldale','VA'),('Fielding','UT'),('Fieldon','IL'),('Fields','OR'),('Fields Landing','CA'),('Fieldton','TX'),('Fife Lake','MI'),('Fifield','WI'),('Fifty Lakes','MN'),('Fifty Six','AR'),('Filer','ID'),('Filer City','MI'),('Filion','MI'),('Filley','NE'),('Fillmore','CA'),('Fillmore','IL'),('Fillmore','IN'),('Fillmore','MO'),('Fillmore','NY'),('Fillmore','UT'),('Fincastle','VA'),('Finchville','KY'),('Findlay','IL'),('Findlay','OH'),('Findley Lake','NY'),('Fine','NY'),('Fingal','ND'),('Finger','TN'),('Fingerville','SC'),('Finksburg','MD'),('Finland','MN'),('Finlayson','MN'),('Finley','CA'),('Finley','ND'),('Finley','OK'),('Finley','TN'),('Finleyville','PA'),('Finly','IN'),('Firebaugh','CA'),('Firestone','CO'),('Firth','ID'),('Firth','NE'),('Fischer','TX'),('Fish Camp','CA'); + +INSERT INTO [cities] VALUES ('Fish Creek','WI'),('Fish Haven','ID'),('Fisher','AR'),('Fisher','IL'),('Fisher','LA'),('Fisher','MN'),('Fisher','PA'),('Fisher','WV'),('Fishers','IN'),('Fishers','NY'),('Fishers Hill','VA'),('Fishers Island','NY'),('Fishers Landing','NY'),('Fishersville','VA'),('Fishertown','PA'),('Fisherville','KY'),('Fishing Creek','MD'),('Fishkill','NY'),('Fishs Eddy','NY'),('Fishtail','MT'),('Fisk','MO'),('Fiskdale','MA'),('Fiskeville','RI'),('Fisty','KY'),('Fitchburg','MA'),('Fithian','IL'),('Fittstown','OK'),('Fitzgerald','GA'),('Fitzhugh','OK'),('Fitzpatrick','AL'),('Fitzwilliam','NH'),('Five Points','AL'),('Five Points','CA'),('Five Points','TN'),('Flag Pond','TN'),('Flagler','CO'),('Flagler Beach','FL'),('Flagstaff','AZ'),('Flagtown','NJ'),('Flanagan','IL'),('Flanders','NJ'),('Flandreau','SD'),('Flasher','ND'),('Flat','TX'),('Flat Lick','KY'),('Flat Rock','AL'),('Flat Rock','IL'),('Flat Rock','IN'),('Flat Rock','MI'),('Flat Rock','NC'),('Flat Rock','OH'),('Flat Top','WV'),('Flatgap','KY'),('Flatonia','TX'),('Flatwoods','KY'),('Flatwoods','LA'),('Flatwoods','WV'),('Flaxton','ND'),('Flaxville','MT'),('Fleetville','PA'),('Fleetwood','NC'),('Fleetwood','PA'),('Fleischmanns','NY'),('Fleming','CO'),('Fleming','GA'),('Fleming','OH'),('Fleming','PA'),('Fleming Island','FL'),('Flemingsburg','KY'),('Flemington','MO'),('Flemington','NJ'),('Flemington','WV'),('Flensburg','MN'),('Fletcher','MO'),('Fletcher','NC'),('Fletcher','OH'),('Fletcher','OK'),('Flicksville','PA'),('Flint','MI'),('Flint','TX'),('Flint Hill','VA'),('Flinthill','MO'),('Flinton','PA'),('Flintstone','GA'),('Flintstone','MD'),('Flintville','TN'),('Flippin','AR'),('Flom','MN'),('Flomaton','AL'),('Flomot','TX'),('Floodwood','MN'),('Flora','IL'),('Flora','IN'),('Flora','LA'),('Flora','MS'),('Flora Vista','NM'),('Florahome','FL'),('Floral','AR'),('Floral City','FL'),('Floral Park','NY'),('Florala','AL'),('Florence','AL'),('Florence','AZ'),('Florence','CO'),('Florence','IN'),('Florence','KS'),('Florence','KY'),('Florence','MO'),('Florence','MS'),('Florence','MT'),('Florence','NJ'),('Florence','OR'),('Florence','SC'),('Florence','SD'),('Florence','TX'),('Florence','VT'),('Florence','WI'),('Floresville','TX'),('Florham Park','NJ'),('Florida','NY'),('Florida','PR'),('Florien','LA'),('Floris','IA'),('Florissant','CO'),('Florissant','MO'),('Floriston','CA'),('Flossmoor','IL'),('Flournoy','CA'),('Flourtown','PA'),('Flovilla','GA'),('Flower Mound','TX'),('Floweree','MT'),('Flowery Branch','GA'),('Flowood','MS'),('Floyd','IA'),('Floyd','NM'),('Floyd','VA'),('Floydada','TX'),('Floyds Knobs','IN'),('Fluker','LA'),('Flushing','MI'),('Flushing','NY'),('Flushing','OH'),('Fluvanna','TX'),('Fly Creek','NY'),('Flynn','TX'),('Fogelsville','PA'),('Folcroft','PA'),('Foley','AL'),('Foley','MN'),('Foley','MO'),('Folkston','GA'),('Follansbee','WV'),('Follett','TX'),('Folly Beach','SC'),('Folsom','CA'),('Folsom','LA'),('Folsom','NM'),('Folsom','PA'),('Folsom','WV'),('Folsomville','IN'),('Fombell','PA'),('Fond Du Lac','WI'),('Fonda','IA'),('Fonda','NY'),('Fontana','CA'),('Fontana','KS'),('Fontana','WI'),('Fontana Dam','NC'),('Fontanelle','IA'),('Fontanet','IN'),('Foosland','IL'),('Foothill Ranch','CA'),('Footville','WI'),('Forbes','MN'),('Forbes','ND'),('Forbes Road','PA'),('Forbestown','CA'),('Force','PA'),('Ford','KS'),('Ford','VA'),('Ford','WA'),('Ford City','PA'),('Ford Cliff','PA'),('Fordland','MO'),('Fordoche','LA'),('Fords','NJ'),('Fords Branch','KY'),('Fordsville','KY'),('Fordville','ND'),('Fordyce','AR'),('Fordyce','NE'),('Foreman','AR'),('Forest','IN'),('Forest','LA'),('Forest','MS'),('Forest','OH'),('Forest','VA'),('Forest City','IA'),('Forest City','IL'),('Forest City','MO'),('Forest City','NC'),('Forest City','PA'),('Forest Dale','VT'),('Forest Falls','CA'),('Forest Grove','MT'),('Forest Grove','OR'),('Forest Grove','PA'),('Forest Hill','LA'),('Forest Hill','MD'),('Forest Hill','WV'),('Forest Hills','KY'),('Forest Hills','NY'),('Forest Home','AL'),('Forest Junction','WI'),('Forest Knolls','CA'),('Forest Lake','MN'),('Forest Lakes','AZ'),('Forest Park','GA'),('Forest Park','IL'),('Forest Ranch','CA'),('Forest River','ND'),('Forestburg','TX'),('Forestburgh','NY'),('Forestdale','MA'),('Forestdale','RI'),('Foresthill','CA'),('Foreston','MN'),('Forestport','NY'),('Forestville','CA'),('Forestville','MI'),('Forestville','NY'),('Forestville','PA'),('Forestville','WI'),('Forgan','OK'),('Foristell','MO'),('Fork','MD'),('Fork','SC'),('Fork Union','VA'),('Forked River','NJ'),('Forkland','AL'),('Forks','WA'),('Forks Of Salmon','CA'),('Forksville','PA'),('Forman','ND'),('Formoso','KS'),('Forney','TX'),('Forrest','IL'),('Forrest City','AR'),('Forreston','IL'),('Forreston','TX'),('Forsan','TX'),('Forsyth','GA'),('Forsyth','IL'),('Forsyth','MO'),('Forsyth','MT'),('Fort Ann','NY'),('Fort Apache','AZ'),('Fort Ashby','WV'),('Fort Atkinson','IA'),('Fort Atkinson','WI'),('Fort Bayard','NM'),('Fort Belvoir','VA'),('Fort Benning','GA'),('Fort Benton','MT'),('Fort Bidwell','CA'),('Fort Blackmore','VA'),('Fort Bliss','TX'),('Fort Bragg','CA'),('Fort Bragg','NC'),('Fort Branch','IN'),('Fort Bridger','WY'),('Fort Buchanan','PR'),('Fort Calhoun','NE'),('Fort Campbell','KY'),('Fort Cobb','OK'),('Fort Collins','CO'),('Fort Covington','NY'),('Fort Davis','AL'),('Fort Davis','TX'),('Fort Defiance','AZ'),('Fort Defiance','VA'),('Fort Deposit','AL'),('Fort Dick','CA'),('Fort Dix','NJ'),('Fort Dodge','IA'),('Fort Dodge','KS'),('Fort Drum','NY'),('Fort Duchesne','UT'),('Fort Edward','NY'),('Fort Eustis','VA'),('Fort Fairfield','ME'),('Fort Gaines','GA'),('Fort Garland','CO'),('Fort Gay','WV'),('Fort George G Meade','MD'),('Fort Gibson','OK'),('Fort Gratiot','MI'),('Fort Greely','AK'),('Fort Hall','ID'),('Fort Hancock','TX'),('Fort Harrison','MT'),('Fort Hill','PA'),('Fort Howard','MD'),('Fort Huachuca','AZ'),('Fort Hunter','NY'),('Fort Irwin','CA'),('Fort Jennings','OH'),('Fort Johnson','NY'),('Fort Jones','CA'),('Fort Kent','ME'),('Fort Kent Mills','ME'),('Fort Klamath','OR'),('Fort Knox','KY'),('Fort Laramie','WY'),('Fort Lauderdale','FL'),('Fort Lawn','SC'),('Fort Leavenworth','KS'),('Fort Lee','NJ'),('Fort Lee','VA'),('Fort Leonard Wood','MO'),('Fort Littleton','PA'),('Fort Loramie','OH'),('Fort Loudon','PA'),('Fort Lupton','CO'),('Fort Lyon','CO'),('Fort Madison','IA'),('Fort Mc Coy','FL'),('Fort Mc Kavett','TX'),('Fort Mcdowell','AZ'),('Fort Meade','FL'),('Fort Meade','SD'),('Fort Mill','SC'),('Fort Mitchell','AL'),('Fort Mitchell','VA'),('Fort Mohave','AZ'),('Fort Monmouth','NJ'),('Fort Monroe','VA'),('Fort Montgomery','NY'),('Fort Morgan','CO'),('Fort Myers','FL'),('Fort Myers Beach','FL'),('Fort Necessity','LA'),('Fort Ogden','FL'),('Fort Oglethorpe','GA'),('Fort Payne','AL'),('Fort Peck','MT'),('Fort Pierce','FL'),('Fort Pierre','SD'),('Fort Plain','NY'),('Fort Polk','LA'),('Fort Ransom','ND'),('Fort Recovery','OH'),('Fort Richardson','AK'),('Fort Riley','KS'),('Fort Ripley','MN'),('Fort Ritner','IN'),('Fort Rock','OR'),('Fort Rucker','AL'),('Fort Scott','KS'),('Fort Shafter','HI'),('Fort Shaw','MT'),('Fort Sheridan','IL'),('Fort Sill','OK'),('Fort Smith','AR'),('Fort Stanton','NM'),('Fort Stewart','GA'),('Fort Stockton','TX'),('Fort Sumner','NM'),('Fort Supply','OK'),('Fort Thomas','AZ'),('Fort Thomas','KY'),('Fort Thompson','SD'),('Fort Totten','ND'),('Fort Towson','OK'),('Fort Valley','GA'),('Fort Valley','VA'),('Fort Wainwright','AK'),('Fort Walton Beach','FL'),('Fort Washakie','WY'),('Fort Washington','MD'),('Fort Washington','PA'),('Fort Wayne','IN'),('Fort White','FL'),('Fort Wingate','NM'),('Fort Worth','TX'),('Fort Yates','ND'),('Fort Yukon','AK'),('Fortescue','NJ'),('Fortine','MT'),('Fortson','GA'),('Fortuna','CA'),('Fortuna','MO'),('Fortuna','ND'),('Fortville','IN'),('Foss','OK'),('Fossil','OR'),('Fosston','MN'),('Foster','KY'),('Foster','MO'),('Foster','OK'),('Foster','OR'),('Foster','RI'),('Foster','VA'),('Foster','WV'),('Foster City','MI'),('Fosters','AL'),('Fosterville','TN'),('Fostoria','IA'),('Fostoria','KS'),('Fostoria','MI'),('Fostoria','OH'),('Fouke','AR'),('Fountain','CO'),('Fountain','FL'),('Fountain','MI'),('Fountain','MN'),('Fountain','NC'),('Fountain City','IN'),('Fountain City','WI'),('Fountain Green','UT'),('Fountain Hill','AR'),('Fountain Hills','AZ'),('Fountain Inn','SC'),('Fountain Run','KY'),('Fountain Valley','CA'),('Fountaintown','IN'),('Fountainville','PA'),('Four Corners','WY'),('Four Lakes','WA'),('Four Oaks','NC'),('Four States','WV'),('Fourmile','KY'),('Fowler','CA'),('Fowler','CO'),('Fowler','IL'),('Fowler','IN'),('Fowler','KS'),('Fowler','MI'),('Fowler','OH'),('Fowlerton','IN'),('Fowlerton','TX'),('Fowlerville','MI'),('Fowlstown','GA'),('Fox','AR'),('Fox','OK'),('Fox Island','WA'),('Fox Lake','IL'),('Fox Lake','WI'),('Fox River Grove','IL'),('Fox Valley','IL'),('Foxboro','MA'),('Foxboro','WI'),('Foxburg','PA'),('Foxhome','MN'),('Foxworth','MS'),('Foyil','OK'),('Frackville','PA'),('Frakes','KY'),('Frametown','WV'),('Framingham','MA'),('Francestown','NH'),('Francesville','IN'),('Francis','OK'),('Francis Creek','WI'),('Francisco','IN'),('Francitas','TX'),('Franconia','NH'),('Franconia','PA'),('Frankenmuth','MI'),('Frankewing','TN'),('Frankford','DE'),('Frankford','MO'),('Frankford','WV'),('Frankfort','IL'),('Frankfort','IN'),('Frankfort','KS'),('Frankfort','KY'),('Frankfort','ME'),('Frankfort','MI'),('Frankfort','NY'),('Frankfort','OH'),('Frankfort','SD'),('Frankfort Heights','IL'),('Franklin','AL'),('Franklin','AR'),('Franklin','GA'),('Franklin','ID'),('Franklin','IL'),('Franklin','IN'),('Franklin','KS'),('Franklin','KY'),('Franklin','LA'),('Franklin','MA'),('Franklin','ME'),('Franklin','MI'),('Franklin','MN'),('Franklin','MO'),('Franklin','NC'),('Franklin','NE'); + +INSERT INTO [cities] VALUES ('Franklin','NH'),('Franklin','NJ'),('Franklin','NY'),('Franklin','OH'),('Franklin','PA'),('Franklin','TN'),('Franklin','TX'),('Franklin','VA'),('Franklin','VT'),('Franklin','WI'),('Franklin','WV'),('Franklin Furnace','OH'),('Franklin Grove','IL'),('Franklin Lakes','NJ'),('Franklin Park','IL'),('Franklin Park','NJ'),('Franklin Springs','GA'),('Franklin Springs','NY'),('Franklin Square','NY'),('Franklinton','LA'),('Franklinton','NC'),('Franklintown','PA'),('Franklinville','NC'),('Franklinville','NJ'),('Franklinville','NY'),('Frankston','TX'),('Franksville','WI'),('Frankton','IN'),('Franktown','CO'),('Franktown','VA'),('Frankville','AL'),('Frannie','WY'),('Fraser','CO'),('Fraser','MI'),('Frazee','MN'),('Frazer','MT'),('Frazeysburg','OH'),('Frazier Park','CA'),('Fraziers Bottom','WV'),('Fred','TX'),('Frederic','MI'),('Frederic','WI'),('Frederica','DE'),('Frederick','CO'),('Frederick','IL'),('Frederick','MD'),('Frederick','OK'),('Frederick','PA'),('Frederick','SD'),('Fredericksburg','IA'),('Fredericksburg','IN'),('Fredericksburg','OH'),('Fredericksburg','PA'),('Fredericksburg','TX'),('Fredericksburg','VA'),('Fredericktown','MO'),('Fredericktown','OH'),('Fredericktown','PA'),('Frederika','IA'),('Fredonia','AZ'),('Fredonia','KS'),('Fredonia','KY'),('Fredonia','ND'),('Fredonia','NY'),('Fredonia','PA'),('Fredonia','TX'),('Fredonia','WI'),('Free Soil','MI'),('Free Union','VA'),('Freeborn','MN'),('Freeburg','IL'),('Freeburg','MO'),('Freeburg','PA'),('Freeburn','KY'),('Freedom','CA'),('Freedom','IN'),('Freedom','ME'),('Freedom','NH'),('Freedom','NY'),('Freedom','OK'),('Freedom','PA'),('Freedom','WI'),('Freedom','WY'),('Freehold','NJ'),('Freehold','NY'),('Freeland','MD'),('Freeland','MI'),('Freeland','PA'),('Freeland','WA'),('Freelandville','IN'),('Freeman','MO'),('Freeman','SD'),('Freeman','VA'),('Freeman','WV'),('Freeman Spur','IL'),('Freeport','FL'),('Freeport','IL'),('Freeport','KS'),('Freeport','ME'),('Freeport','MI'),('Freeport','MN'),('Freeport','NY'),('Freeport','OH'),('Freeport','PA'),('Freeport','TX'),('Freer','TX'),('Freetown','IN'),('Freeville','NY'),('Freistatt','MO'),('Fremont','CA'),('Fremont','IA'),('Fremont','IN'),('Fremont','MI'),('Fremont','MO'),('Fremont','NC'),('Fremont','NE'),('Fremont','NH'),('Fremont','OH'),('Fremont','WI'),('Fremont Center','NY'),('French Camp','CA'),('French Camp','MS'),('French Creek','WV'),('French Gulch','CA'),('French Lick','IN'),('French Settlement','LA'),('French Village','MO'),('Frenchboro','ME'),('Frenchburg','KY'),('Frenchglen','OR'),('Frenchmans Bayou','AR'),('Frenchton','WV'),('Frenchtown','MT'),('Frenchtown','NJ'),('Frenchville','ME'),('Frenchville','PA'),('Fresh Meadows','NY'),('Fresno','CA'),('Fresno','OH'),('Fresno','TX'),('Frewsburg','NY'),('Friant','CA'),('Friars Point','MS'),('Friday Harbor','WA'),('Friedens','PA'),('Friedensburg','PA'),('Friedheim','MO'),('Friend','NE'),('Friendly','WV'),('Friendship','AR'),('Friendship','IN'),('Friendship','MD'),('Friendship','ME'),('Friendship','NY'),('Friendship','OH'),('Friendship','TN'),('Friendship','WI'),('Friendsville','MD'),('Friendsville','PA'),('Friendsville','TN'),('Friendswood','TX'),('Frierson','LA'),('Fries','VA'),('Friesland','WI'),('Friona','TX'),('Frisco','CO'),('Frisco','NC'),('Frisco','TX'),('Frisco City','AL'),('Fritch','TX'),('Frohna','MO'),('Froid','MT'),('Fromberg','MT'),('Front Royal','VA'),('Frontenac','KS'),('Frontenac','MN'),('Frontier','MI'),('Frontier','WY'),('Frost','MN'),('Frost','TX'),('Frostburg','MD'),('Frostburg','PA'),('Frostproof','FL'),('Fruita','CO'),('Fruitdale','AL'),('Fruithurst','AL'),('Fruitland','IA'),('Fruitland','ID'),('Fruitland','MD'),('Fruitland','NM'),('Fruitland','UT'),('Fruitland','WA'),('Fruitland Park','FL'),('Fruitport','MI'),('Fruitvale','TN'),('Fruitvale','TX'),('Fryburg','PA'),('Fryeburg','ME'),('Ft Mitchell','KY'),('Ft Myer','VA'),('Ft Warren Afb','WY'),('Fulda','IN'),('Fulda','MN'),('Fulks Run','VA'),('Fullerton','CA'),('Fullerton','ND'),('Fullerton','NE'),('Fulshear','TX'),('Fulton','AL'),('Fulton','AR'),('Fulton','CA'),('Fulton','IL'),('Fulton','IN'),('Fulton','KS'),('Fulton','KY'),('Fulton','MD'),('Fulton','MI'),('Fulton','MO'),('Fulton','MS'),('Fulton','NY'),('Fulton','OH'),('Fulton','SD'),('Fulton','TX'),('Fultondale','AL'),('Fultonham','NY'),('Fultonham','OH'),('Fultonville','NY'),('Fults','IL'),('Funk','NE'),('Funkstown','MD'),('Funston','GA'),('Fuquay Varina','NC'),('Furlong','PA'),('Furman','AL'),('Furman','SC'),('Fyffe','AL'),('Gaastra','MI'),('Gabbs','NV'),('Gable','SC'),('Gabriels','NY'),('Gackle','ND'),('Gadsden','AL'),('Gadsden','AZ'),('Gadsden','SC'),('Gadsden','TN'),('Gaffney','SC'),('Gage','OK'),('Gagetown','MI'),('Gail','TX'),('Gaines','MI'),('Gaines','PA'),('Gainesboro','TN'),('Gainestown','AL'),('Gainesville','AL'),('Gainesville','FL'),('Gainesville','GA'),('Gainesville','MO'),('Gainesville','NY'),('Gainesville','TX'),('Gainesville','VA'),('Gaithersburg','MD'),('Gakona','AK'),('Galata','MT'),('Galatia','IL'),('Galax','VA'),('Galena','AK'),('Galena','IL'),('Galena','KS'),('Galena','MD'),('Galena','MO'),('Galena','OH'),('Galena Park','TX'),('Gales Creek','OR'),('Gales Ferry','CT'),('Galesburg','IL'),('Galesburg','KS'),('Galesburg','MI'),('Galesburg','ND'),('Galesville','MD'),('Galesville','WI'),('Galeton','CO'),('Galeton','PA'),('Galien','MI'),('Galion','OH'),('Galivants Ferry','SC'),('Gallagher','WV'),('Gallant','AL'),('Gallatin','MO'),('Gallatin','TN'),('Gallatin','TX'),('Gallatin Gateway','MT'),('Gallaway','TN'),('Galliano','LA'),('Gallina','NM'),('Gallion','AL'),('Gallipolis','OH'),('Gallipolis Ferry','WV'),('Gallitzin','PA'),('Gallman','MS'),('Galloway','OH'),('Galloway','WI'),('Galloway','WV'),('Gallup','NM'),('Gallupville','NY'),('Galt','CA'),('Galt','IA'),('Galt','IL'),('Galt','MO'),('Galva','IA'),('Galva','IL'),('Galva','KS'),('Galveston','IN'),('Galveston','TX'),('Galvin','WA'),('Galway','NY'),('Gamaliel','AR'),('Gamaliel','KY'),('Gambell','AK'),('Gambier','OH'),('Gambrills','MD'),('Gamerco','NM'),('Ganado','AZ'),('Ganado','TX'),('Gandeeville','WV'),('Gann Valley','SD'),('Gans','OK'),('Gans','PA'),('Gansevoort','NY'),('Gantt','AL'),('Gap','PA'),('Gap Mills','WV'),('Gapville','KY'),('Garards Fort','PA'),('Garber','IA'),('Garber','OK'),('Garberville','CA'),('Garcia','CO'),('Garciasville','TX'),('Garden','MI'),('Garden City','AL'),('Garden City','IA'),('Garden City','ID'),('Garden City','KS'),('Garden City','LA'),('Garden City','MI'),('Garden City','MN'),('Garden City','MO'),('Garden City','NY'),('Garden City','SD'),('Garden City','TX'),('Garden City','UT'),('Garden Grove','CA'),('Garden Grove','IA'),('Garden Plain','KS'),('Garden Prairie','IL'),('Garden Valley','CA'),('Garden Valley','ID'),('Gardena','CA'),('Gardendale','AL'),('Gardendale','TX'),('Gardenville','PA'),('Gardiner','ME'),('Gardiner','MT'),('Gardiner','NY'),('Gardiner','OR'),('Gardner','CO'),('Gardner','IL'),('Gardner','KS'),('Gardner','LA'),('Gardner','MA'),('Gardner','ND'),('Gardners','PA'),('Gardnerville','NV'),('Garfield','AR'),('Garfield','GA'),('Garfield','KS'),('Garfield','KY'),('Garfield','MN'),('Garfield','NJ'),('Garfield','NM'),('Garfield','WA'),('Garibaldi','OR'),('Garita','NM'),('Garland','KS'),('Garland','ME'),('Garland','NC'),('Garland','NE'),('Garland','PA'),('Garland','TX'),('Garland','UT'),('Garland City','AR'),('Garnavillo','IA'),('Garner','AR'),('Garner','IA'),('Garner','KY'),('Garner','NC'),('Garnerville','NY'),('Garnett','KS'),('Garnett','SC'),('Garrard','KY'),('Garrattsville','NY'),('Garretson','SD'),('Garrett','IN'),('Garrett','KY'),('Garrett','PA'),('Garrett','WY'),('Garrett Park','MD'),('Garrettsville','OH'),('Garrison','IA'),('Garrison','KY'),('Garrison','MN'),('Garrison','MO'),('Garrison','MT'),('Garrison','ND'),('Garrison','NY'),('Garrison','TX'),('Garrison','UT'),('Garrisonville','VA'),('Garrochales','PR'),('Garryowen','MT'),('Garvin','MN'),('Garvin','OK'),('Garwin','IA'),('Garwood','NJ'),('Garwood','TX'),('Gary','IN'),('Gary','MN'),('Gary','SD'),('Gary','TX'),('Gary','WV'),('Garysburg','NC'),('Garyville','LA'),('Gas','KS'),('Gas City','IN'),('Gasburg','VA'),('Gasconade','MO'),('Gasport','NY'),('Gasquet','CA'),('Gassaway','WV'),('Gassville','AR'),('Gaston','IN'),('Gaston','NC'),('Gaston','OR'),('Gaston','SC'),('Gastonia','NC'),('Gastonville','PA'),('Gate','OK'),('Gate City','VA'),('Gates','NC'),('Gates','OR'),('Gates','TN'),('Gates Mills','OH'),('Gatesville','NC'),('Gatesville','TX'),('Gateway','AR'),('Gateway','CO'),('Gatewood','MO'),('Gatlinburg','TN'),('Gattman','MS'),('Gatzke','MN'),('Gauley Bridge','WV'),('Gause','TX'),('Gautier','MS'),('Gay','GA'),('Gay','WV'),('Gaylesville','AL'),('Gaylord','KS'),('Gaylord','MI'),('Gaylord','MN'),('Gaylordsville','CT'),('Gays','IL'),('Gays Creek','KY'),('Gays Mills','WI'),('Gaysville','VT'),('Gayville','SD'),('Gazelle','CA'),('Geary','OK'),('Geddes','SD'),('Geff','IL'),('Geigertown','PA'),('Geismar','LA'),('Gem','KS'),('Gene Autry','OK'),('Genesee','ID'),('Genesee','MI'),('Genesee','PA'),('Genesee Depot','WI'),('Geneseo','IL'),('Geneseo','KS'),('Geneseo','NY'),('Geneva','AL'),('Geneva','FL'),('Geneva','GA'),('Geneva','IA'),('Geneva','ID'),('Geneva','IL'),('Geneva','IN'),('Geneva','MN'),('Geneva','NE'),('Geneva','NY'); + +INSERT INTO [cities] VALUES ('Geneva','OH'),('Genoa','AR'),('Genoa','CO'),('Genoa','IL'),('Genoa','NE'),('Genoa','NV'),('Genoa','NY'),('Genoa','OH'),('Genoa','WI'),('Genoa','WV'),('Genoa City','WI'),('Gentry','AR'),('Gentry','MO'),('Gentryville','IN'),('George','IA'),('George','WA'),('George West','TX'),('Georges Mills','NH'),('Georgetown','CA'),('Georgetown','CO'),('Georgetown','CT'),('Georgetown','DE'),('Georgetown','FL'),('Georgetown','GA'),('Georgetown','ID'),('Georgetown','IL'),('Georgetown','IN'),('Georgetown','KY'),('Georgetown','LA'),('Georgetown','MA'),('Georgetown','MD'),('Georgetown','ME'),('Georgetown','MN'),('Georgetown','MS'),('Georgetown','NY'),('Georgetown','OH'),('Georgetown','PA'),('Georgetown','SC'),('Georgetown','TN'),('Georgetown','TX'),('Georgiana','AL'),('Gepp','AR'),('Gerald','MO'),('Geraldine','AL'),('Geraldine','MT'),('Gerber','CA'),('Gering','NE'),('Gerlach','NV'),('Gerlaw','IL'),('German Valley','IL'),('Germansville','PA'),('Germanton','NC'),('Germantown','IL'),('Germantown','KY'),('Germantown','MD'),('Germantown','NY'),('Germantown','OH'),('Germantown','TN'),('Germantown','WI'),('Germfask','MI'),('Geronimo','OK'),('Geronimo','TX'),('Gerrardstown','WV'),('Gerry','NY'),('Gerton','NC'),('Gervais','OR'),('Gettysburg','OH'),('Gettysburg','PA'),('Gettysburg','SD'),('Getzville','NY'),('Geuda Springs','KS'),('Geyser','MT'),('Geyserville','CA'),('Gheens','LA'),('Ghent','KY'),('Ghent','MN'),('Ghent','NY'),('Ghent','WV'),('Gibbon','MN'),('Gibbon','NE'),('Gibbon Glade','PA'),('Gibbonsville','ID'),('Gibbs','MO'),('Gibbsboro','NJ'),('Gibbstown','NJ'),('Gibsland','LA'),('Gibson','GA'),('Gibson','IA'),('Gibson','LA'),('Gibson','MO'),('Gibson','NC'),('Gibson','PA'),('Gibson','TN'),('Gibson City','IL'),('Gibson Island','MD'),('Gibsonburg','OH'),('Gibsonia','PA'),('Gibsonton','FL'),('Gibsonville','NC'),('Giddings','TX'),('Gideon','MO'),('Gifford','IA'),('Gifford','IL'),('Gifford','PA'),('Gifford','SC'),('Gifford','WA'),('Gig Harbor','WA'),('Gila','NM'),('Gila Bend','AZ'),('Gilbert','AR'),('Gilbert','AZ'),('Gilbert','IA'),('Gilbert','LA'),('Gilbert','MN'),('Gilbert','PA'),('Gilbert','SC'),('Gilbert','WV'),('Gilberton','PA'),('Gilbertown','AL'),('Gilberts','IL'),('Gilbertsville','KY'),('Gilbertsville','NY'),('Gilbertsville','PA'),('Gilbertville','IA'),('Gilbertville','MA'),('Gilboa','NY'),('Gilboa','WV'),('Gilby','ND'),('Gilchrist','OR'),('Gilchrist','TX'),('Gilcrest','CO'),('Gildford','MT'),('Gile','WI'),('Gilead','NE'),('Gilford','MI'),('Gilford','NH'),('Gill','CO'),('Gill','MA'),('Gillespie','IL'),('Gillett','AR'),('Gillett','PA'),('Gillett','TX'),('Gillett','WI'),('Gillett Grove','IA'),('Gillette','NJ'),('Gillette','WY'),('Gillham','AR'),('Gilliam','LA'),('Gilliam','MO'),('Gillsville','GA'),('Gilman','CT'),('Gilman','IA'),('Gilman','IL'),('Gilman','MN'),('Gilman','VT'),('Gilman','WI'),('Gilman City','MO'),('Gilmanton','NH'),('Gilmanton','WI'),('Gilmanton Iron Works','NH'),('Gilmer','TX'),('Gilmore','AR'),('Gilmore City','IA'),('Gilroy','CA'),('Gilson','IL'),('Gilsum','NH'),('Giltner','NE'),('Gipsy','MO'),('Gipsy','PA'),('Girard','GA'),('Girard','IL'),('Girard','KS'),('Girard','OH'),('Girard','PA'),('Girard','TX'),('Girardville','PA'),('Girdler','KY'),('Girdletree','MD'),('Girdwood','AK'),('Girvin','TX'),('Given','WV'),('Gladbrook','IA'),('Glade','KS'),('Glade Hill','VA'),('Glade Park','CO'),('Glade Spring','VA'),('Glade Valley','NC'),('Gladeville','TN'),('Gladewater','TX'),('Gladstone','IL'),('Gladstone','MI'),('Gladstone','ND'),('Gladstone','NJ'),('Gladstone','NM'),('Gladstone','OR'),('Gladstone','VA'),('Gladwin','MI'),('Gladwyne','PA'),('Glady','WV'),('Gladys','VA'),('Glandorf','OH'),('Glasco','KS'),('Glasco','NY'),('Glasford','IL'),('Glasgow','KY'),('Glasgow','MO'),('Glasgow','MT'),('Glasgow','PA'),('Glasgow','VA'),('Glasgow','WV'),('Glassboro','NJ'),('Glasser','NJ'),('Glassport','PA'),('Glasston','ND'),('Glastonbury','CT'),('Gleason','TN'),('Gleason','WI'),('Glen','MS'),('Glen','MT'),('Glen','NH'),('Glen','WV'),('Glen Allan','MS'),('Glen Allen','AL'),('Glen Allen','VA'),('Glen Alpine','NC'),('Glen Arbor','MI'),('Glen Arm','MD'),('Glen Aubrey','NY'),('Glen Burnie','MD'),('Glen Campbell','PA'),('Glen Carbon','IL'),('Glen Cove','ME'),('Glen Cove','NY'),('Glen Dale','WV'),('Glen Daniel','WV'),('Glen Easton','WV'),('Glen Echo','MD'),('Glen Elder','KS'),('Glen Ellen','CA'),('Glen Ellyn','IL'),('Glen Ferris','WV'),('Glen Flora','TX'),('Glen Flora','WI'),('Glen Fork','WV'),('Glen Gardner','NJ'),('Glen Haven','CO'),('Glen Haven','WI'),('Glen Head','NY'),('Glen Hope','PA'),('Glen Jean','WV'),('Glen Lyn','VA'),('Glen Lyon','PA'),('Glen Mills','PA'),('Glen Oaks','NY'),('Glen Richey','PA'),('Glen Riddle Lima','PA'),('Glen Ridge','NJ'),('Glen Rock','NJ'),('Glen Rock','PA'),('Glen Rogers','WV'),('Glen Rose','TX'),('Glen Saint Mary','FL'),('Glen Spey','NY'),('Glen Ullin','ND'),('Glen White','WV'),('Glen Wild','NY'),('Glen Wilton','VA'),('Glenallen','MO'),('Glenarm','IL'),('Glenbeulah','WI'),('Glenbrook','NV'),('Glenburn','ND'),('Glencliff','NH'),('Glencoe','AR'),('Glencoe','CA'),('Glencoe','IL'),('Glencoe','KY'),('Glencoe','MN'),('Glencoe','MO'),('Glencoe','NM'),('Glencoe','OH'),('Glencoe','OK'),('Glencross','SD'),('Glendale','AZ'),('Glendale','CA'),('Glendale','KY'),('Glendale','MA'),('Glendale','OR'),('Glendale','RI'),('Glendale','SC'),('Glendale','UT'),('Glendale Heights','IL'),('Glendale Luke Afb','AZ'),('Glendale Springs','NC'),('Glendive','MT'),('Glendo','WY'),('Glendora','CA'),('Glendora','MS'),('Glendora','NJ'),('Gleneden Beach','OR'),('Glenelg','MD'),('Glenfield','ND'),('Glenfield','NY'),('Glenford','NY'),('Glenford','OH'),('Glengary','WV'),('Glenham','NY'),('Glenham','SD'),('Glenhaven','CA'),('Glenmont','NY'),('Glenmont','OH'),('Glenmoore','PA'),('Glenmora','LA'),('Glenn','CA'),('Glenn','GA'),('Glenn','MI'),('Glenn Dale','MD'),('Glennallen','AK'),('Glennie','MI'),('Glenns Ferry','ID'),('Glennville','CA'),('Glennville','GA'),('Glenolden','PA'),('Glenoma','WA'),('Glenpool','OK'),('Glenrock','WY'),('Glens Falls','NY'),('Glens Fork','KY'),('Glenshaw','PA'),('Glenside','PA'),('Glentana','MT'),('Glenview','IL'),('Glenview','KY'),('Glenvil','NE'),('Glenville','MN'),('Glenville','NC'),('Glenville','PA'),('Glenville','WV'),('Glenwood','AL'),('Glenwood','AR'),('Glenwood','FL'),('Glenwood','GA'),('Glenwood','IA'),('Glenwood','IL'),('Glenwood','IN'),('Glenwood','MD'),('Glenwood','MN'),('Glenwood','MO'),('Glenwood','NC'),('Glenwood','NJ'),('Glenwood','NM'),('Glenwood','NY'),('Glenwood','UT'),('Glenwood','WA'),('Glenwood','WV'),('Glenwood City','WI'),('Glenwood Landing','NY'),('Glenwood Springs','CO'),('Glidden','IA'),('Glidden','TX'),('Glidden','WI'),('Glide','OR'),('Globe','AZ'),('Glorieta','NM'),('Gloster','LA'),('Gloster','MS'),('Gloucester','MA'),('Gloucester','NC'),('Gloucester','VA'),('Gloucester City','NJ'),('Gloucester Point','VA'),('Glouster','OH'),('Glover','VT'),('Gloversville','NY'),('Gloverville','SC'),('Glyndon','MD'),('Glyndon','MN'),('Glynn','LA'),('Gnadenhutten','OH'),('Gober','TX'),('Gobler','MO'),('Gobles','MI'),('Goddard','KS'),('Godfrey','IL'),('Godley','TX'),('Godwin','NC'),('Goehner','NE'),('Goessel','KS'),('Goetzville','MI'),('Goff','KS'),('Goffstown','NH'),('Golconda','IL'),('Golconda','NV'),('Gold Bar','WA'),('Gold Beach','OR'),('Gold Creek','MT'),('Gold Hill','NC'),('Gold Hill','OR'),('Gold Run','CA'),('Golden','CO'),('Golden','IL'),('Golden','MO'),('Golden','MS'),('Golden','OK'),('Golden','TX'),('Golden City','MO'),('Golden Eagle','IL'),('Golden Gate','IL'),('Golden Meadow','LA'),('Golden Valley','AZ'),('Golden Valley','ND'),('Goldendale','WA'),('Goldenrod','FL'),('Goldens Bridge','NY'),('Goldfield','IA'),('Goldfield','NV'),('Goldonna','LA'),('Goldsboro','MD'),('Goldsboro','NC'),('Goldsboro','TX'),('Goldsmith','IN'),('Goldsmith','TX'),('Goldston','NC'),('Goldthwaite','TX'),('Goldvein','VA'),('Goleta','CA'),('Golf','IL'),('Goliad','TX'),('Goltry','OK'),('Golva','ND'),('Gomer','OH'),('Gonvick','MN'),('Gonzales','CA'),('Gonzales','LA'),('Gonzales','TX'),('Gonzalez','FL'),('Goochland','VA'),('Good Hart','MI'),('Good Hope','GA'),('Good Hope','IL'),('Good Thunder','MN'),('Goode','VA'),('Goodell','IA'),('Goodells','MI'),('Goodfellow Afb','TX'),('Goodfield','IL'),('Goodhue','MN'),('Gooding','ID'),('Goodland','FL'),('Goodland','IN'),('Goodland','KS'),('Goodland','MN'),('Goodlettsville','TN'),('Goodman','MO'),('Goodman','MS'),('Goodman','WI'),('Goodnews Bay','AK'),('Goodrich','MI'),('Goodrich','ND'),('Goodrich','TX'),('Goodridge','MN'),('Goodspring','TN'),('Goodsprings','AL'),('Goodview','VA'),('Goodville','PA'),('Goodwater','AL'),('Goodway','AL'),('Goodwell','OK'),('Goodwin','AR'),('Goodwin','SD'),('Goodwine','IL'),('Goodyear','AZ'),('Goodyears Bar','CA'),('Goose Creek','SC'),('Goose Lake','IA'),('Goose Prairie','WA'),('Goose Rock','KY'),('Gordo','AL'),('Gordon','AL'),('Gordon','GA'),('Gordon','KY'),('Gordon','NE'),('Gordon','PA'),('Gordon','TX'),('Gordon','WI'),('Gordon','WV'),('Gordonsville','TN'),('Gordonsville','VA'),('Gordonville','MO'),('Gordonville','PA'),('Gordonville','TX'),('Gore','OK'),('Gore','VA'),('Gore Springs','MS'),('Goree','TX'),('Goreville','IL'),('Gorham','IL'),('Gorham','KS'),('Gorham','ME'),('Gorham','NH'); + +INSERT INTO [cities] VALUES ('Gorham','NY'),('Gorin','MO'),('Gorman','TX'),('Gormania','WV'),('Gorum','LA'),('Goshen','AL'),('Goshen','AR'),('Goshen','CA'),('Goshen','CT'),('Goshen','IN'),('Goshen','KY'),('Goshen','MA'),('Goshen','NH'),('Goshen','NJ'),('Goshen','NY'),('Goshen','OH'),('Goshen','UT'),('Goshen','VA'),('Gosnell','AR'),('Gosport','IN'),('Gotebo','OK'),('Gotha','FL'),('Gotham','WI'),('Gothenburg','NE'),('Gough','GA'),('Gould','AR'),('Gould','OK'),('Gould City','MI'),('Gouldbusk','TX'),('Gouldsboro','ME'),('Gouldsboro','PA'),('Gouverneur','NY'),('Gove','KS'),('Government Camp','OR'),('Gowanda','NY'),('Gowen','MI'),('Gowen','OK'),('Gower','MO'),('Gowrie','IA'),('Grabill','IN'),('Grace','ID'),('Grace','MS'),('Grace City','ND'),('Gracemont','OK'),('Graceville','FL'),('Graceville','MN'),('Gracewood','GA'),('Gracey','KY'),('Grady','AL'),('Grady','AR'),('Grady','NM'),('Gradyville','KY'),('Gradyville','PA'),('Graettinger','IA'),('Graff','MO'),('Graford','TX'),('Grafton','IA'),('Grafton','IL'),('Grafton','MA'),('Grafton','ND'),('Grafton','NE'),('Grafton','NH'),('Grafton','NY'),('Grafton','OH'),('Grafton','VT'),('Grafton','WI'),('Grafton','WV'),('Graham','AL'),('Graham','FL'),('Graham','KY'),('Graham','MO'),('Graham','NC'),('Graham','OK'),('Graham','TX'),('Graham','WA'),('Grahamsville','NY'),('Grahn','KY'),('Grain Valley','MO'),('Grainfield','KS'),('Grambling','LA'),('Gramercy','LA'),('Gramling','SC'),('Grammer','IN'),('Grampian','PA'),('Granada','CO'),('Granada','MN'),('Granada Hills','CA'),('Granbury','TX'),('Granby','CO'),('Granby','CT'),('Granby','MA'),('Granby','MO'),('Granby','VT'),('Grand Bay','AL'),('Grand Blanc','MI'),('Grand Cane','LA'),('Grand Canyon','AZ'),('Grand Chain','IL'),('Grand Chenier','LA'),('Grand Coteau','LA'),('Grand Coulee','WA'),('Grand Forks','ND'),('Grand Forks Afb','ND'),('Grand Gorge','NY'),('Grand Haven','MI'),('Grand Island','FL'),('Grand Island','NE'),('Grand Island','NY'),('Grand Isle','LA'),('Grand Isle','ME'),('Grand Isle','VT'),('Grand Junction','CO'),('Grand Junction','IA'),('Grand Junction','MI'),('Grand Junction','TN'),('Grand Lake','CO'),('Grand Lake Stream','ME'),('Grand Ledge','MI'),('Grand Marais','MI'),('Grand Marais','MN'),('Grand Marsh','WI'),('Grand Meadow','MN'),('Grand Mound','IA'),('Grand Portage','MN'),('Grand Prairie','TX'),('Grand Rapids','MI'),('Grand Rapids','MN'),('Grand Rapids','OH'),('Grand Ridge','FL'),('Grand Ridge','IL'),('Grand River','IA'),('Grand River','OH'),('Grand Rivers','KY'),('Grand Ronde','OR'),('Grand Saline','TX'),('Grand Terrace','CA'),('Grand Tower','IL'),('Grand Valley','PA'),('Grand View','ID'),('Grand View','WI'),('Grandfalls','TX'),('Grandfield','OK'),('Grandin','FL'),('Grandin','MO'),('Grandin','ND'),('Grandview','IA'),('Grandview','IN'),('Grandview','MO'),('Grandview','TN'),('Grandview','TX'),('Grandview','WA'),('Grandville','MI'),('Grandy','MN'),('Grandy','NC'),('Granger','IA'),('Granger','IN'),('Granger','MO'),('Granger','TX'),('Granger','WA'),('Granger','WY'),('Grangeville','ID'),('Granite','CO'),('Granite','OK'),('Granite Bay','CA'),('Granite Canon','WY'),('Granite City','IL'),('Granite Falls','MN'),('Granite Falls','NC'),('Granite Falls','WA'),('Granite Quarry','NC'),('Granite Springs','NY'),('Graniteville','SC'),('Graniteville','VT'),('Grannis','AR'),('Grant','AL'),('Grant','CO'),('Grant','FL'),('Grant','IA'),('Grant','LA'),('Grant','MI'),('Grant','NE'),('Grant','OK'),('Grant City','MO'),('Grant Park','IL'),('Grant Town','WV'),('Grantham','NH'),('Grantham','PA'),('Granton','WI'),('Grants','NM'),('Grants Pass','OR'),('Grantsboro','NC'),('Grantsburg','IL'),('Grantsburg','IN'),('Grantsburg','WI'),('Grantsdale','MT'),('Grantsville','MD'),('Grantsville','UT'),('Grantsville','WV'),('Grantville','GA'),('Grantville','KS'),('Grantville','PA'),('Granville','IA'),('Granville','IL'),('Granville','MA'),('Granville','ND'),('Granville','NY'),('Granville','OH'),('Granville','PA'),('Granville','TN'),('Granville','VT'),('Granville','WV'),('Granville Summit','PA'),('Grapeland','TX'),('Grapeview','WA'),('Grapeville','PA'),('Grapevine','AR'),('Grapevine','TX'),('Grasonville','MD'),('Grass Creek','IN'),('Grass Lake','MI'),('Grass Range','MT'),('Grass Valley','CA'),('Grass Valley','OR'),('Grassflat','PA'),('Grasston','MN'),('Grassy Butte','ND'),('Grassy Creek','NC'),('Grassy Meadows','WV'),('Gratiot','OH'),('Gratiot','WI'),('Gratis','OH'),('Graton','CA'),('Gratz','PA'),('Gravel Switch','KY'),('Gravelly','AR'),('Graves Mill','VA'),('Gravette','AR'),('Gravity','IA'),('Gravois Mills','MO'),('Grawn','MI'),('Gray','GA'),('Gray','IA'),('Gray','KY'),('Gray','LA'),('Gray','ME'),('Gray','PA'),('Gray Court','SC'),('Gray Hawk','KY'),('Gray Mountain','AZ'),('Gray Summit','MO'),('Grayland','WA'),('Grayling','AK'),('Grayling','MI'),('Graymont','IL'),('Grayridge','MO'),('Grays Knob','KY'),('Grays River','WA'),('Grayslake','IL'),('Grayson','GA'),('Grayson','KY'),('Grayson','LA'),('Graysville','AL'),('Graysville','GA'),('Graysville','IN'),('Graysville','OH'),('Graysville','PA'),('Graysville','TN'),('Graytown','OH'),('Grayville','IL'),('Great Barrington','MA'),('Great Bend','KS'),('Great Bend','NY'),('Great Bend','PA'),('Great Cacapon','WV'),('Great Falls','MT'),('Great Falls','SC'),('Great Falls','VA'),('Great Lakes','IL'),('Great Meadows','NJ'),('Great Mills','MD'),('Great Neck','NY'),('Great River','NY'),('Great Valley','NY'),('Greeley','CO'),('Greeley','IA'),('Greeley','KS'),('Greeley','NE'),('Greeley','PA'),('Greeleyville','SC'),('Green','KS'),('Green','OH'),('Green Bank','WV'),('Green Bay','VA'),('Green Bay','WI'),('Green Camp','OH'),('Green Castle','MO'),('Green City','MO'),('Green Cove Springs','FL'),('Green Creek','NJ'),('Green Forest','AR'),('Green Harbor','MA'),('Green Isle','MN'),('Green Lake','WI'),('Green Lane','PA'),('Green Mountain','NC'),('Green Mountain Falls','CO'),('Green Pond','AL'),('Green Pond','SC'),('Green Ridge','MO'),('Green River','UT'),('Green River','WY'),('Green Road','KY'),('Green Sea','SC'),('Green Spring','WV'),('Green Springs','OH'),('Green Sulphur Springs','WV'),('Green Valley','AZ'),('Green Valley','IL'),('Green Valley','WI'),('Green Valley Lake','CA'),('Green Village','NJ'),('Greenacres','WA'),('Greenback','TN'),('Greenbackville','VA'),('Greenbank','WA'),('Greenbelt','MD'),('Greenbrae','CA'),('Greenbrier','AR'),('Greenbrier','TN'),('Greenbush','MA'),('Greenbush','ME'),('Greenbush','MI'),('Greenbush','MN'),('Greenbush','VA'),('Greenbush','WI'),('Greencastle','IN'),('Greencastle','PA'),('Greencreek','ID'),('Greendale','WI'),('Greendell','NJ'),('Greene','IA'),('Greene','ME'),('Greene','NY'),('Greene','RI'),('Greeneville','TN'),('Greenfield','CA'),('Greenfield','IA'),('Greenfield','IL'),('Greenfield','IN'),('Greenfield','MA'),('Greenfield','MO'),('Greenfield','NH'),('Greenfield','OH'),('Greenfield','OK'),('Greenfield','TN'),('Greenfield Center','NY'),('Greenfield Park','NY'),('Greenford','OH'),('Greenhurst','NY'),('Greenland','AR'),('Greenland','MI'),('Greenland','NH'),('Greenlawn','NY'),('Greenleaf','ID'),('Greenleaf','KS'),('Greenleaf','WI'),('Greenock','PA'),('Greenport','NY'),('Greens Farms','CT'),('Greens Fork','IN'),('Greensboro','AL'),('Greensboro','FL'),('Greensboro','GA'),('Greensboro','IN'),('Greensboro','MD'),('Greensboro','NC'),('Greensboro','PA'),('Greensboro','VT'),('Greensboro Bend','VT'),('Greensburg','IN'),('Greensburg','KS'),('Greensburg','KY'),('Greensburg','LA'),('Greensburg','PA'),('Greentop','MO'),('Greentown','IN'),('Greentown','OH'),('Greentown','PA'),('Greenup','IL'),('Greenup','KY'),('Greenvale','NY'),('Greenview','CA'),('Greenview','IL'),('Greenville','AL'),('Greenville','CA'),('Greenville','FL'),('Greenville','GA'),('Greenville','IA'),('Greenville','IL'),('Greenville','IN'),('Greenville','KY'),('Greenville','ME'),('Greenville','MI'),('Greenville','MO'),('Greenville','MS'),('Greenville','NC'),('Greenville','NH'),('Greenville','NY'),('Greenville','OH'),('Greenville','PA'),('Greenville','RI'),('Greenville','SC'),('Greenville','TX'),('Greenville','UT'),('Greenville','VA'),('Greenville','WI'),('Greenville','WV'),('Greenville Junction','ME'),('Greenwald','MN'),('Greenway','AR'),('Greenway','VA'),('Greenwell Springs','LA'),('Greenwich','CT'),('Greenwich','KS'),('Greenwich','NJ'),('Greenwich','NY'),('Greenwich','OH'),('Greenwich','UT'),('Greenwood','AR'),('Greenwood','CA'),('Greenwood','DE'),('Greenwood','FL'),('Greenwood','IN'),('Greenwood','LA'),('Greenwood','ME'),('Greenwood','MO'),('Greenwood','MS'),('Greenwood','NE'),('Greenwood','NY'),('Greenwood','SC'),('Greenwood','TX'),('Greenwood','VA'),('Greenwood','WI'),('Greenwood Lake','NY'),('Greenwood Springs','MS'),('Greer','AZ'),('Greer','SC'),('Gregory','AR'),('Gregory','MI'),('Gregory','SD'),('Gregory','TX'),('Greig','NY'),('Grelton','OH'),('Grenada','CA'),('Grenada','MS'),('Grenloch','NJ'),('Grenola','KS'),('Grenora','ND'),('Grenville','NM'),('Grenville','SD'),('Gresham','NE'),('Gresham','OR'),('Gresham','SC'),('Gresham','WI'),('Grethel','KY'),('Gretna','FL'),('Gretna','LA'),('Gretna','NE'),('Gretna','VA'),('Grey Eagle','MN'),('Greybull','WY'),('Greycliff','MT'),('Greystone Park','NJ'),('Gridley','CA'),('Gridley','IL'),('Gridley','KS'),('Griffin','GA'),('Griffin','IN'),('Griffith','IN'),('Griffithsville','WV'),('Griffithville','AR'),('Grifton','NC'),('Griggsville','IL'),('Grimes','CA'),('Grimes','IA'),('Grimesland','NC'),('Grimsley','TN'),('Grimstead','VA'),('Grindstone','PA'),('Grinnell','IA'),('Grinnell','KS'),('Grissom Arb','IN'),('Griswold','IA'),('Grizzly Flats','CA'),('Groesbeck','TX'),('Groom','TX'),('Grosse Ile','MI'),('Grosse Pointe','MI'),('Grosse Tete','LA'),('Grosvenor Dale','CT'); + +INSERT INTO [cities] VALUES ('Groton','CT'),('Groton','MA'),('Groton','NY'),('Groton','SD'),('Groton','VT'),('Grottoes','VA'),('Grouse Creek','UT'),('Grove','OK'),('Grove City','MN'),('Grove City','OH'),('Grove City','PA'),('Grove Hill','AL'),('Groveland','CA'),('Groveland','FL'),('Groveland','IL'),('Groveland','MA'),('Groveland','NY'),('Groveoak','AL'),('Groveport','OH'),('Grover','CO'),('Grover','MO'),('Grover','NC'),('Grover','PA'),('Grover','SC'),('Grover','WY'),('Grover Beach','CA'),('Grover Hill','OH'),('Grovertown','IN'),('Groves','TX'),('Grovespring','MO'),('Groveton','TX'),('Grovetown','GA'),('Grubbs','AR'),('Grubville','MO'),('Gruetli Laager','TN'),('Grulla','TX'),('Grundy','VA'),('Grundy Center','IA'),('Gruver','IA'),('Gruver','TX'),('Grygla','MN'),('Guadalupe','CA'),('Guadalupita','NM'),('Gualala','CA'),('Guanica','PR'),('Guasti','CA'),('Guatay','CA'),('Guayama','PR'),('Guayanilla','PR'),('Guaynabo','PR'),('Guerneville','CA'),('Guernsey','IA'),('Guernsey','WY'),('Guerra','TX'),('Gueydan','LA'),('Guffey','CO'),('Guide Rock','NE'),('Guild','NH'),('Guild','TN'),('Guilderland','NY'),('Guilderland Center','NY'),('Guildhall','VT'),('Guilford','CT'),('Guilford','IN'),('Guilford','ME'),('Guilford','MO'),('Guilford','NY'),('Guin','AL'),('Guinda','CA'),('Guion','AR'),('Gulf','NC'),('Gulf Breeze','FL'),('Gulf Hammock','FL'),('Gulf Shores','AL'),('Gulfport','MS'),('Gulliver','MI'),('Gully','MN'),('Gulston','KY'),('Gum Spring','VA'),('Gunlock','KY'),('Gunlock','UT'),('Gunnison','CO'),('Gunnison','MS'),('Gunnison','UT'),('Gunpowder','MD'),('Gunter','TX'),('Guntersville','AL'),('Guntown','MS'),('Gurabo','PR'),('Gurdon','AR'),('Gurley','AL'),('Gurley','NE'),('Gurnee','IL'),('Gustavus','AK'),('Gustine','CA'),('Gustine','TX'),('Guston','KY'),('Guthrie','KY'),('Guthrie','OK'),('Guthrie','TX'),('Guthrie Center','IA'),('Guttenberg','IA'),('Guy','AR'),('Guy','TX'),('Guymon','OK'),('Guys','TN'),('Guys Mills','PA'),('Guysville','OH'),('Guyton','GA'),('Gwinn','MI'),('Gwinner','ND'),('Gwynedd','PA'),('Gwynedd Valley','PA'),('Gwynn','VA'),('Gwynn Oak','MD'),('Gwynneville','IN'),('Gypsum','CO'),('Gypsum','KS'),('Gypsum','OH'),('Gypsy','WV'),('Hachita','NM'),('Hacienda Heights','CA'),('Hackberry','AZ'),('Hackberry','LA'),('Hackensack','MN'),('Hackensack','NJ'),('Hacker Valley','WV'),('Hackett','AR'),('Hackettstown','NJ'),('Hackleburg','AL'),('Hacksneck','VA'),('Hadar','NE'),('Haddam','CT'),('Haddam','KS'),('Haddock','GA'),('Haddon Heights','NJ'),('Haddon Township','NJ'),('Haddonfield','NJ'),('Hadensville','VA'),('Hadley','MA'),('Hadley','MI'),('Hadley','NY'),('Hadley','PA'),('Hadlyme','CT'),('Hagaman','NY'),('Hagan','GA'),('Hagar Shores','MI'),('Hagarstown','IL'),('Hagarville','AR'),('Hager City','WI'),('Hagerhill','KY'),('Hagerman','ID'),('Hagerman','NM'),('Hagerstown','IN'),('Hagerstown','MD'),('Hague','ND'),('Hague','NY'),('Hague','VA'),('Hahira','GA'),('Hahnville','LA'),('Haigler','NE'),('Haiku','HI'),('Hailesboro','NY'),('Hailey','ID'),('Haileyville','OK'),('Haines','AK'),('Haines','OR'),('Haines City','FL'),('Haines Falls','NY'),('Hainesport','NJ'),('Hakalau','HI'),('Halbur','IA'),('Halcottsville','NY'),('Hale','MI'),('Hale','MO'),('Hale Center','TX'),('Haledon','NJ'),('Haleiwa','HI'),('Hales Corners','WI'),('Halethorpe','MD'),('Haleyville','AL'),('Half Moon Bay','CA'),('Half Way','MO'),('Halfway','OR'),('Halifax','MA'),('Halifax','NC'),('Halifax','PA'),('Halifax','VA'),('Hall','MT'),('Hall','NY'),('Hall Summit','LA'),('Hallam','NE'),('Hallandale','FL'),('Halleck','NV'),('Hallett','OK'),('Hallettsville','TX'),('Halliday','ND'),('Hallie','KY'),('Hallieford','VA'),('Hallock','MN'),('Hallowell','ME'),('Halls','TN'),('Hallsboro','NC'),('Hallstead','PA'),('Hallsville','MO'),('Hallsville','OH'),('Hallsville','TX'),('Halltown','MO'),('Halltown','WV'),('Hallwood','VA'),('Halma','MN'),('Halsey','NE'),('Halsey','OR'),('Halstad','MN'),('Halstead','KS'),('Haltom City','TX'),('Hambleton','WV'),('Hamburg','AR'),('Hamburg','IA'),('Hamburg','IL'),('Hamburg','LA'),('Hamburg','MI'),('Hamburg','MN'),('Hamburg','NJ'),('Hamburg','NY'),('Hamburg','PA'),('Hamden','CT'),('Hamden','NY'),('Hamden','OH'),('Hamel','IL'),('Hamel','MN'),('Hamer','ID'),('Hamer','SC'),('Hamersville','OH'),('Hamill','SD'),('Hamilton','AL'),('Hamilton','CO'),('Hamilton','GA'),('Hamilton','IA'),('Hamilton','IL'),('Hamilton','IN'),('Hamilton','KS'),('Hamilton','MA'),('Hamilton','MI'),('Hamilton','MO'),('Hamilton','MS'),('Hamilton','MT'),('Hamilton','NC'),('Hamilton','ND'),('Hamilton','NY'),('Hamilton','OH'),('Hamilton','PA'),('Hamilton','TX'),('Hamilton','VA'),('Hamilton','WA'),('Hamilton City','CA'),('Hamler','OH'),('Hamlet','IN'),('Hamlet','NC'),('Hamlin','IA'),('Hamlin','NY'),('Hamlin','PA'),('Hamlin','TX'),('Hamlin','WV'),('Hammett','ID'),('Hammon','OK'),('Hammond','IL'),('Hammond','IN'),('Hammond','LA'),('Hammond','MT'),('Hammond','NY'),('Hammond','OR'),('Hammond','WI'),('Hammondsport','NY'),('Hammondsville','OH'),('Hammonton','NJ'),('Hampden','MA'),('Hampden','ME'),('Hampden','ND'),('Hampden Sydney','VA'),('Hampshire','IL'),('Hampshire','TN'),('Hampstead','MD'),('Hampstead','NC'),('Hampstead','NH'),('Hampton','AR'),('Hampton','CT'),('Hampton','FL'),('Hampton','GA'),('Hampton','IA'),('Hampton','IL'),('Hampton','KY'),('Hampton','MN'),('Hampton','NE'),('Hampton','NH'),('Hampton','NJ'),('Hampton','NY'),('Hampton','SC'),('Hampton','TN'),('Hampton','VA'),('Hampton Bays','NY'),('Hampton Falls','NH'),('Hamptonville','NC'),('Hamshire','TX'),('Hamtramck','MI'),('Hana','HI'),('Hanalei','HI'),('Hanamaulu','HI'),('Hanapepe','HI'),('Hanceville','AL'),('Hancock','IA'),('Hancock','MD'),('Hancock','ME'),('Hancock','MI'),('Hancock','MN'),('Hancock','NH'),('Hancock','NY'),('Hancock','VT'),('Hancock','WI'),('Hancocks Bridge','NJ'),('Handley','WV'),('Hanford','CA'),('Hankamer','TX'),('Hankins','NY'),('Hankinson','ND'),('Hanksville','UT'),('Hanley Falls','MN'),('Hanlontown','IA'),('Hanna','IN'),('Hanna','OK'),('Hanna','UT'),('Hanna','WY'),('Hanna City','IL'),('Hannacroix','NY'),('Hannaford','ND'),('Hannah','ND'),('Hannastown','PA'),('Hannawa Falls','NY'),('Hannibal','MO'),('Hannibal','NY'),('Hannibal','OH'),('Hannibal','WI'),('Hanover','CT'),('Hanover','IL'),('Hanover','IN'),('Hanover','KS'),('Hanover','MA'),('Hanover','MD'),('Hanover','ME'),('Hanover','MI'),('Hanover','MN'),('Hanover','NH'),('Hanover','NM'),('Hanover','PA'),('Hanover','VA'),('Hanover','WI'),('Hanover','WV'),('Hanover Park','IL'),('Hanoverton','OH'),('Hansboro','ND'),('Hanscom Afb','MA'),('Hansen','ID'),('Hansford','WV'),('Hanska','MN'),('Hanson','KY'),('Hanson','MA'),('Hanston','KS'),('Hansville','WA'),('Happy','KY'),('Happy','TX'),('Happy Camp','CA'),('Happy Jack','AZ'),('Happy Valley','OR'),('Haralson','GA'),('Harbert','MI'),('Harbeson','DE'),('Harbinger','NC'),('Harbor Beach','MI'),('Harbor City','CA'),('Harbor Springs','MI'),('Harbor View','OH'),('Harborcreek','PA'),('Harborside','ME'),('Harborton','VA'),('Harcourt','IA'),('Hardaway','AL'),('Hardburly','KY'),('Hardeeville','SC'),('Hardenville','MO'),('Hardesty','OK'),('Hardin','IL'),('Hardin','KY'),('Hardin','MO'),('Hardin','MT'),('Hardin','TX'),('Hardinsburg','IN'),('Hardinsburg','KY'),('Hardtner','KS'),('Hardwick','GA'),('Hardwick','MA'),('Hardwick','MN'),('Hardwick','VT'),('Hardy','AR'),('Hardy','IA'),('Hardy','KY'),('Hardy','NE'),('Hardy','VA'),('Hardyville','KY'),('Hardyville','VA'),('Harford','NY'),('Harford','PA'),('Hargill','TX'),('Harker Heights','TX'),('Harkers Island','NC'),('Harlan','IA'),('Harlan','IN'),('Harlan','KY'),('Harleigh','PA'),('Harlem','GA'),('Harlem','MT'),('Harlem Springs','OH'),('Harleton','TX'),('Harleysville','PA'),('Harleyville','SC'),('Harlingen','TX'),('Harlowton','MT'),('Harman','WV'),('Harmans','MD'),('Harmon','IL'),('Harmonsburg','PA'),('Harmony','CA'),('Harmony','IN'),('Harmony','ME'),('Harmony','MN'),('Harmony','NC'),('Harmony','PA'),('Harmony','RI'),('Harned','KY'),('Harold','KY'),('Harper','IA'),('Harper','KS'),('Harper','OR'),('Harper','TX'),('Harper','WV'),('Harper Woods','MI'),('Harpers Ferry','IA'),('Harpers Ferry','WV'),('Harpersfield','NY'),('Harpersville','AL'),('Harperville','MS'),('Harpster','OH'),('Harpswell','ME'),('Harpursville','NY'),('Harrah','OK'),('Harrah','WA'),('Harrell','AR'),('Harrells','NC'),('Harrellsville','NC'),('Harriet','AR'),('Harrietta','MI'),('Harriman','NY'),('Harriman','TN'),('Harrington','DE'),('Harrington','ME'),('Harrington','WA'),('Harrington Park','NJ'),('Harris','IA'),('Harris','MI'),('Harris','MN'),('Harris','MO'),('Harris','NC'),('Harris','NY'),('Harrisburg','AR'),('Harrisburg','IL'),('Harrisburg','MO'),('Harrisburg','NC'),('Harrisburg','NE'),('Harrisburg','OH'),('Harrisburg','OR'),('Harrisburg','PA'),('Harrisburg','SD'),('Harrison','AR'),('Harrison','GA'),('Harrison','ID'),('Harrison','ME'),('Harrison','MI'),('Harrison','MT'),('Harrison','NE'),('Harrison','NJ'),('Harrison','NY'),('Harrison','OH'),('Harrison','SD'),('Harrison','TN'),('Harrison City','PA'),('Harrison Township','MI'),('Harrison Valley','PA'),('Harrisonburg','LA'),('Harrisonburg','VA'),('Harrisonville','MO'),('Harrisonville','NJ'); + +INSERT INTO [cities] VALUES ('Harrisonville','PA'),('Harriston','MS'),('Harristown','IL'),('Harrisville','MI'),('Harrisville','MS'),('Harrisville','NH'),('Harrisville','NY'),('Harrisville','OH'),('Harrisville','PA'),('Harrisville','RI'),('Harrisville','WV'),('Harrod','OH'),('Harrods Creek','KY'),('Harrodsburg','IN'),('Harrodsburg','KY'),('Harrogate','TN'),('Harrold','SD'),('Harrold','TX'),('Harsens Island','MI'),('Harshaw','WI'),('Hart','MI'),('Hart','TX'),('Hartfield','VA'),('Hartford','AL'),('Hartford','AR'),('Hartford','CT'),('Hartford','IA'),('Hartford','IL'),('Hartford','KS'),('Hartford','KY'),('Hartford','MI'),('Hartford','NY'),('Hartford','OH'),('Hartford','SD'),('Hartford','TN'),('Hartford','VT'),('Hartford','WI'),('Hartford','WV'),('Hartford City','IN'),('Hartington','NE'),('Hartland','ME'),('Hartland','MI'),('Hartland','MN'),('Hartland','VT'),('Hartland','WI'),('Hartland Four Corners','VT'),('Hartleton','PA'),('Hartley','IA'),('Hartley','TX'),('Hartline','WA'),('Hartly','DE'),('Hartman','AR'),('Hartman','CO'),('Harts','WV'),('Hartsburg','IL'),('Hartsburg','MO'),('Hartsdale','NY'),('Hartsel','CO'),('Hartselle','AL'),('Hartsfield','GA'),('Hartshorn','MO'),('Hartshorne','OK'),('Hartstown','PA'),('Hartsville','IN'),('Hartsville','SC'),('Hartsville','TN'),('Hartville','MO'),('Hartville','OH'),('Hartville','WY'),('Hartwell','GA'),('Hartwick','IA'),('Hartwick','NY'),('Hartwood','VA'),('Harvard','ID'),('Harvard','IL'),('Harvard','MA'),('Harvard','NE'),('Harvel','IL'),('Harvest','AL'),('Harvey','AR'),('Harvey','IA'),('Harvey','IL'),('Harvey','LA'),('Harvey','ND'),('Harveys Lake','PA'),('Harveysburg','OH'),('Harveyville','KS'),('Harviell','MO'),('Harwich','MA'),('Harwich Port','MA'),('Harwick','PA'),('Harwinton','CT'),('Harwood','MD'),('Harwood','MO'),('Harwood','ND'),('Harwood','TX'),('Harwood Heights','IL'),('Hasbrouck Heights','NJ'),('Haskell','NJ'),('Haskell','OK'),('Haskell','TX'),('Haskins','OH'),('Haslet','TX'),('Haslett','MI'),('Hassell','NC'),('Hastings','FL'),('Hastings','IA'),('Hastings','MI'),('Hastings','MN'),('Hastings','NE'),('Hastings','NY'),('Hastings','OK'),('Hastings','PA'),('Hastings On Hudson','NY'),('Hasty','AR'),('Hasty','CO'),('Haswell','CO'),('Hat Creek','CA'),('Hatboro','PA'),('Hatch','NM'),('Hatch','UT'),('Hatchechubbee','AL'),('Hatfield','AR'),('Hatfield','IN'),('Hatfield','MA'),('Hatfield','MO'),('Hatfield','PA'),('Hathaway','MT'),('Hathaway Pines','CA'),('Hathorne','MA'),('Hatillo','PR'),('Hatley','WI'),('Hatteras','NC'),('Hattiesburg','MS'),('Hattieville','AR'),('Hatton','ND'),('Haubstadt','IN'),('Haugan','MT'),('Haugen','WI'),('Haughton','LA'),('Hauppauge','NY'),('Hauula','HI'),('Havana','AR'),('Havana','FL'),('Havana','IL'),('Havana','KS'),('Havana','ND'),('Havelock','IA'),('Havelock','NC'),('Haven','KS'),('Havensville','KS'),('Haverford','PA'),('Haverhill','IA'),('Haverhill','MA'),('Haverhill','NH'),('Haverhill','OH'),('Haverstraw','NY'),('Havertown','PA'),('Haviland','KS'),('Haviland','OH'),('Havre','MT'),('Havre De Grace','MD'),('Haw River','NC'),('Hawaii National Park','HI'),('Hawaiian Gardens','CA'),('Hawarden','IA'),('Hawesville','KY'),('Hawi','HI'),('Hawk Point','MO'),('Hawk Run','PA'),('Hawk Springs','WY'),('Hawkeye','IA'),('Hawkins','TX'),('Hawkins','WI'),('Hawkinsville','GA'),('Hawks','MI'),('Hawley','MN'),('Hawley','PA'),('Hawley','TX'),('Hawleyville','CT'),('Haworth','NJ'),('Haworth','OK'),('Hawthorn','PA'),('Hawthorne','CA'),('Hawthorne','FL'),('Hawthorne','NJ'),('Hawthorne','NV'),('Hawthorne','NY'),('Hawthorne','WI'),('Haxtun','CO'),('Hay','WA'),('Hay Springs','NE'),('Hayden','AL'),('Hayden','AZ'),('Hayden','CO'),('Hayden','ID'),('Hayden','IN'),('Haydenville','MA'),('Haydenville','OH'),('Hayes','LA'),('Hayes','SD'),('Hayes','VA'),('Hayes Center','NE'),('Hayesville','IA'),('Hayesville','NC'),('Hayesville','OH'),('Hayfield','MN'),('Hayfork','CA'),('Haymarket','VA'),('Haynes','AR'),('Haynesville','LA'),('Haynesville','VA'),('Hayneville','AL'),('Hays','KS'),('Hays','MT'),('Hays','NC'),('Haysi','VA'),('Haysville','KS'),('Hayti','MO'),('Hayti','SD'),('Hayward','CA'),('Hayward','MN'),('Hayward','WI'),('Haywood','VA'),('Haywood','WV'),('Hazard','KY'),('Hazard','NE'),('Hazel','KY'),('Hazel','SD'),('Hazel Crest','IL'),('Hazel Green','AL'),('Hazel Green','KY'),('Hazel Green','WI'),('Hazel Hurst','PA'),('Hazel Park','MI'),('Hazelhurst','WI'),('Hazelton','ID'),('Hazelton','KS'),('Hazelton','ND'),('Hazelwood','MO'),('Hazelwood','NC'),('Hazen','AR'),('Hazen','ND'),('Hazlehurst','GA'),('Hazlehurst','MS'),('Hazlet','NJ'),('Hazleton','IA'),('Hazleton','IN'),('Hazleton','PA'),('Head Waters','VA'),('Headland','AL'),('Headrick','OK'),('Heafford Junction','WI'),('Healdsburg','CA'),('Healdton','OK'),('Healy','AK'),('Healy','KS'),('Hearne','TX'),('Heart Butte','MT'),('Heartwell','NE'),('Heaters','WV'),('Heath','MA'),('Heath','OH'),('Heath Springs','SC'),('Heathsville','VA'),('Heavener','OK'),('Hebbronville','TX'),('Heber','AZ'),('Heber','CA'),('Heber City','UT'),('Heber Springs','AR'),('Hebo','OR'),('Hebron','CT'),('Hebron','IL'),('Hebron','IN'),('Hebron','KY'),('Hebron','MD'),('Hebron','ME'),('Hebron','ND'),('Hebron','NE'),('Hebron','NH'),('Hebron','OH'),('Hecker','IL'),('Hecla','SD'),('Hector','AR'),('Hector','MN'),('Hector','NY'),('Hedgesville','WV'),('Hedley','TX'),('Hedrick','IA'),('Heflin','AL'),('Heflin','LA'),('Hegins','PA'),('Heidelberg','KY'),('Heidelberg','MS'),('Heidenheimer','TX'),('Heidrick','KY'),('Heilwood','PA'),('Heiskell','TN'),('Heislerville','NJ'),('Heisson','WA'),('Helen','GA'),('Helen','MD'),('Helen','WV'),('Helena','AL'),('Helena','AR'),('Helena','GA'),('Helena','MO'),('Helena','MT'),('Helena','NY'),('Helena','OH'),('Helena','OK'),('Helendale','CA'),('Helenville','WI'),('Helenwood','TN'),('Helix','OR'),('Hellertown','PA'),('Hellier','KY'),('Helm','CA'),('Helmetta','NJ'),('Helmsburg','IN'),('Helmville','MT'),('Helotes','TX'),('Helper','UT'),('Helton','KY'),('Heltonville','IN'),('Helvetia','WV'),('Hematite','MO'),('Hemet','CA'),('Hemingford','NE'),('Hemingway','SC'),('Hemlock','IN'),('Hemlock','MI'),('Hemlock','NY'),('Hemphill','TX'),('Hemphill','WV'),('Hempstead','NY'),('Hempstead','TX'),('Henagar','AL'),('Henderson','AR'),('Henderson','CO'),('Henderson','IA'),('Henderson','IL'),('Henderson','KY'),('Henderson','MD'),('Henderson','MI'),('Henderson','MN'),('Henderson','NC'),('Henderson','NE'),('Henderson','NV'),('Henderson','NY'),('Henderson','TN'),('Henderson','TX'),('Henderson','WV'),('Henderson Harbor','NY'),('Hendersonville','NC'),('Hendersonville','PA'),('Hendersonville','TN'),('Hendley','NE'),('Hendricks','MN'),('Hendricks','WV'),('Hendrix','OK'),('Hendrum','MN'),('Henefer','UT'),('Henlawson','WV'),('Henley','MO'),('Hennepin','IL'),('Hennepin','OK'),('Hennessey','OK'),('Henniker','NH'),('Henning','IL'),('Henning','MN'),('Henning','TN'),('Henrico','NC'),('Henrietta','MO'),('Henrietta','NC'),('Henrietta','NY'),('Henrietta','TX'),('Henriette','MN'),('Henrieville','UT'),('Henry','IL'),('Henry','SD'),('Henry','TN'),('Henry','VA'),('Henryetta','OK'),('Henryville','IN'),('Henryville','PA'),('Hensel','ND'),('Hensley','AR'),('Hensley','WV'),('Hensonville','NY'),('Hephzibah','GA'),('Hepler','KS'),('Heppner','OR'),('Hepzibah','WV'),('Herald','CA'),('Herbster','WI'),('Herculaneum','MO'),('Hercules','CA'),('Hereford','AZ'),('Hereford','CO'),('Hereford','OR'),('Hereford','PA'),('Hereford','TX'),('Herington','KS'),('Herkimer','NY'),('Herlong','CA'),('Herman','MN'),('Herman','NE'),('Herman','PA'),('Hermann','MO'),('Hermansville','MI'),('Hermanville','MS'),('Herminie','PA'),('Hermiston','OR'),('Hermitage','AR'),('Hermitage','MO'),('Hermitage','PA'),('Hermitage','TN'),('Hermleigh','TX'),('Hermon','NY'),('Hermosa','SD'),('Hermosa Beach','CA'),('Hernandez','NM'),('Hernando','FL'),('Hernando','MS'),('Herndon','KS'),('Herndon','KY'),('Herndon','PA'),('Herndon','VA'),('Herndon','WV'),('Hernshaw','WV'),('Herod','IL'),('Heron','MT'),('Heron Lake','MN'),('Herreid','SD'),('Herrick','IL'),('Herrick','SD'),('Herrick Center','PA'),('Herriman','UT'),('Herrin','IL'),('Herron','MI'),('Herscher','IL'),('Hersey','MI'),('Hershey','NE'),('Hershey','PA'),('Hertel','WI'),('Hertford','NC'),('Hesperia','CA'),('Hesperia','MI'),('Hesperus','CO'),('Hessel','MI'),('Hessmer','LA'),('Hesston','KS'),('Hesston','PA'),('Hestand','KY'),('Hester','LA'),('Heth','AR'),('Hettick','IL'),('Hettinger','ND'),('Heuvelton','NY'),('Hewett','WV'),('Hewitt','MN'),('Hewitt','NJ'),('Hewitt','TX'),('Hewitt','WI'),('Hewlett','NY'),('Hext','TX'),('Heyburn','ID'),('Heyworth','IL'),('Hi Hat','KY'),('Hialeah','FL'),('Hiawassee','GA'),('Hiawatha','IA'),('Hiawatha','KS'),('Hiawatha','WV'),('Hibbing','MN'),('Hibbs','PA'),('Hibernia','NJ'),('Hickam Afb','HI'),('Hickman','CA'),('Hickman','KY'),('Hickman','NE'),('Hickman','TN'),('Hickory','KY'),('Hickory','MS'),('Hickory','NC'),('Hickory','PA'),('Hickory Corners','MI'),('Hickory Flat','MS'),('Hickory Grove','SC'),('Hickory Hills','IL'),('Hickory Plains','AR'),('Hickory Ridge','AR'),('Hickory Valley','TN'),('Hicksville','NY'),('Hicksville','OH'),('Hico','TX'),('Hico','WV'),('Hidalgo','IL'); + +INSERT INTO [cities] VALUES ('Hidalgo','TX'),('Hidden Valley','PA'),('Hidden Valley Lake','CA'),('Hiddenite','NC'),('Hideaway Hls','OH'),('Higbee','MO'),('Higden','AR'),('Higdon','AL'),('Higganum','CT'),('Higgins','TX'),('Higgins Lake','MI'),('Higginson','AR'),('Higginsport','OH'),('Higginsville','MO'),('High Bridge','NJ'),('High Bridge','WI'),('High Falls','NY'),('High Hill','MO'),('High Island','TX'),('High Point','MO'),('High Point','NC'),('High Ridge','MO'),('High Rolls Mountain Park','NM'),('High Shoals','GA'),('High Shoals','NC'),('High Springs','FL'),('High View','WV'),('Highfalls','NC'),('Highgate Center','VT'),('Highgate Springs','VT'),('Highland','CA'),('Highland','IL'),('Highland','IN'),('Highland','KS'),('Highland','MD'),('Highland','MI'),('Highland','NY'),('Highland','OH'),('Highland','WI'),('Highland City','FL'),('Highland Falls','NY'),('Highland Home','AL'),('Highland Lake','NY'),('Highland Lakes','NJ'),('Highland Mills','NY'),('Highland Park','IL'),('Highland Park','MI'),('Highland Park','NJ'),('Highland Springs','VA'),('Highlands','NC'),('Highlands','NJ'),('Highlands','TX'),('Highlandville','IA'),('Highlandville','MO'),('Highmore','SD'),('Highmount','NY'),('Highspire','PA'),('Hightstown','NJ'),('Highwood','IL'),('Highwood','MT'),('Higley','AZ'),('Hiko','NV'),('Hiland','WY'),('Hilbert','WI'),('Hilda','SC'),('Hildale','UT'),('Hildebran','NC'),('Hildreth','NE'),('Hilger','MT'),('Hilham','TN'),('Hill','NH'),('Hill Afb','UT'),('Hill City','ID'),('Hill City','KS'),('Hill City','MN'),('Hill City','SD'),('Hillburn','NY'),('Hiller','PA'),('Hilliard','FL'),('Hilliard','OH'),('Hilliards','PA'),('Hillister','TX'),('Hillman','MI'),('Hillman','MN'),('Hillpoint','WI'),('Hillrose','CO'),('Hills','IA'),('Hills','MN'),('Hillsboro','AL'),('Hillsboro','GA'),('Hillsboro','IA'),('Hillsboro','IL'),('Hillsboro','IN'),('Hillsboro','KS'),('Hillsboro','KY'),('Hillsboro','MD'),('Hillsboro','MO'),('Hillsboro','MS'),('Hillsboro','ND'),('Hillsboro','NM'),('Hillsboro','OH'),('Hillsboro','OR'),('Hillsboro','TN'),('Hillsboro','TX'),('Hillsboro','WI'),('Hillsboro','WV'),('Hillsborough','NC'),('Hillsborough','NH'),('Hillsborough','NJ'),('Hillsdale','IL'),('Hillsdale','IN'),('Hillsdale','KS'),('Hillsdale','MI'),('Hillsdale','NJ'),('Hillsdale','NY'),('Hillsdale','OK'),('Hillsdale','PA'),('Hillsdale','WY'),('Hillsgrove','PA'),('Hillside','CO'),('Hillside','IL'),('Hillside','NJ'),('Hillsville','PA'),('Hillsville','VA'),('Hilltop','WV'),('Hilltown','PA'),('Hillview','IL'),('Hillview','KY'),('Hilmar','CA'),('Hilo','HI'),('Hilton','NY'),('Hilton Head Island','SC'),('Hiltons','VA'),('Hima','KY'),('Himrod','NY'),('Hinckley','IL'),('Hinckley','ME'),('Hinckley','MN'),('Hinckley','NY'),('Hinckley','OH'),('Hinckley','UT'),('Hindman','KY'),('Hindsboro','IL'),('Hindsville','AR'),('Hines','IL'),('Hines','MN'),('Hines','OR'),('Hinesburg','VT'),('Hineston','LA'),('Hinesville','GA'),('Hingham','MA'),('Hingham','MT'),('Hingham','WI'),('Hinkle','KY'),('Hinkley','CA'),('Hinsdale','IL'),('Hinsdale','MA'),('Hinsdale','MT'),('Hinsdale','NH'),('Hinsdale','NY'),('Hinton','IA'),('Hinton','OK'),('Hinton','VA'),('Hinton','WV'),('Hiram','GA'),('Hiram','ME'),('Hiram','OH'),('Hiseville','KY'),('Hitchcock','OK'),('Hitchcock','SD'),('Hitchcock','TX'),('Hitchins','KY'),('Hitchita','OK'),('Hitterdal','MN'),('Hiwasse','AR'),('Hiwassee','VA'),('Hixson','TN'),('Hixton','WI'),('Ho Ho Kus','NJ'),('Hoagland','IN'),('Hobart','IN'),('Hobart','NY'),('Hobart','OK'),('Hobart','WA'),('Hobbs','IN'),('Hobbs','NM'),('Hobbsville','NC'),('Hobe Sound','FL'),('Hobgood','NC'),('Hoboken','GA'),('Hoboken','NJ'),('Hobson','MT'),('Hobson','TX'),('Hobucken','NC'),('Hochheim','TX'),('Hockessin','DE'),('Hockingport','OH'),('Hockley','TX'),('Hodge','LA'),('Hodgen','OK'),('Hodgenville','KY'),('Hodges','AL'),('Hodges','SC'),('Hoehne','CO'),('Hoffman','IL'),('Hoffman','MN'),('Hoffman','NC'),('Hoffman Estates','IL'),('Hoffmeister','NY'),('Hogansburg','NY'),('Hogansville','GA'),('Hogeland','MT'),('Hohenwald','TN'),('Hoisington','KS'),('Hokah','MN'),('Holabird','SD'),('Holbrook','AZ'),('Holbrook','ID'),('Holbrook','MA'),('Holbrook','NE'),('Holbrook','NY'),('Holbrook','PA'),('Holcomb','IL'),('Holcomb','KS'),('Holcomb','MO'),('Holcomb','MS'),('Holcombe','WI'),('Holden','LA'),('Holden','MA'),('Holden','ME'),('Holden','MO'),('Holden','UT'),('Holden','WV'),('Holdenville','OK'),('Holder','FL'),('Holderness','NH'),('Holdingford','MN'),('Holdrege','NE'),('Holgate','OH'),('Holicong','PA'),('Holiday','FL'),('Holladay','TN'),('Holland','IA'),('Holland','IN'),('Holland','KY'),('Holland','MA'),('Holland','MI'),('Holland','MN'),('Holland','MO'),('Holland','NY'),('Holland','OH'),('Holland','TX'),('Holland Patent','NY'),('Hollandale','MN'),('Hollandale','MS'),('Hollandale','WI'),('Hollansburg','OH'),('Hollenberg','KS'),('Holley','NY'),('Holliday','MO'),('Holliday','TX'),('Hollidaysburg','PA'),('Hollins','AL'),('Hollis','NH'),('Hollis','NY'),('Hollis','OK'),('Hollis Center','ME'),('Hollister','CA'),('Hollister','FL'),('Hollister','MO'),('Hollister','NC'),('Hollister','OK'),('Holliston','MA'),('Holloman Air Force Base','NM'),('Hollow Rock','TN'),('Holloway','MN'),('Holloway','OH'),('Hollowville','NY'),('Hollsopple','PA'),('Holly','CO'),('Holly','MI'),('Holly Bluff','MS'),('Holly Grove','AR'),('Holly Hill','SC'),('Holly Pond','AL'),('Holly Ridge','MS'),('Holly Ridge','NC'),('Holly Springs','GA'),('Holly Springs','MS'),('Holly Springs','NC'),('Hollytree','AL'),('Hollywood','AL'),('Hollywood','FL'),('Hollywood','MD'),('Hollywood','SC'),('Holman','NM'),('Holmdel','NJ'),('Holmen','WI'),('Holmes','NY'),('Holmes','PA'),('Holmes Beach','FL'),('Holmes City','MN'),('Holmes Mill','KY'),('Holmesville','OH'),('Holstein','IA'),('Holstein','NE'),('Holt','CA'),('Holt','FL'),('Holt','MI'),('Holt','MO'),('Holton','IN'),('Holton','KS'),('Holton','MI'),('Holts Summit','MO'),('Holtsville','NY'),('Holtville','CA'),('Holtwood','PA'),('Holualoa','HI'),('Holy City','CA'),('Holy Cross','AK'),('Holy Cross','IA'),('Holy Trinity','AL'),('Holyoke','CO'),('Holyoke','MA'),('Holyoke','MN'),('Holyrood','KS'),('Home','KS'),('Home','PA'),('Homedale','ID'),('Homelake','CO'),('Homeland','CA'),('Homeland','FL'),('Homer','AK'),('Homer','GA'),('Homer','IL'),('Homer','IN'),('Homer','LA'),('Homer','MI'),('Homer','MN'),('Homer','NE'),('Homer','NY'),('Homer','OH'),('Homer City','PA'),('Homer Glen','IL'),('Homerville','GA'),('Homerville','OH'),('Homestead','FL'),('Homestead','IA'),('Homestead','MT'),('Homestead','PA'),('Hometown','IL'),('Hometown','WV'),('Homewood','CA'),('Homewood','IL'),('Homeworth','OH'),('Hominy','OK'),('Homosassa','FL'),('Homosassa Springs','FL'),('Honaker','VA'),('Honaunau','HI'),('Hondo','NM'),('Hondo','TX'),('Honea Path','SC'),('Honeoye','NY'),('Honeoye Falls','NY'),('Honesdale','PA'),('Honey Brook','PA'),('Honey Creek','IA'),('Honey Creek','WI'),('Honey Grove','PA'),('Honey Grove','TX'),('Honeydew','CA'),('Honeyville','UT'),('Honokaa','HI'),('Honolulu','HI'),('Honomu','HI'),('Honor','MI'),('Honoraville','AL'),('Hood','CA'),('Hood','VA'),('Hood River','OR'),('Hoodsport','WA'),('Hooker','OK'),('Hookerton','NC'),('Hooks','TX'),('Hooksett','NH'),('Hookstown','PA'),('Hoolehua','HI'),('Hoonah','AK'),('Hoopa','CA'),('Hooper','CO'),('Hooper','NE'),('Hooper','UT'),('Hooper','WA'),('Hooper Bay','AK'),('Hoopeston','IL'),('Hoople','ND'),('Hooppole','IL'),('Hoosick','NY'),('Hoosick Falls','NY'),('Hooven','OH'),('Hooversville','PA'),('Hop Bottom','PA'),('Hopatcong','NJ'),('Hope','AK'),('Hope','AR'),('Hope','ID'),('Hope','IN'),('Hope','KS'),('Hope','KY'),('Hope','ME'),('Hope','MI'),('Hope','MN'),('Hope','ND'),('Hope','NJ'),('Hope','NM'),('Hope','RI'),('Hope Hull','AL'),('Hope Mills','NC'),('Hope Valley','RI'),('Hopedale','IL'),('Hopedale','MA'),('Hopedale','OH'),('Hopeland','PA'),('Hopeton','OK'),('Hopewell','NJ'),('Hopewell','OH'),('Hopewell','PA'),('Hopewell','VA'),('Hopewell Junction','NY'),('Hopkins','MI'),('Hopkins','MN'),('Hopkins','MO'),('Hopkins','SC'),('Hopkins Park','IL'),('Hopkinsville','KY'),('Hopkinton','IA'),('Hopkinton','MA'),('Hopkinton','RI'),('Hopland','CA'),('Hopwood','PA'),('Hoquiam','WA'),('Horace','ND'),('Horatio','AR'),('Horatio','SC'),('Hordville','NE'),('Horicon','WI'),('Hormigueros','PR'),('Horn Lake','MS'),('Hornbeak','TN'),('Hornbeck','LA'),('Hornbrook','CA'),('Hornell','NY'),('Horner','WV'),('Hornersville','MO'),('Hornick','IA'),('Hornitos','CA'),('Hornsby','TN'),('Horntown','VA'),('Horse Branch','KY'),('Horse Cave','KY'),('Horse Creek','WY'),('Horse Shoe','NC'),('Horseheads','NY'),('Horsepen','VA'),('Horseshoe Bay','TX'),('Horseshoe Beach','FL'),('Horseshoe Bend','AR'),('Horseshoe Bend','ID'),('Horsham','PA'),('Hortense','GA'),('Horton','AL'),('Horton','KS'),('Horton','MI'),('Hortonville','NY'),('Hortonville','WI'),('Hoschton','GA'),('Hosford','FL'),('Hoskins','NE'),('Hoskinston','KY'),('Hosmer','SD'),('Hospers','IA'),('Hosston','LA'),('Hostetter','PA'),('Hot Springs','MT'),('Hot Springs','NC'),('Hot Springs','SD'),('Hot Springs','VA'),('Hot Springs National Park','AR'),('Hot Springs Village','AR'),('Hot Sulphur Springs','CO'),('Hotchkiss','CO'),('Hotevilla','AZ'),('Houck','AZ'),('Houghton','IA'),('Houghton','MI'),('Houghton','NY'),('Houghton','SD'),('Houghton Lake','MI'),('Houghton Lake Heights','MI'); + +INSERT INTO [cities] VALUES ('Houlka','MS'),('Houlton','ME'),('Houlton','WI'),('Houma','LA'),('Housatonic','MA'),('House','NM'),('House Springs','MO'),('Houston','AK'),('Houston','AL'),('Houston','AR'),('Houston','DE'),('Houston','MN'),('Houston','MO'),('Houston','MS'),('Houston','OH'),('Houston','PA'),('Houston','TX'),('Houstonia','MO'),('Houtzdale','PA'),('Hoven','SD'),('Hovland','MN'),('Howard','CO'),('Howard','GA'),('Howard','KS'),('Howard','OH'),('Howard','PA'),('Howard','SD'),('Howard Beach','NY'),('Howard City','MI'),('Howard Lake','MN'),('Howardsville','VA'),('Howe','ID'),('Howe','IN'),('Howe','OK'),('Howe','TX'),('Howell','MI'),('Howell','NJ'),('Howell','UT'),('Howells','NE'),('Howells','NY'),('Howes','SD'),('Howes Cave','NY'),('Howey In The Hills','FL'),('Howland','ME'),('Hoxie','AR'),('Hoxie','KS'),('Hoyleton','IL'),('Hoyt','KS'),('Hoyt','OK'),('Hoyt Lakes','MN'),('Hoytville','OH'),('Huachuca City','AZ'),('Hualapai','AZ'),('Hubbard','IA'),('Hubbard','NE'),('Hubbard','OH'),('Hubbard','OR'),('Hubbard','TX'),('Hubbard Lake','MI'),('Hubbardston','MA'),('Hubbardston','MI'),('Hubbardsville','NY'),('Hubbell','MI'),('Hubbell','NE'),('Hubert','NC'),('Hubertus','WI'),('Huddleston','VA'),('Huddy','KY'),('Hudgins','VA'),('Hudson','CO'),('Hudson','FL'),('Hudson','IA'),('Hudson','IL'),('Hudson','IN'),('Hudson','KS'),('Hudson','KY'),('Hudson','MA'),('Hudson','ME'),('Hudson','MI'),('Hudson','NC'),('Hudson','NH'),('Hudson','NY'),('Hudson','OH'),('Hudson','SD'),('Hudson','WI'),('Hudson','WY'),('Hudson Falls','NY'),('Hudsonville','MI'),('Huey','IL'),('Hueysville','KY'),('Huffman','TX'),('Hufsmith','TX'),('Huger','SC'),('Huggins','MO'),('Hughes','AK'),('Hughes','AR'),('Hughes Springs','TX'),('Hugheston','WV'),('Hughesville','MD'),('Hughesville','MO'),('Hughesville','PA'),('Hughson','CA'),('Hughsonville','NY'),('Hugo','CO'),('Hugo','MN'),('Hugo','OK'),('Hugoton','KS'),('Huguenot','NY'),('Hulbert','MI'),('Hulbert','OK'),('Hulen','KY'),('Hulett','WY'),('Huletts Landing','NY'),('Hull','GA'),('Hull','IA'),('Hull','IL'),('Hull','MA'),('Hull','TX'),('Hulls Cove','ME'),('Humacao','PR'),('Humansville','MO'),('Humarock','MA'),('Humbird','WI'),('Humble','TX'),('Humboldt','AZ'),('Humboldt','IA'),('Humboldt','IL'),('Humboldt','KS'),('Humboldt','MN'),('Humboldt','NE'),('Humboldt','SD'),('Humboldt','TN'),('Hume','CA'),('Hume','IL'),('Hume','MO'),('Hume','NY'),('Hume','VA'),('Humeston','IA'),('Hummels Wharf','PA'),('Hummelstown','PA'),('Humnoke','AR'),('Humphrey','AR'),('Humphrey','NE'),('Humphreys','MO'),('Humptulips','WA'),('Hundred','WV'),('Hungerford','TX'),('Hungry Horse','MT'),('Hunker','PA'),('Hunlock Creek','PA'),('Hunnewell','MO'),('Hunt','NY'),('Hunt','TX'),('Hunt Valley','MD'),('Hunter','AR'),('Hunter','KS'),('Hunter','ND'),('Hunter','NY'),('Hunter','OK'),('Hunters','WA'),('Huntersville','NC'),('Huntertown','IN'),('Huntingburg','IN'),('Huntingdon','PA'),('Huntingdon','TN'),('Huntingdon Valley','PA'),('Huntington','AR'),('Huntington','IN'),('Huntington','MA'),('Huntington','NY'),('Huntington','OR'),('Huntington','TX'),('Huntington','UT'),('Huntington','VT'),('Huntington','WV'),('Huntington Beach','CA'),('Huntington Mills','PA'),('Huntington Park','CA'),('Huntington Station','NY'),('Huntington Woods','MI'),('Huntingtown','MD'),('Huntland','TN'),('Huntley','IL'),('Huntley','MN'),('Huntley','MT'),('Huntley','WY'),('Huntly','VA'),('Huntsburg','OH'),('Huntsville','AL'),('Huntsville','AR'),('Huntsville','IL'),('Huntsville','MO'),('Huntsville','OH'),('Huntsville','TN'),('Huntsville','TX'),('Huntsville','UT'),('Hurdland','MO'),('Hurdle Mills','NC'),('Hurdsfield','ND'),('Hurlburt Field','FL'),('Hurley','MO'),('Hurley','MS'),('Hurley','NM'),('Hurley','NY'),('Hurley','SD'),('Hurley','VA'),('Hurley','WI'),('Hurleyville','NY'),('Hurlock','MD'),('Huron','CA'),('Huron','IN'),('Huron','OH'),('Huron','SD'),('Huron','TN'),('Hurricane','UT'),('Hurricane','WV'),('Hurricane Mills','TN'),('Hurst','IL'),('Hurst','TX'),('Hurt','VA'),('Hurtsboro','AL'),('Huslia','AK'),('Huson','MT'),('Husser','LA'),('Hustisford','WI'),('Hustle','VA'),('Hustler','WI'),('Huston','ID'),('Hustontown','PA'),('Hustonville','KY'),('Husum','WA'),('Hutchins','TX'),('Hutchinson','KS'),('Hutchinson','MN'),('Hutchinson','PA'),('Hutsonville','IL'),('Huttig','AR'),('Hutto','TX'),('Huttonsville','WV'),('Huxford','AL'),('Huxley','IA'),('Hyampom','CA'),('Hyannis','MA'),('Hyannis','NE'),('Hyannis Port','MA'),('Hyattsville','MD'),('Hyattville','WY'),('Hydaburg','AK'),('Hyde','PA'),('Hyde Park','MA'),('Hyde Park','NY'),('Hyde Park','PA'),('Hyde Park','UT'),('Hyde Park','VT'),('Hyden','KY'),('Hyder','AK'),('Hydes','MD'),('Hydesville','CA'),('Hydetown','PA'),('Hydeville','VT'),('Hydro','OK'),('Hye','TX'),('Hygiene','CO'),('Hymera','IN'),('Hyndman','PA'),('Hyner','PA'),('Hyrum','UT'),('Hysham','MT'),('Iaeger','WV'),('Ibapah','UT'),('Iberia','MO'),('Iberia','OH'),('Icard','NC'),('Ickesburg','PA'),('Ida','AR'),('Ida','LA'),('Ida','MI'),('Ida Grove','IA'),('Idabel','OK'),('Idaho City','ID'),('Idaho Falls','ID'),('Idaho Springs','CO'),('Idalia','CO'),('Idalou','TX'),('Idamay','WV'),('Idanha','OR'),('Idaville','IN'),('Idaville','PA'),('Ideal','GA'),('Ideal','SD'),('Ider','AL'),('Idledale','CO'),('Idlewild','MI'),('Idlewild','TN'),('Idleyld Park','OR'),('Idyllwild','CA'),('Ignacio','CO'),('Igo','CA'),('Ihlen','MN'),('Ijamsville','MD'),('Ikes Fork','WV'),('Ila','GA'),('Ilfeld','NM'),('Iliamna','AK'),('Iliff','CO'),('Ilion','NY'),('Illinois City','IL'),('Illiopolis','IL'),('Ilwaco','WA'),('Imbler','OR'),('Imboden','AR'),('Imlay','NV'),('Imlay City','MI'),('Imlaystown','NJ'),('Imler','PA'),('Immaculata','PA'),('Immokalee','FL'),('Imnaha','OR'),('Imogene','IA'),('Imperial','CA'),('Imperial','MO'),('Imperial','NE'),('Imperial','PA'),('Imperial','TX'),('Imperial Beach','CA'),('Ina','IL'),('Inavale','NE'),('Inchelium','WA'),('Incline Village','NV'),('Independence','CA'),('Independence','IA'),('Independence','KS'),('Independence','KY'),('Independence','LA'),('Independence','MO'),('Independence','MS'),('Independence','OH'),('Independence','OR'),('Independence','VA'),('Independence','WI'),('Independence','WV'),('Index','WA'),('Indiahoma','OK'),('Indialantic','FL'),('Indian','AK'),('Indian Head','MD'),('Indian Head','PA'),('Indian Hills','CO'),('Indian Lake','NY'),('Indian Lake Estates','FL'),('Indian Mound','TN'),('Indian River','MI'),('Indian Rocks Beach','FL'),('Indian Springs','NV'),('Indian Trail','NC'),('Indian Valley','ID'),('Indian Valley','VA'),('Indian Wells','AZ'),('Indian Wells','CA'),('Indiana','PA'),('Indianapolis','IN'),('Indianola','IA'),('Indianola','IL'),('Indianola','MS'),('Indianola','NE'),('Indianola','OK'),('Indianola','PA'),('Indianola','WA'),('Indiantown','FL'),('Indio','CA'),('Indore','WV'),('Industry','IL'),('Industry','PA'),('Industry','TX'),('Inez','KY'),('Inez','TX'),('Ingalls','IN'),('Ingalls','KS'),('Ingalls','MI'),('Inglefield','IN'),('Ingleside','IL'),('Ingleside','MD'),('Ingleside','TX'),('Inglewood','CA'),('Inglis','FL'),('Ingomar','MT'),('Ingomar','PA'),('Ingraham','IL'),('Ingram','KY'),('Ingram','TX'),('Inkom','ID'),('Inkster','MI'),('Inkster','ND'),('Inland','NE'),('Inlet','NY'),('Inman','KS'),('Inman','NE'),('Inman','SC'),('Innis','LA'),('Inola','OK'),('Institute','WV'),('Intercession City','FL'),('Intercourse','PA'),('Interior','SD'),('Interlachen','FL'),('Interlaken','NY'),('Interlochen','MI'),('International Falls','MN'),('Intervale','NH'),('Inver Grove Heights','MN'),('Inverness','CA'),('Inverness','FL'),('Inverness','MS'),('Inverness','MT'),('Inwood','IA'),('Inwood','NY'),('Inwood','WV'),('Inyokern','CA'),('Iola','KS'),('Iola','TX'),('Iola','WI'),('Iona','ID'),('Iona','MN'),('Ione','CA'),('Ione','OR'),('Ione','WA'),('Ionia','IA'),('Ionia','MI'),('Ionia','MO'),('Ionia','NY'),('Iota','LA'),('Iowa','LA'),('Iowa City','IA'),('Iowa Falls','IA'),('Iowa Park','TX'),('Ipava','IL'),('Ipswich','MA'),('Ipswich','SD'),('Ira','IA'),('Ira','TX'),('Iraan','TX'),('Irasburg','VT'),('Iredell','TX'),('Ireland','IN'),('Ireland','WV'),('Irene','SD'),('Irene','TX'),('Ireton','IA'),('Irma','WI'),('Irmo','SC'),('Iron','MN'),('Iron Belt','WI'),('Iron City','GA'),('Iron City','TN'),('Iron Gate','VA'),('Iron Mountain','MI'),('Iron Ridge','WI'),('Iron River','MI'),('Iron River','WI'),('Iron Springs','AZ'),('Iron Station','NC'),('Irondale','MO'),('Irondale','OH'),('Ironia','NJ'),('Irons','MI'),('Ironside','OR'),('Ironsides','MD'),('Ironton','MN'),('Ironton','MO'),('Ironton','OH'),('Ironwood','MI'),('Iroquois','IL'),('Iroquois','SD'),('Irrigon','OR'),('Irvine','CA'),('Irvine','KY'),('Irvine','PA'),('Irving','IL'),('Irving','NY'),('Irving','TX'),('Irvington','AL'),('Irvington','IL'),('Irvington','KY'),('Irvington','NJ'),('Irvington','NY'),('Irvington','VA'),('Irvona','PA'),('Irwin','IA'),('Irwin','ID'),('Irwin','OH'),('Irwin','PA'),('Irwinton','GA'),('Irwinville','GA'),('Isaban','WV'),('Isabel','KS'),('Isabel','SD'),('Isabela','PR'),('Isabella','MN'),('Isabella','MO'),('Isabella','OK'); + +INSERT INTO [cities] VALUES ('Isabella','PA'),('Isanti','MN'),('Iselin','NJ'),('Ishpeming','MI'),('Islamorada','FL'),('Island','KY'),('Island City','KY'),('Island Falls','ME'),('Island Grove','FL'),('Island Heights','NJ'),('Island Lake','IL'),('Island Park','ID'),('Island Park','NY'),('Island Pond','VT'),('Islandia','NY'),('Islandton','SC'),('Isle','MN'),('Isle Au Haut','ME'),('Isle La Motte','VT'),('Isle Of Palms','SC'),('Isle Of Springs','ME'),('Isle Of Wight','VA'),('Isle Saint George','OH'),('Islesboro','ME'),('Islesford','ME'),('Isleta','NM'),('Isleton','CA'),('Islip','NY'),('Islip Terrace','NY'),('Ismay','MT'),('Isola','MS'),('Isom','KY'),('Isonville','KY'),('Issaquah','WA'),('Issue','MD'),('Istachatta','FL'),('Italy','TX'),('Itasca','IL'),('Itasca','TX'),('Ithaca','MI'),('Ithaca','NE'),('Ithaca','NY'),('Itmann','WV'),('Itta Bena','MS'),('Iuka','IL'),('Iuka','KS'),('Iuka','MS'),('Iva','SC'),('Ivan','AR'),('Ivanhoe','CA'),('Ivanhoe','MN'),('Ivanhoe','NC'),('Ivanhoe','TX'),('Ivanhoe','VA'),('Ivel','KY'),('Ivesdale','IL'),('Ivins','UT'),('Ivor','VA'),('Ivoryton','CT'),('Ivy','VA'),('Ivydale','WV'),('Ixonia','WI'),('Jachin','AL'),('Jack','AL'),('Jackhorn','KY'),('Jackman','ME'),('Jackpot','NV'),('Jacks Creek','TN'),('Jacksboro','TN'),('Jacksboro','TX'),('Jackson','AL'),('Jackson','CA'),('Jackson','GA'),('Jackson','KY'),('Jackson','LA'),('Jackson','MI'),('Jackson','MN'),('Jackson','MO'),('Jackson','MS'),('Jackson','MT'),('Jackson','NC'),('Jackson','NE'),('Jackson','NH'),('Jackson','NJ'),('Jackson','OH'),('Jackson','PA'),('Jackson','SC'),('Jackson','TN'),('Jackson','WI'),('Jackson','WY'),('Jackson Center','OH'),('Jackson Center','PA'),('Jackson Heights','NY'),('Jackson Springs','NC'),('Jacksonboro','SC'),('Jacksonburg','WV'),('Jacksonport','AR'),('Jacksons Gap','AL'),('Jacksontown','OH'),('Jacksonville','AL'),('Jacksonville','AR'),('Jacksonville','FL'),('Jacksonville','GA'),('Jacksonville','IL'),('Jacksonville','MO'),('Jacksonville','NC'),('Jacksonville','NY'),('Jacksonville','OH'),('Jacksonville','OR'),('Jacksonville','TX'),('Jacksonville','VT'),('Jacksonville Beach','FL'),('Jacob','IL'),('Jacobs Creek','PA'),('Jacobsburg','OH'),('Jacobson','MN'),('Jacumba','CA'),('Jadwin','MO'),('Jaffrey','NH'),('Jakin','GA'),('Jal','NM'),('Jamaica','IA'),('Jamaica','NY'),('Jamaica','VA'),('Jamaica','VT'),('Jamaica Plain','MA'),('James City','PA'),('James Creek','PA'),('Jameson','MO'),('Jamesport','MO'),('Jamesport','NY'),('Jamestown','CA'),('Jamestown','CO'),('Jamestown','IN'),('Jamestown','KS'),('Jamestown','KY'),('Jamestown','LA'),('Jamestown','MI'),('Jamestown','MO'),('Jamestown','NC'),('Jamestown','ND'),('Jamestown','NM'),('Jamestown','NY'),('Jamestown','OH'),('Jamestown','PA'),('Jamestown','RI'),('Jamestown','SC'),('Jamestown','TN'),('Jamestown','VA'),('Jamesville','NC'),('Jamesville','NY'),('Jamesville','VA'),('Jamieson','OR'),('Jamison','PA'),('Jamul','CA'),('Jane Lew','WV'),('Janesville','CA'),('Janesville','IA'),('Janesville','IL'),('Janesville','MN'),('Janesville','WI'),('Jansen','NE'),('Jarales','NM'),('Jarbidge','NV'),('Jaroso','CO'),('Jarratt','VA'),('Jarreau','LA'),('Jarrell','TX'),('Jarrettsville','MD'),('Jarvisburg','NC'),('Jasonville','IN'),('Jasper','AL'),('Jasper','AR'),('Jasper','FL'),('Jasper','GA'),('Jasper','IN'),('Jasper','MI'),('Jasper','MN'),('Jasper','MO'),('Jasper','NY'),('Jasper','OH'),('Jasper','TN'),('Jasper','TX'),('Java','SD'),('Java','VA'),('Java Center','NY'),('Java Village','NY'),('Jay','FL'),('Jay','ME'),('Jay','NY'),('Jay','OK'),('Jay Em','WY'),('Jayess','MS'),('Jayton','TX'),('Jayuya','PR'),('Jean','NV'),('Jeanerette','LA'),('Jeannette','PA'),('Jeddo','MI'),('Jeff','KY'),('Jeffers','MN'),('Jefferson','AL'),('Jefferson','AR'),('Jefferson','CO'),('Jefferson','GA'),('Jefferson','IA'),('Jefferson','MA'),('Jefferson','MD'),('Jefferson','ME'),('Jefferson','NC'),('Jefferson','NH'),('Jefferson','NY'),('Jefferson','OH'),('Jefferson','OR'),('Jefferson','PA'),('Jefferson','SC'),('Jefferson','SD'),('Jefferson','TX'),('Jefferson','WI'),('Jefferson City','MO'),('Jefferson City','MT'),('Jefferson City','TN'),('Jefferson Valley','NY'),('Jeffersonton','VA'),('Jeffersonville','GA'),('Jeffersonville','IN'),('Jeffersonville','KY'),('Jeffersonville','NY'),('Jeffersonville','OH'),('Jeffersonville','VT'),('Jeffrey','WV'),('Jeffrey City','WY'),('Jekyll Island','GA'),('Jellico','TN'),('Jelm','WY'),('Jemez Pueblo','NM'),('Jemez Springs','NM'),('Jemison','AL'),('Jena','LA'),('Jenera','OH'),('Jenison','MI'),('Jenkinjones','WV'),('Jenkins','KY'),('Jenkins','MN'),('Jenkins Bridge','VA'),('Jenkinsburg','GA'),('Jenkinsville','SC'),('Jenkintown','PA'),('Jenks','OK'),('Jenner','CA'),('Jenners','PA'),('Jennerstown','PA'),('Jennings','FL'),('Jennings','KS'),('Jennings','LA'),('Jennings','OK'),('Jensen','UT'),('Jensen Beach','FL'),('Jeremiah','KY'),('Jericho','NY'),('Jericho','VT'),('Jerico Springs','MO'),('Jermyn','PA'),('Jermyn','TX'),('Jerome','AZ'),('Jerome','ID'),('Jerome','MI'),('Jerome','MO'),('Jerome','PA'),('Jeromesville','OH'),('Jerry City','OH'),('Jersey','AR'),('Jersey','GA'),('Jersey','VA'),('Jersey City','NJ'),('Jersey Mills','PA'),('Jersey Shore','PA'),('Jerseyville','IL'),('Jerusalem','AR'),('Jerusalem','OH'),('Jesse','WV'),('Jessie','ND'),('Jessieville','AR'),('Jessup','MD'),('Jessup','PA'),('Jesup','GA'),('Jesup','IA'),('Jet','OK'),('Jetersville','VA'),('Jetmore','KS'),('Jetson','KY'),('Jewell','GA'),('Jewell','IA'),('Jewell','KS'),('Jewell','OH'),('Jewell Ridge','VA'),('Jewett','IL'),('Jewett','NY'),('Jewett','OH'),('Jewett','TX'),('Jewett City','CT'),('Jigger','LA'),('Jim Falls','WI'),('Jim Thorpe','PA'),('Joanna','SC'),('Joaquin','TX'),('Jobstown','NJ'),('Joelton','TN'),('Joes','CO'),('Joffre','PA'),('Johannesburg','CA'),('Johannesburg','MI'),('John Day','OR'),('Johns Island','SC'),('Johnsburg','NY'),('Johnson','AR'),('Johnson','KS'),('Johnson','NE'),('Johnson','NY'),('Johnson','VT'),('Johnson City','NY'),('Johnson City','TN'),('Johnson City','TX'),('Johnson Creek','WI'),('Johnsonburg','NJ'),('Johnsonburg','PA'),('Johnsonville','IL'),('Johnsonville','NY'),('Johnsonville','SC'),('Johnston','IA'),('Johnston','RI'),('Johnston','SC'),('Johnston City','IL'),('Johnstown','CO'),('Johnstown','NE'),('Johnstown','NY'),('Johnstown','OH'),('Johnstown','PA'),('Joice','IA'),('Joiner','AR'),('Joinerville','TX'),('Joliet','IL'),('Joliet','MT'),('Jolley','IA'),('Jolo','WV'),('Jolon','CA'),('Jonancy','KY'),('Jonas Ridge','NC'),('Jones','AL'),('Jones','LA'),('Jones','MI'),('Jones','OK'),('Jones Mill','AR'),('Jones Mills','PA'),('Jonesboro','AR'),('Jonesboro','GA'),('Jonesboro','IL'),('Jonesboro','IN'),('Jonesboro','LA'),('Jonesboro','ME'),('Jonesboro','TX'),('Jonesborough','TN'),('Jonesburg','MO'),('Jonesport','ME'),('Jonestown','MS'),('Jonestown','PA'),('Jonesville','IN'),('Jonesville','KY'),('Jonesville','LA'),('Jonesville','MI'),('Jonesville','NC'),('Jonesville','SC'),('Jonesville','TX'),('Jonesville','VA'),('Jonesville','VT'),('Joplin','MO'),('Joplin','MT'),('Joppa','AL'),('Joppa','IL'),('Joppa','MD'),('Jordan','MN'),('Jordan','MT'),('Jordan','NY'),('Jordan Valley','OR'),('Jordanville','NY'),('Joseph','OR'),('Joseph','UT'),('Joseph City','AZ'),('Josephine','PA'),('Josephine','TX'),('Josephine','WV'),('Joshua','TX'),('Joshua Tree','CA'),('Jourdanton','TX'),('Joy','IL'),('Joyce','LA'),('Joyce','WA'),('Juana Diaz','PR'),('Jud','ND'),('Juda','WI'),('Judith Gap','MT'),('Judson','IN'),('Judson','TX'),('Judsonia','AR'),('Julesburg','CO'),('Juliaetta','ID'),('Julian','CA'),('Julian','NC'),('Julian','NE'),('Julian','PA'),('Julian','WV'),('Juliette','GA'),('Juliustown','NJ'),('Jump River','WI'),('Jumping Branch','WV'),('Juncos','PR'),('Junction','IL'),('Junction','TX'),('Junction','UT'),('Junction City','AR'),('Junction City','CA'),('Junction City','GA'),('Junction City','KS'),('Junction City','KY'),('Junction City','OH'),('Junction City','OR'),('Junction City','WI'),('June Lake','CA'),('Juneau','AK'),('Juneau','WI'),('Junedale','PA'),('Juniata','NE'),('Junior','WV'),('Juntura','OR'),('Jupiter','FL'),('Justice','IL'),('Justice','WV'),('Justiceburg','TX'),('Justin','TX'),('Kaaawa','HI'),('Kabetogama','MN'),('Kadoka','SD'),('Kahlotus','WA'),('Kahoka','MO'),('Kahuku','HI'),('Kahului','HI'),('Kaibeto','AZ'),('Kailua','HI'),('Kailua Kona','HI'),('Kaiser','MO'),('Kake','AK'),('Kaktovik','AK'),('Kalaheo','HI'),('Kalama','WA'),('Kalamazoo','MI'),('Kalaupapa','HI'),('Kaleva','MI'),('Kalida','OH'),('Kalispell','MT'),('Kalkaska','MI'),('Kalona','IA'),('Kalskag','AK'),('Kaltag','AK'),('Kamas','UT'),('Kamay','TX'),('Kamiah','ID'),('Kampsville','IL'),('Kamrar','IA'),('Kamuela','HI'),('Kanab','UT'),('Kanaranzi','MN'),('Kanarraville','UT'),('Kanawha','IA'),('Kanawha Falls','WV'),('Kanawha Head','WV'),('Kandiyohi','MN'),('Kane','IL'),('Kane','PA'),('Kaneohe','HI'),('Kaneville','IL'),('Kankakee','IL'),('Kannapolis','NC'),('Kanona','NY'),('Kanopolis','KS'),('Kanorado','KS'),('Kanosh','UT'),('Kansas','AL'),('Kansas','IL'),('Kansas','OH'),('Kansas','OK'),('Kansas City','KS'),('Kansas City','MO'),('Kansasville','WI'),('Kantner','PA'),('Kapaa','HI'),('Kapaau','HI'),('Kaplan','LA'),('Kapolei','HI'),('Kapowsin','WA'),('Karbers Ridge','IL'); + +INSERT INTO [cities] VALUES ('Karlsruhe','ND'),('Karlstad','MN'),('Karluk','AK'),('Karnack','TX'),('Karnak','IL'),('Karnes City','TX'),('Karns City','PA'),('Karthaus','PA'),('Karval','CO'),('Kasbeer','IL'),('Kasigluk','AK'),('Kasilof','AK'),('Kasota','MN'),('Kasson','MN'),('Kathleen','FL'),('Kathleen','GA'),('Kathryn','ND'),('Katonah','NY'),('Kattskill Bay','NY'),('Katy','TX'),('Kaufman','TX'),('Kaukauna','WI'),('Kaumakani','HI'),('Kaunakakai','HI'),('Kauneonga Lake','NY'),('Kaw City','OK'),('Kaweah','CA'),('Kawkawlin','MI'),('Kaycee','WY'),('Kayenta','AZ'),('Kaylor','SD'),('Kaysville','UT'),('Keaau','HI'),('Kealakekua','HI'),('Kealia','HI'),('Keams Canyon','AZ'),('Keansburg','NJ'),('Kearney','MO'),('Kearney','NE'),('Kearneysville','WV'),('Kearny','AZ'),('Kearny','NJ'),('Kearsarge','MI'),('Kearsarge','NH'),('Keasbey','NJ'),('Keatchie','LA'),('Keaton','KY'),('Keauhou','HI'),('Keavy','KY'),('Kechi','KS'),('Keedysville','MD'),('Keego Harbor','MI'),('Keeler','CA'),('Keeling','VA'),('Keen Mountain','VA'),('Keene','CA'),('Keene','KY'),('Keene','ND'),('Keene','NH'),('Keene','NY'),('Keene','OH'),('Keene','TX'),('Keene','VA'),('Keene Valley','NY'),('Keenes','IL'),('Keenesburg','CO'),('Keensburg','IL'),('Keeseville','NY'),('Keewatin','MN'),('Keezletown','VA'),('Kegley','WV'),('Keiser','AR'),('Keisterville','PA'),('Keithsburg','IL'),('Keithville','LA'),('Keizer','OR'),('Kekaha','HI'),('Kelayres','PA'),('Keldron','SD'),('Kelford','NC'),('Kell','IL'),('Keller','TX'),('Keller','VA'),('Keller','WA'),('Kellerman','AL'),('Kellerton','IA'),('Kelley','IA'),('Kelleys Island','OH'),('Kelliher','MN'),('Kellnersville','WI'),('Kellogg','IA'),('Kellogg','ID'),('Kellogg','MN'),('Kelly','LA'),('Kelly','NC'),('Kelly','WY'),('Kellysville','WV'),('Kellyton','AL'),('Kellyville','OK'),('Kelseyville','CA'),('Kelso','MO'),('Kelso','TN'),('Kelso','WA'),('Kelton','PA'),('Kemah','TX'),('Kemblesville','PA'),('Kemmerer','WY'),('Kemp','OK'),('Kemp','TX'),('Kempner','TX'),('Kempton','IL'),('Kempton','IN'),('Kempton','PA'),('Kenai','AK'),('Kenansville','FL'),('Kenansville','NC'),('Kenbridge','VA'),('Kendalia','TX'),('Kendall','KS'),('Kendall','MI'),('Kendall','NY'),('Kendall','WI'),('Kendall Park','NJ'),('Kendallville','IN'),('Kendleton','TX'),('Kendrick','ID'),('Kenduskeag','ME'),('Kenedy','TX'),('Kenefic','OK'),('Kenesaw','NE'),('Kenilworth','IL'),('Kenilworth','NJ'),('Kenilworth','UT'),('Kenly','NC'),('Kenmare','ND'),('Kenmore','WA'),('Kenna','NM'),('Kenna','WV'),('Kennan','WI'),('Kennard','IN'),('Kennard','NE'),('Kennard','TX'),('Kennebec','SD'),('Kennebunk','ME'),('Kennebunkport','ME'),('Kennedale','TX'),('Kennedy','AL'),('Kennedy','MN'),('Kennedy','NY'),('Kennedyville','MD'),('Kenner','LA'),('Kennerdell','PA'),('Kennesaw','GA'),('Kenneth','MN'),('Kennett','MO'),('Kennett Square','PA'),('Kennewick','WA'),('Kenney','IL'),('Kenney','TX'),('Keno','OR'),('Kenosha','WI'),('Kenova','WV'),('Kenoza Lake','NY'),('Kensal','ND'),('Kensett','AR'),('Kensett','IA'),('Kensington','KS'),('Kensington','MD'),('Kensington','MN'),('Kensington','OH'),('Kent','AL'),('Kent','CT'),('Kent','IL'),('Kent','MN'),('Kent','NY'),('Kent','OH'),('Kent','OR'),('Kent','PA'),('Kent','WA'),('Kent City','MI'),('Kentfield','CA'),('Kentland','IN'),('Kenton','DE'),('Kenton','KY'),('Kenton','OH'),('Kenton','OK'),('Kenton','TN'),('Kents Hill','ME'),('Kents Store','VA'),('Kentwood','LA'),('Kenvil','NJ'),('Kenvir','KY'),('Kenwood','CA'),('Kenyon','MN'),('Kenyon','RI'),('Keo','AR'),('Keokee','VA'),('Keokuk','IA'),('Keosauqua','IA'),('Keota','IA'),('Keota','OK'),('Kerby','OR'),('Kerens','TX'),('Kerens','WV'),('Kerhonkson','NY'),('Kerkhoven','MN'),('Kerman','CA'),('Kermit','TX'),('Kermit','WV'),('Kernersville','NC'),('Kernville','CA'),('Kerr','OH'),('Kerrick','MN'),('Kerrick','TX'),('Kerrville','TX'),('Kersey','CO'),('Kersey','PA'),('Kershaw','SC'),('Keshena','WI'),('Keslers Cross Lanes','WV'),('Kesley','IA'),('Keswick','IA'),('Keswick','VA'),('Ketchikan','AK'),('Ketchum','ID'),('Ketchum','OK'),('Kettle Falls','WA'),('Kettle Island','KY'),('Kettle River','MN'),('Kettleman City','CA'),('Kettlersville','OH'),('Keuka Park','NY'),('Kevil','KY'),('Kevin','MT'),('Kew Gardens','NY'),('Kewadin','MI'),('Kewanee','IL'),('Kewanee','MO'),('Kewanna','IN'),('Kewaskum','WI'),('Kewaunee','WI'),('Key Biscayne','FL'),('Key Colony Beach','FL'),('Key Largo','FL'),('Key West','FL'),('Keyes','CA'),('Keyes','OK'),('Keyesport','IL'),('Keymar','MD'),('Keyport','NJ'),('Keyport','WA'),('Keyser','WV'),('Keystone','IA'),('Keystone','IN'),('Keystone','NE'),('Keystone','SD'),('Keystone Heights','FL'),('Keysville','GA'),('Keysville','VA'),('Keytesville','MO'),('Kiahsville','WV'),('Kiamesha Lake','NY'),('Kiamichi Christian Mission','OK'),('Kiana','AK'),('Kidder','MO'),('Kidron','OH'),('Kiefer','OK'),('Kiel','WI'),('Kieler','WI'),('Kiester','MN'),('Kihei','HI'),('Kila','MT'),('Kilauea','HI'),('Kilbourne','IL'),('Kilbourne','LA'),('Kilbourne','OH'),('Kildare','TX'),('Kilgore','NE'),('Kilgore','TX'),('Kilkenny','MN'),('Kill Buck','NY'),('Kill Devil Hills','NC'),('Killarney','FL'),('Killawog','NY'),('Killbuck','OH'),('Killdeer','ND'),('Killduff','IA'),('Killeen','TX'),('Killen','AL'),('Killington','VT'),('Killingworth','CT'),('Kilmarnock','VA'),('Kilmichael','MS'),('Kiln','MS'),('Kim','CO'),('Kimball','MN'),('Kimball','NE'),('Kimball','SD'),('Kimball','WV'),('Kimballton','IA'),('Kimberling City','MO'),('Kimberly','AL'),('Kimberly','ID'),('Kimberly','OR'),('Kimberly','WI'),('Kimberly','WV'),('Kimberton','PA'),('Kimbolton','OH'),('Kimmell','IN'),('Kimmswick','MO'),('Kimper','KY'),('Kinards','SC'),('Kincaid','IL'),('Kincaid','KS'),('Kincaid','WV'),('Kincheloe','MI'),('Kinde','MI'),('Kinder','LA'),('Kinderhook','IL'),('Kinderhook','NY'),('Kindred','ND'),('King','NC'),('King','WI'),('King And Queen Court House','VA'),('King City','CA'),('King City','MO'),('King Cove','AK'),('King Ferry','NY'),('King George','VA'),('King Hill','ID'),('King Of Prussia','PA'),('King Salmon','AK'),('King William','VA'),('Kingdom City','MO'),('Kingfield','ME'),('Kingfisher','OK'),('Kingman','AZ'),('Kingman','IN'),('Kingman','KS'),('Kingman','ME'),('Kingmont','WV'),('Kings Bay','GA'),('Kings Beach','CA'),('Kings Canyon National Pk','CA'),('Kings Mills','OH'),('Kings Mountain','KY'),('Kings Mountain','NC'),('Kings Park','NY'),('Kingsburg','CA'),('Kingsbury','IN'),('Kingsbury','TX'),('Kingsford','MI'),('Kingsford Heights','IN'),('Kingsland','AR'),('Kingsland','GA'),('Kingsland','TX'),('Kingsley','IA'),('Kingsley','MI'),('Kingsley','PA'),('Kingsport','TN'),('Kingston','AR'),('Kingston','GA'),('Kingston','ID'),('Kingston','IL'),('Kingston','MA'),('Kingston','MI'),('Kingston','MO'),('Kingston','NH'),('Kingston','NJ'),('Kingston','NY'),('Kingston','OH'),('Kingston','OK'),('Kingston','PA'),('Kingston','RI'),('Kingston','TN'),('Kingston','UT'),('Kingston','WA'),('Kingston','WI'),('Kingston Mines','IL'),('Kingston Springs','TN'),('Kingstree','SC'),('Kingsville','MD'),('Kingsville','MO'),('Kingsville','OH'),('Kingsville','TX'),('Kingwood','TX'),('Kingwood','WV'),('Kinmundy','IL'),('Kinnear','WY'),('Kinney','MN'),('Kinross','MI'),('Kinsale','VA'),('Kinsey','MT'),('Kinsley','KS'),('Kinsman','IL'),('Kinsman','OH'),('Kinston','AL'),('Kinston','NC'),('Kinta','OK'),('Kintnersville','PA'),('Kintyre','ND'),('Kinzers','PA'),('Kiowa','CO'),('Kiowa','KS'),('Kiowa','OK'),('Kipling','NC'),('Kipling','OH'),('Kipnuk','AK'),('Kipton','OH'),('Kirby','AR'),('Kirby','OH'),('Kirby','WY'),('Kirbyville','MO'),('Kirbyville','TX'),('Kirk','CO'),('Kirkersville','OH'),('Kirkland','AZ'),('Kirkland','IL'),('Kirkland','WA'),('Kirklin','IN'),('Kirkman','IA'),('Kirksey','KY'),('Kirksville','MO'),('Kirkville','IA'),('Kirkville','NY'),('Kirkwood','CA'),('Kirkwood','DE'),('Kirkwood','IL'),('Kirkwood','NY'),('Kirkwood','PA'),('Kiron','IA'),('Kirtland','NM'),('Kirtland Afb','NM'),('Kirvin','TX'),('Kirwin','KS'),('Kismet','KS'),('Kissee Mills','MO'),('Kissimmee','FL'),('Kistler','WV'),('Kit Carson','CA'),('Kit Carson','CO'),('Kite','GA'),('Kite','KY'),('Kittanning','PA'),('Kittery','ME'),('Kittery Point','ME'),('Kittitas','WA'),('Kittredge','CO'),('Kittrell','NC'),('Kitts Hill','OH'),('Kitty Hawk','NC'),('Kitzmiller','MD'),('Kivalina','AK'),('Klamath','CA'),('Klamath Falls','OR'),('Klamath River','CA'),('Klawock','AK'),('Kleinfeltersville','PA'),('Klemme','IA'),('Klickitat','WA'),('Klingerstown','PA'),('Klondike','TX'),('Knapp','WI'),('Kneeland','CA'),('Knickerbocker','TX'),('Knierim','IA'),('Knife River','MN'),('Knifley','KY'),('Knightdale','NC'),('Knights Landing','CA'),('Knightsen','CA'),('Knightstown','IN'),('Knightsville','IN'),('Knippa','TX'),('Knob Lick','KY'),('Knob Lick','MO'),('Knob Noster','MO'),('Knobel','AR'),('Knott','TX'),('Knotts Island','NC'),('Knowlesville','NY'),('Knox','IN'),('Knox','ND'),('Knox','NY'),('Knox','PA'),('Knox City','MO'),('Knox City','TX'),('Knox Dale','PA'),('Knoxboro','NY'),('Knoxville','AL'),('Knoxville','AR'),('Knoxville','GA'),('Knoxville','IA'),('Knoxville','IL'),('Knoxville','MD'),('Knoxville','PA'),('Knoxville','TN'),('Kobuk','AK'),('Kodak','TN'),('Kodiak','AK'); + +INSERT INTO [cities] VALUES ('Koeltztown','MO'),('Kohler','WI'),('Kokomo','IN'),('Kokomo','MS'),('Koleen','IN'),('Koloa','HI'),('Konawa','OK'),('Kongiganak','AK'),('Koosharem','UT'),('Kooskia','ID'),('Kootenai','ID'),('Koppel','PA'),('Kopperl','TX'),('Kopperston','WV'),('Korbel','CA'),('Kosciusko','MS'),('Koshkonong','MO'),('Kosse','TX'),('Kossuth','PA'),('Kotlik','AK'),('Kotzebue','AK'),('Kountze','TX'),('Kouts','IN'),('Koyuk','AK'),('Koyukuk','AK'),('Kraemer','LA'),('Krakow','WI'),('Kramer','ND'),('Kranzburg','SD'),('Kreamer','PA'),('Krebs','OK'),('Kremlin','MT'),('Kremlin','OK'),('Kremmling','CO'),('Kresgeville','PA'),('Kress','TX'),('Krotz Springs','LA'),('Krum','TX'),('Krypton','KY'),('Kualapuu','HI'),('Kula','HI'),('Kulm','ND'),('Kulpmont','PA'),('Kulpsville','PA'),('Kuna','ID'),('Kunia','HI'),('Kunkle','OH'),('Kunkletown','PA'),('Kure Beach','NC'),('Kurten','TX'),('Kurthwood','LA'),('Kurtistown','HI'),('Kurtz','IN'),('Kuttawa','KY'),('Kutztown','PA'),('Kwethluk','AK'),('Kwigillingok','AK'),('Kyburz','CA'),('Kykotsmovi Village','AZ'),('Kyle','SD'),('Kyle','TX'),('Kyle','WV'),('Kylertown','PA'),('Kyles Ford','TN'),('La Barge','WY'),('La Belle','MO'),('La Belle','PA'),('La Blanca','TX'),('La Canada Flintridge','CA'),('La Center','KY'),('La Center','WA'),('La Conner','WA'),('La Coste','TX'),('La Crescent','MN'),('La Crescenta','CA'),('La Crosse','FL'),('La Crosse','IN'),('La Crosse','KS'),('La Crosse','VA'),('La Crosse','WI'),('La Farge','WI'),('La Fargeville','NY'),('La Fayette','GA'),('La Fayette','IL'),('La Fayette','KY'),('La Fayette','NY'),('La Feria','TX'),('La Follette','TN'),('La Fontaine','IN'),('La France','SC'),('La Grande','OR'),('La Grande','WA'),('La Grange','AR'),('La Grange','CA'),('La Grange','IL'),('La Grange','KY'),('La Grange','MO'),('La Grange','NC'),('La Grange','TN'),('La Grange','TX'),('La Grange Park','IL'),('La Habra','CA'),('La Harpe','IL'),('La Harpe','KS'),('La Honda','CA'),('La Jara','CO'),('La Jara','NM'),('La Jolla','CA'),('La Jose','PA'),('La Joya','NM'),('La Joya','TX'),('La Junta','CO'),('La Loma','NM'),('La Luz','NM'),('La Madera','NM'),('La Marque','TX'),('La Mesa','CA'),('La Mesa','NM'),('La Mirada','CA'),('La Moille','IL'),('La Monte','MO'),('La Motte','IA'),('La Palma','CA'),('La Pine','OR'),('La Place','IL'),('La Place','LA'),('La Plata','MD'),('La Plata','MO'),('La Plata','NM'),('La Plata','PR'),('La Plume','PA'),('La Pointe','WI'),('La Porte','IN'),('La Porte','TX'),('La Porte City','IA'),('La Prairie','IL'),('La Pryor','TX'),('La Puente','CA'),('La Push','WA'),('La Quinta','CA'),('La Rose','IL'),('La Rue','OH'),('La Russell','MO'),('La Sal','UT'),('La Salle','CO'),('La Salle','IL'),('La Salle','MI'),('La Salle','MN'),('La Salle','TX'),('La Valle','WI'),('La Vergne','TN'),('La Verkin','UT'),('La Verne','CA'),('La Vernia','TX'),('La Veta','CO'),('La Villa','TX'),('La Vista','NE'),('La Ward','TX'),('Labadie','MO'),('Labadieville','LA'),('Labelle','FL'),('Labolt','SD'),('Lac Du Flambeau','WI'),('Lacarne','OH'),('Lacassine','LA'),('Lacey','WA'),('Lacey Spring','VA'),('Laceys Spring','AL'),('Laceyville','PA'),('Lachine','MI'),('Lackawaxen','PA'),('Lackey','KY'),('Lackey','VA'),('Laclede','ID'),('Laclede','MO'),('Lacombe','LA'),('Lacon','IL'),('Lacona','IA'),('Lacona','NY'),('Laconia','IN'),('Laconia','NH'),('Laconia','TN'),('Lacoochee','FL'),('Lacota','MI'),('Lacrosse','WA'),('Lacygne','KS'),('Ladd','IL'),('Laddonia','MO'),('Ladera Ranch','CA'),('Ladiesburg','MD'),('Ladoga','IN'),('Ladonia','TX'),('Ladora','IA'),('Ladson','SC'),('Lady Lake','FL'),('Ladys Island','SC'),('Ladysmith','VA'),('Ladysmith','WI'),('Lafayette','AL'),('Lafayette','CA'),('Lafayette','CO'),('Lafayette','IN'),('Lafayette','LA'),('Lafayette','MN'),('Lafayette','NJ'),('Lafayette','OH'),('Lafayette','OR'),('Lafayette','TN'),('Lafayette Hill','PA'),('Lafe','AR'),('Lafferty','OH'),('Lafitte','LA'),('Lafox','IL'),('Lagrange','GA'),('Lagrange','IN'),('Lagrange','ME'),('Lagrange','OH'),('Lagrange','WY'),('Lagrangeville','NY'),('Lagro','IN'),('Laguna','NM'),('Laguna Beach','CA'),('Laguna Hills','CA'),('Laguna Niguel','CA'),('Laguna Park','TX'),('Laguna Woods','CA'),('Lagunitas','CA'),('Lahaina','HI'),('Lahaska','PA'),('Lahmansville','WV'),('Lahoma','OK'),('Laie','HI'),('Laings','OH'),('Laingsburg','MI'),('Laird Hill','TX'),('Lairdsville','PA'),('Lajas','PR'),('Lake','MI'),('Lake','MS'),('Lake','WV'),('Lake Alfred','FL'),('Lake Andes','SD'),('Lake Ann','MI'),('Lake Ariel','PA'),('Lake Arrowhead','CA'),('Lake Arthur','LA'),('Lake Arthur','NM'),('Lake Benton','MN'),('Lake Bluff','IL'),('Lake Bronson','MN'),('Lake Butler','FL'),('Lake Charles','LA'),('Lake Cicott','IN'),('Lake City','AR'),('Lake City','CA'),('Lake City','CO'),('Lake City','FL'),('Lake City','IA'),('Lake City','KS'),('Lake City','MI'),('Lake City','MN'),('Lake City','PA'),('Lake City','SC'),('Lake City','SD'),('Lake City','TN'),('Lake Clear','NY'),('Lake Como','FL'),('Lake Como','PA'),('Lake Cormorant','MS'),('Lake Creek','TX'),('Lake Crystal','MN'),('Lake Dallas','TX'),('Lake Delton','WI'),('Lake Elmo','MN'),('Lake Elmore','VT'),('Lake Elsinore','CA'),('Lake Forest','CA'),('Lake Forest','IL'),('Lake Fork','ID'),('Lake Fork','IL'),('Lake Geneva','FL'),('Lake Geneva','WI'),('Lake George','CO'),('Lake George','MI'),('Lake George','MN'),('Lake George','NY'),('Lake Grove','NY'),('Lake Hamilton','FL'),('Lake Harbor','FL'),('Lake Harmony','PA'),('Lake Havasu City','AZ'),('Lake Helen','FL'),('Lake Hiawatha','NJ'),('Lake Hill','NY'),('Lake Hopatcong','NJ'),('Lake Hubert','MN'),('Lake Hughes','CA'),('Lake Huntington','NY'),('Lake In The Hills','IL'),('Lake Isabella','CA'),('Lake Jackson','TX'),('Lake Junaluska','NC'),('Lake Katrine','NY'),('Lake Leelanau','MI'),('Lake Lillian','MN'),('Lake Linden','MI'),('Lake Lure','NC'),('Lake Luzerne','NY'),('Lake Lynn','PA'),('Lake Mary','FL'),('Lake Mc Donald','MT'),('Lake Mills','IA'),('Lake Mills','WI'),('Lake Milton','OH'),('Lake Minchumina','AK'),('Lake Monroe','FL'),('Lake Montezuma','AZ'),('Lake Nebagamon','WI'),('Lake Norden','SD'),('Lake Odessa','MI'),('Lake Orion','MI'),('Lake Oswego','OR'),('Lake Ozark','MO'),('Lake Panasoffkee','FL'),('Lake Park','GA'),('Lake Park','IA'),('Lake Park','MN'),('Lake Peekskill','NY'),('Lake Placid','FL'),('Lake Placid','NY'),('Lake Pleasant','MA'),('Lake Pleasant','NY'),('Lake Powell','UT'),('Lake Preston','SD'),('Lake Providence','LA'),('Lake Saint Louis','MO'),('Lake Spring','MO'),('Lake Station','IN'),('Lake Stevens','WA'),('Lake Tomahawk','WI'),('Lake Toxaway','NC'),('Lake View','IA'),('Lake View','NY'),('Lake View','SC'),('Lake Villa','IL'),('Lake Village','AR'),('Lake Village','IN'),('Lake Waccamaw','NC'),('Lake Wales','FL'),('Lake Wilson','MN'),('Lake Winola','PA'),('Lake Worth','FL'),('Lake Zurich','IL'),('Lakebay','WA'),('Lakefield','MN'),('Lakehead','CA'),('Lakehills','TX'),('Lakehurst','NJ'),('Lakeland','FL'),('Lakeland','GA'),('Lakeland','LA'),('Lakeland','MI'),('Lakeland','MN'),('Lakemont','GA'),('Lakemont','NY'),('Lakemore','OH'),('Lakeport','CA'),('Lakeshore','CA'),('Lakeshore','MS'),('Lakeside','AZ'),('Lakeside','CA'),('Lakeside','CT'),('Lakeside','MI'),('Lakeside','MT'),('Lakeside','NE'),('Lakeside','OR'),('Lakeside Marblehead','OH'),('Laketon','IN'),('Laketown','UT'),('Lakeview','AR'),('Lakeview','MI'),('Lakeview','NC'),('Lakeview','OH'),('Lakeview','OR'),('Lakeview','TX'),('Lakeville','CT'),('Lakeville','IN'),('Lakeville','MA'),('Lakeville','MI'),('Lakeville','MN'),('Lakeville','NY'),('Lakeville','OH'),('Lakeville','PA'),('Lakewood','CA'),('Lakewood','IL'),('Lakewood','NJ'),('Lakewood','NM'),('Lakewood','NY'),('Lakewood','OH'),('Lakewood','PA'),('Lakewood','WA'),('Lakewood','WI'),('Lakin','KS'),('Lakota','IA'),('Lakota','ND'),('Lamar','AR'),('Lamar','CO'),('Lamar','IN'),('Lamar','MO'),('Lamar','MS'),('Lamar','OK'),('Lamar','PA'),('Lamar','SC'),('Lamartine','PA'),('Lambert','MS'),('Lambert','MT'),('Lambert Lake','ME'),('Lamberton','MN'),('Lambertville','MI'),('Lambertville','NJ'),('Lambrook','AR'),('Lambsburg','VA'),('Lame Deer','MT'),('Lamesa','TX'),('Lamoille','NV'),('Lamona','WA'),('Lamoni','IA'),('Lamont','CA'),('Lamont','FL'),('Lamont','IA'),('Lamont','KS'),('Lamont','MI'),('Lamont','OK'),('Lamont','WA'),('Lamoure','ND'),('Lampasas','TX'),('Lampe','MO'),('Lampeter','PA'),('Lamy','NM'),('Lanagan','MO'),('Lanai City','HI'),('Lanark','IL'),('Lanark','WV'),('Lanark Village','FL'),('Lancaster','CA'),('Lancaster','IL'),('Lancaster','KS'),('Lancaster','KY'),('Lancaster','MA'),('Lancaster','MN'),('Lancaster','MO'),('Lancaster','NH'),('Lancaster','NY'),('Lancaster','OH'),('Lancaster','PA'),('Lancaster','SC'),('Lancaster','TN'),('Lancaster','TX'),('Lancaster','VA'),('Lancaster','WI'),('Lance Creek','WY'),('Lancing','TN'),('Land O Lakes','FL'),('Land O Lakes','WI'),('Landenberg','PA'),('Lander','WY'),('Landers','CA'),('Landing','NJ'),('Landingville','PA'),('Landis','NC'),('Landisburg','PA'),('Landisville','NJ'),('Landisville','PA'),('Lando','SC'),('Landrum','SC'),('Lane','IL'),('Lane','KS'),('Lane','OK'),('Lane','SC'),('Lane','SD'),('Lane City','TX'),('Laneburg','AR'),('Lanesboro','IA'),('Lanesboro','MA'),('Lanesboro','MN'),('Lanesboro','PA'),('Lanesville','IN'),('Lanesville','NY'),('Lanett','AL'),('Laneview','VA'),('Laneville','TX'),('Lanexa','VA'),('Langdon','ND'),('Langeloth','PA'),('Langford','SD'),('Langhorne','PA'),('Langley','AR'),('Langley','KY'),('Langley','OK'),('Langley','SC'); + +INSERT INTO [cities] VALUES ('Langley','WA'),('Langlois','OR'),('Langston','AL'),('Langston','OK'),('Langsville','OH'),('Langtry','TX'),('Langworthy','IA'),('Lanham','MD'),('Lankin','ND'),('Lannon','WI'),('Lanoka Harbor','NJ'),('Lansdale','PA'),('Lansdowne','PA'),('Lanse','MI'),('Lanse','PA'),('Lansford','ND'),('Lansford','PA'),('Lansing','IA'),('Lansing','IL'),('Lansing','KS'),('Lansing','MI'),('Lansing','MN'),('Lansing','NC'),('Lansing','NY'),('Lansing','OH'),('Lansing','WV'),('Lantry','SD'),('Laona','WI'),('Laotto','IN'),('Lapaz','IN'),('Lapeer','MI'),('Lapel','IN'),('Lapine','AL'),('Lapoint','UT'),('Laporte','CO'),('Laporte','MN'),('Laporte','PA'),('Lapwai','ID'),('Laquey','MO'),('Laramie','WY'),('Larchmont','NY'),('Larchwood','IA'),('Laredo','MO'),('Laredo','TX'),('Lares','PR'),('Largo','FL'),('Larimer','PA'),('Larimore','ND'),('Larkspur','CA'),('Larkspur','CO'),('Larned','KS'),('Larose','LA'),('Larrabee','IA'),('Larsen','WI'),('Larsen Bay','AK'),('Larslan','MT'),('Larue','TX'),('Larwill','IN'),('Las Animas','CO'),('Las Cruces','NM'),('Las Marias','PR'),('Las Piedras','PR'),('Las Vegas','NM'),('Las Vegas','NV'),('Lasara','TX'),('Lascassas','TN'),('Lashmeet','WV'),('Lastrup','MN'),('Latah','WA'),('Latexo','TX'),('Latham','IL'),('Latham','KS'),('Latham','MO'),('Latham','NY'),('Latham','OH'),('Lathrop','CA'),('Lathrop','MO'),('Latimer','IA'),('Laton','CA'),('Latonia','KY'),('Latrobe','PA'),('Latta','SC'),('Lattimer Mines','PA'),('Lattimore','NC'),('Latty','OH'),('Lauderdale','MS'),('Laughlin','NV'),('Laughlin A F B','TX'),('Laughlintown','PA'),('Laupahoehoe','HI'),('Laura','IL'),('Laura','OH'),('Laurel','DE'),('Laurel','FL'),('Laurel','IA'),('Laurel','IN'),('Laurel','MD'),('Laurel','MS'),('Laurel','MT'),('Laurel','NE'),('Laurel','NY'),('Laurel Bloomery','TN'),('Laurel Fork','VA'),('Laurel Hill','FL'),('Laurel Hill','NC'),('Laurel Springs','NC'),('Laurelton','PA'),('Laurelville','OH'),('Laurens','IA'),('Laurens','NY'),('Laurens','SC'),('Laurie','MO'),('Laurier','WA'),('Laurinburg','NC'),('Laurys Station','PA'),('Lava Hot Springs','ID'),('Lavaca','AR'),('Lavalette','WV'),('Lavallette','NJ'),('Laveen','AZ'),('Lavelle','PA'),('Laverne','OK'),('Lavina','MT'),('Lavinia','TN'),('Lavon','TX'),('Lavonia','GA'),('Lawai','HI'),('Lawler','IA'),('Lawley','AL'),('Lawn','PA'),('Lawn','TX'),('Lawndale','CA'),('Lawndale','IL'),('Lawndale','NC'),('Lawnside','NJ'),('Lawrence','KS'),('Lawrence','MA'),('Lawrence','MI'),('Lawrence','MS'),('Lawrence','NE'),('Lawrence','NY'),('Lawrence','PA'),('Lawrenceburg','IN'),('Lawrenceburg','KY'),('Lawrenceburg','TN'),('Lawrenceville','GA'),('Lawrenceville','IL'),('Lawrenceville','NY'),('Lawrenceville','PA'),('Lawrenceville','VA'),('Lawson','AR'),('Lawson','MO'),('Lawsonville','NC'),('Lawtell','LA'),('Lawtey','FL'),('Lawton','IA'),('Lawton','MI'),('Lawton','ND'),('Lawton','OK'),('Lawton','PA'),('Lawtons','NY'),('Layland','WV'),('Layton','NJ'),('Layton','UT'),('Laytonville','CA'),('Lazbuddie','TX'),('Lazear','CO'),('Le Center','MN'),('Le Claire','IA'),('Le Grand','CA'),('Le Grand','IA'),('Le Mars','IA'),('Le Raysville','PA'),('Le Roy','IL'),('Le Roy','KS'),('Le Roy','MN'),('Le Roy','NY'),('Le Roy','WV'),('Le Sueur','MN'),('Leachville','AR'),('Lead','SD'),('Lead Hill','AR'),('Leadore','ID'),('Leadville','CO'),('Leadwood','MO'),('Leaf River','IL'),('League City','TX'),('Leakesville','MS'),('Leakey','TX'),('Leamington','UT'),('Leander','TX'),('Leary','GA'),('Leasburg','MO'),('Leasburg','NC'),('Leavenworth','IN'),('Leavenworth','KS'),('Leavenworth','WA'),('Leavittsburg','OH'),('Leawood','KS'),('Lebam','WA'),('Lebanon','CT'),('Lebanon','GA'),('Lebanon','IL'),('Lebanon','IN'),('Lebanon','KS'),('Lebanon','KY'),('Lebanon','ME'),('Lebanon','MO'),('Lebanon','NE'),('Lebanon','NH'),('Lebanon','NJ'),('Lebanon','OH'),('Lebanon','OK'),('Lebanon','OR'),('Lebanon','PA'),('Lebanon','SD'),('Lebanon','TN'),('Lebanon','VA'),('Lebanon','WI'),('Lebanon Junction','KY'),('Lebeau','LA'),('Lebec','CA'),('Leblanc','LA'),('Lebo','KS'),('Leburn','KY'),('Lecanto','FL'),('Leck Kill','PA'),('Leckrone','PA'),('Lecompte','LA'),('Lecompton','KS'),('Lecontes Mills','PA'),('Ledbetter','KY'),('Ledbetter','TX'),('Lederach','PA'),('Ledger','MT'),('Ledgewood','NJ'),('Ledyard','CT'),('Ledyard','IA'),('Lee','FL'),('Lee','IL'),('Lee','MA'),('Lee','ME'),('Lee','NH'),('Lee Center','IL'),('Lee Center','NY'),('Lee Vining','CA'),('Leechburg','PA'),('Leedey','OK'),('Leeds','AL'),('Leeds','MA'),('Leeds','ME'),('Leeds','ND'),('Leeds','NY'),('Leeds','UT'),('Leeds Point','NJ'),('Leeper','PA'),('Lees Creek','OH'),('Lees Summit','MO'),('Leesburg','AL'),('Leesburg','FL'),('Leesburg','GA'),('Leesburg','IN'),('Leesburg','NJ'),('Leesburg','OH'),('Leesburg','TX'),('Leesburg','VA'),('Leesport','PA'),('Leesville','LA'),('Leesville','OH'),('Leesville','SC'),('Leesville','TX'),('Leeton','MO'),('Leetonia','OH'),('Leetsdale','PA'),('Leflore','OK'),('Lefor','ND'),('Lefors','TX'),('Left Hand','WV'),('Leggett','CA'),('Leggett','TX'),('Lehi','UT'),('Lehigh','IA'),('Lehigh','KS'),('Lehigh','OK'),('Lehigh Acres','FL'),('Lehigh Valley','PA'),('Lehighton','PA'),('Lehman','PA'),('Lehr','ND'),('Leicester','MA'),('Leicester','NC'),('Leicester','NY'),('Leigh','NE'),('Leighton','AL'),('Leighton','IA'),('Leipsic','OH'),('Leisenring','PA'),('Leitchfield','KY'),('Leiter','WY'),('Leiters Ford','IN'),('Leivasy','WV'),('Lejunior','KY'),('Leland','IA'),('Leland','IL'),('Leland','MI'),('Leland','MS'),('Leland','NC'),('Lelia Lake','TX'),('Lemasters','PA'),('Lemhi','ID'),('Leming','TX'),('Lemitar','NM'),('Lemmon','SD'),('Lemon Cove','CA'),('Lemon Grove','CA'),('Lemon Springs','NC'),('Lemont','IL'),('Lemont','PA'),('Lemont Furnace','PA'),('Lemoore','CA'),('Lemoyne','NE'),('Lemoyne','OH'),('Lemoyne','PA'),('Lempster','NH'),('Lena','IL'),('Lena','LA'),('Lena','MS'),('Lena','WI'),('Lenapah','OK'),('Lenexa','KS'),('Lengby','MN'),('Lenhartsville','PA'),('Lenni','PA'),('Lennon','MI'),('Lennox','SD'),('Lenoir','NC'),('Lenoir City','TN'),('Lenora','KS'),('Lenorah','TX'),('Lenore','ID'),('Lenore','WV'),('Lenox','AL'),('Lenox','GA'),('Lenox','IA'),('Lenox','MA'),('Lenox','MO'),('Lenox','TN'),('Lenox Dale','MA'),('Lenoxville','PA'),('Lentner','MO'),('Lenzburg','IL'),('Leo','IN'),('Leola','AR'),('Leola','PA'),('Leola','SD'),('Leoma','TN'),('Leominster','MA'),('Leon','IA'),('Leon','KS'),('Leon','NY'),('Leon','OK'),('Leon','VA'),('Leon','WV'),('Leona','TX'),('Leonard','MI'),('Leonard','MN'),('Leonard','MO'),('Leonard','ND'),('Leonard','OK'),('Leonard','TX'),('Leonardo','NJ'),('Leonardsville','NY'),('Leonardtown','MD'),('Leonardville','KS'),('Leonia','NJ'),('Leonidas','MI'),('Leonore','IL'),('Leonville','LA'),('Leopold','IN'),('Leopold','MO'),('Leopolis','WI'),('Leota','MN'),('Leoti','KS'),('Lepanto','AR'),('Lequire','OK'),('Lerna','IL'),('Lerona','WV'),('Leroy','AL'),('Leroy','IN'),('Leroy','MI'),('Leroy','TX'),('Lesage','WV'),('Leslie','AR'),('Leslie','GA'),('Leslie','MI'),('Leslie','MO'),('Leslie','WV'),('Lester','AL'),('Lester','IA'),('Lester','WV'),('Lester Prairie','MN'),('Lesterville','MO'),('Lesterville','SD'),('Letart','WV'),('Letcher','KY'),('Letcher','SD'),('Letha','ID'),('Letohatchee','AL'),('Letona','AR'),('Letts','IA'),('Lettsworth','LA'),('Leupp','AZ'),('Levan','UT'),('Levant','KS'),('Levant','ME'),('Levasy','MO'),('Levelland','TX'),('Levelock','AK'),('Levels','WV'),('Leverett','MA'),('Levering','MI'),('Levittown','NY'),('Levittown','PA'),('Lewellen','NE'),('Lewes','DE'),('Lewis','CO'),('Lewis','IA'),('Lewis','IN'),('Lewis','KS'),('Lewis','NY'),('Lewis Center','OH'),('Lewis Run','PA'),('Lewisberry','PA'),('Lewisburg','KY'),('Lewisburg','OH'),('Lewisburg','PA'),('Lewisburg','TN'),('Lewisburg','WV'),('Lewisport','KY'),('Lewiston','CA'),('Lewiston','ID'),('Lewiston','ME'),('Lewiston','MI'),('Lewiston','MN'),('Lewiston','NE'),('Lewiston','NY'),('Lewiston','UT'),('Lewiston Woodville','NC'),('Lewistown','IL'),('Lewistown','MO'),('Lewistown','MT'),('Lewistown','OH'),('Lewistown','PA'),('Lewisville','AR'),('Lewisville','ID'),('Lewisville','IN'),('Lewisville','MN'),('Lewisville','NC'),('Lewisville','OH'),('Lewisville','PA'),('Lewisville','TX'),('Lexa','AR'),('Lexington','AL'),('Lexington','GA'),('Lexington','IL'),('Lexington','IN'),('Lexington','KY'),('Lexington','MA'),('Lexington','MI'),('Lexington','MO'),('Lexington','MS'),('Lexington','NC'),('Lexington','NE'),('Lexington','NY'),('Lexington','OK'),('Lexington','OR'),('Lexington','SC'),('Lexington','TN'),('Lexington','TX'),('Lexington','VA'),('Lexington Park','MD'),('Libby','MT'),('Liberal','KS'),('Liberal','MO'),('Liberty','IL'),('Liberty','IN'),('Liberty','KS'),('Liberty','KY'),('Liberty','ME'),('Liberty','MO'),('Liberty','MS'),('Liberty','NC'),('Liberty','NE'),('Liberty','NY'),('Liberty','PA'),('Liberty','SC'),('Liberty','TN'),('Liberty','TX'),('Liberty','WV'),('Liberty Center','IA'); + +INSERT INTO [cities] VALUES ('Liberty Center','IN'),('Liberty Center','OH'),('Liberty Corner','NJ'),('Liberty Hill','SC'),('Liberty Hill','TX'),('Liberty Lake','WA'),('Liberty Mills','IN'),('Libertytown','MD'),('Libertyville','IA'),('Libertyville','IL'),('Libuse','LA'),('Lick Creek','KY'),('Licking','MO'),('Lickingville','PA'),('Lidderdale','IA'),('Lidgerwood','ND'),('Liebenthal','KS'),('Light','AR'),('Lightfoot','VA'),('Lighthouse Point','FL'),('Lightstreet','PA'),('Lignite','ND'),('Lignum','VA'),('Ligonier','IN'),('Ligonier','PA'),('Liguori','MO'),('Lihue','HI'),('Likely','CA'),('Lilbourn','MO'),('Lilburn','GA'),('Lilesville','NC'),('Lillian','AL'),('Lillian','TX'),('Lillie','LA'),('Lillington','NC'),('Lilliwaup','WA'),('Lilly','GA'),('Lilly','PA'),('Lily','KY'),('Lily Dale','NY'),('Lima','IL'),('Lima','MT'),('Lima','NY'),('Lima','OH'),('Limaville','OH'),('Lime Ridge','WI'),('Lime Springs','IA'),('Limekiln','PA'),('Limeport','PA'),('Limerick','ME'),('Limerick','NY'),('Limestone','ME'),('Limestone','NY'),('Limestone','PA'),('Limestone','TN'),('Limington','ME'),('Limon','CO'),('Linch','WY'),('Lincoln','AL'),('Lincoln','AR'),('Lincoln','CA'),('Lincoln','DE'),('Lincoln','IA'),('Lincoln','IL'),('Lincoln','KS'),('Lincoln','MA'),('Lincoln','ME'),('Lincoln','MI'),('Lincoln','MO'),('Lincoln','MT'),('Lincoln','NE'),('Lincoln','NH'),('Lincoln','NM'),('Lincoln','RI'),('Lincoln','TX'),('Lincoln','VA'),('Lincoln','WA'),('Lincoln Acres','CA'),('Lincoln City','IN'),('Lincoln City','OR'),('Lincoln Park','MI'),('Lincoln Park','NJ'),('Lincoln University','PA'),('Lincolndale','NY'),('Lincolns New Salem','IL'),('Lincolnshire','IL'),('Lincolnton','GA'),('Lincolnton','NC'),('Lincolnville','KS'),('Lincolnville','ME'),('Lincolnville Center','ME'),('Lincolnwood','IL'),('Lincroft','NJ'),('Lind','WA'),('Lindale','GA'),('Lindale','TX'),('Linden','AL'),('Linden','CA'),('Linden','IA'),('Linden','IN'),('Linden','MI'),('Linden','NC'),('Linden','NJ'),('Linden','PA'),('Linden','TN'),('Linden','TX'),('Linden','VA'),('Linden','WI'),('Lindenhurst','NY'),('Lindenwood','IL'),('Lindley','NY'),('Lindon','CO'),('Lindon','UT'),('Lindrith','NM'),('Lindsay','CA'),('Lindsay','MT'),('Lindsay','NE'),('Lindsay','OK'),('Lindsay','TX'),('Lindsborg','KS'),('Lindsey','OH'),('Lindside','WV'),('Lindstrom','MN'),('Line Lexington','PA'),('Lineboro','MD'),('Linefork','KY'),('Linesville','PA'),('Lineville','AL'),('Lineville','IA'),('Lingle','WY'),('Lingleville','TX'),('Lingo','NM'),('Linkwood','MD'),('Linn','KS'),('Linn','MO'),('Linn','TX'),('Linn','WV'),('Linn Creek','MO'),('Linn Grove','IA'),('Linneus','MO'),('Linthicum Heights','MD'),('Linton','IN'),('Linton','ND'),('Linville','NC'),('Linville','VA'),('Linville Falls','NC'),('Linwood','KS'),('Linwood','MA'),('Linwood','MI'),('Linwood','NC'),('Linwood','NE'),('Linwood','NJ'),('Linwood','NY'),('Lionville','PA'),('Lipan','TX'),('Lipscomb','TX'),('Lisbon','IA'),('Lisbon','LA'),('Lisbon','MD'),('Lisbon','ME'),('Lisbon','ND'),('Lisbon','NH'),('Lisbon','NY'),('Lisbon','OH'),('Lisbon Falls','ME'),('Lisco','NE'),('Liscomb','IA'),('Lisle','IL'),('Lisle','NY'),('Lisman','AL'),('Lismore','MN'),('Lissie','TX'),('Listie','PA'),('Litchfield','CA'),('Litchfield','CT'),('Litchfield','IL'),('Litchfield','ME'),('Litchfield','MI'),('Litchfield','MN'),('Litchfield','NE'),('Litchfield','NH'),('Litchfield','OH'),('Litchfield Park','AZ'),('Litchville','ND'),('Literberry','IL'),('Lithia','FL'),('Lithia Springs','GA'),('Lithonia','GA'),('Lithopolis','OH'),('Lititz','PA'),('Littcarr','KY'),('Little America','WY'),('Little Birch','WV'),('Little Cedar','IA'),('Little Chute','WI'),('Little Compton','RI'),('Little Creek','DE'),('Little Deer Isle','ME'),('Little Eagle','SD'),('Little Egg Harbor Twp','NJ'),('Little Elm','TX'),('Little Falls','MN'),('Little Falls','NJ'),('Little Falls','NY'),('Little Ferry','NJ'),('Little Genesee','NY'),('Little Hocking','OH'),('Little Lake','CA'),('Little Lake','MI'),('Little Meadows','PA'),('Little Mountain','SC'),('Little Neck','NY'),('Little Orleans','MD'),('Little Plymouth','VA'),('Little River','AL'),('Little River','CA'),('Little River','KS'),('Little River','SC'),('Little River Academy','TX'),('Little Rock','AR'),('Little Rock','IA'),('Little Rock','MS'),('Little Rock','SC'),('Little Rock Air Force Base','AR'),('Little Silver','NJ'),('Little Sioux','IA'),('Little Suamico','WI'),('Little Switzerland','NC'),('Little Valley','NY'),('Little York','IL'),('Little York','IN'),('Little York','NJ'),('Little York','NY'),('Littlefield','AZ'),('Littlefield','TX'),('Littlefork','MN'),('Littlerock','CA'),('Littlerock','WA'),('Littlestown','PA'),('Littleton','CO'),('Littleton','IL'),('Littleton','MA'),('Littleton','NC'),('Littleton','NH'),('Littleton','WV'),('Live Oak','CA'),('Live Oak','FL'),('Lively','VA'),('Livermore','CA'),('Livermore','CO'),('Livermore','IA'),('Livermore','KY'),('Livermore','ME'),('Livermore Falls','ME'),('Liverpool','IL'),('Liverpool','NY'),('Liverpool','PA'),('Liverpool','TX'),('Livingston','AL'),('Livingston','CA'),('Livingston','IL'),('Livingston','KY'),('Livingston','LA'),('Livingston','MT'),('Livingston','NJ'),('Livingston','NY'),('Livingston','TN'),('Livingston','TX'),('Livingston','WI'),('Livingston Manor','NY'),('Livonia','LA'),('Livonia','MI'),('Livonia','MO'),('Livonia','NY'),('Livonia Center','NY'),('Lizella','GA'),('Lizemores','WV'),('Lizton','IN'),('Llano','CA'),('Llano','NM'),('Llano','TX'),('Llewellyn','PA'),('Lloyd','FL'),('Lloyd','MT'),('Loa','UT'),('Loachapoka','AL'),('Loami','IL'),('Lobeco','SC'),('Lobelville','TN'),('Loch Sheldrake','NY'),('Lochgelly','WV'),('Lochloosa','FL'),('Lochmere','NH'),('Lock Haven','PA'),('Lock Springs','MO'),('Lockbourne','OH'),('Locke','NY'),('Lockeford','CA'),('Lockesburg','AR'),('Lockhart','AL'),('Lockhart','SC'),('Lockhart','TX'),('Lockney','TX'),('Lockport','IL'),('Lockport','KY'),('Lockport','LA'),('Lockport','NY'),('Lockridge','IA'),('Lockwood','CA'),('Lockwood','MO'),('Lockwood','NY'),('Loco','OK'),('Loco Hills','NM'),('Locust','NC'),('Locust Dale','VA'),('Locust Fork','AL'),('Locust Gap','PA'),('Locust Grove','AR'),('Locust Grove','GA'),('Locust Grove','OK'),('Locust Grove','VA'),('Locust Hill','VA'),('Locust Valley','NY'),('Locustdale','PA'),('Locustville','VA'),('Loda','IL'),('Lodge','SC'),('Lodge Grass','MT'),('Lodgepole','NE'),('Lodgepole','SD'),('Lodi','CA'),('Lodi','MO'),('Lodi','NJ'),('Lodi','NY'),('Lodi','OH'),('Lodi','TX'),('Lodi','WI'),('Log Lane Village','CO'),('Logan','AL'),('Logan','IA'),('Logan','IL'),('Logan','KS'),('Logan','NM'),('Logan','OH'),('Logan','UT'),('Logan','WV'),('Logandale','NV'),('Logansport','IN'),('Logansport','LA'),('Loganton','PA'),('Loganville','GA'),('Loganville','PA'),('Loganville','WI'),('Logsden','OR'),('Lohman','MO'),('Lohn','TX'),('Lohrville','IA'),('Loiza','PR'),('Loleta','CA'),('Lolita','TX'),('Lolo','MT'),('Loma','CO'),('Loma','MT'),('Loma Linda','CA'),('Loma Mar','CA'),('Loman','MN'),('Lomax','IL'),('Lombard','IL'),('Lometa','TX'),('Lomira','WI'),('Lomita','CA'),('Lompoc','CA'),('Lonaconing','MD'),('London','AR'),('London','KY'),('London','OH'),('London','TX'),('London','WV'),('London Mills','IL'),('Londonderry','NH'),('Londonderry','OH'),('Londonderry','VT'),('Lone','KY'),('Lone Grove','OK'),('Lone Jack','MO'),('Lone Mountain','TN'),('Lone Oak','TX'),('Lone Pine','CA'),('Lone Rock','IA'),('Lone Rock','WI'),('Lone Star','TX'),('Lone Tree','IA'),('Lone Wolf','OK'),('Lonedell','MO'),('Lonepine','MT'),('Lonetree','WY'),('Long Barn','CA'),('Long Beach','CA'),('Long Beach','MS'),('Long Beach','NY'),('Long Beach','WA'),('Long Bottom','OH'),('Long Branch','NJ'),('Long Branch','TX'),('Long Creek','OR'),('Long Creek','SC'),('Long Eddy','NY'),('Long Green','MD'),('Long Grove','IA'),('Long Grove','IL'),('Long Island','KS'),('Long Island','ME'),('Long Island','VA'),('Long Island City','NY'),('Long Key','FL'),('Long Lake','MI'),('Long Lake','MN'),('Long Lake','NY'),('Long Lake','WI'),('Long Lane','MO'),('Long Pine','NE'),('Long Point','IL'),('Long Pond','PA'),('Long Prairie','MN'),('Long Valley','NJ'),('Long Valley','SD'),('Longboat Key','FL'),('Longbranch','WA'),('Longdale','OK'),('Longford','KS'),('Longlake','SD'),('Longleaf','LA'),('Longmeadow','MA'),('Longmire','WA'),('Longmont','CO'),('Longport','NJ'),('Longs','SC'),('Longstreet','LA'),('Longton','KS'),('Longview','IL'),('Longview','TX'),('Longview','WA'),('Longville','LA'),('Longville','MN'),('Longwood','FL'),('Longwood','NC'),('Lonoke','AR'),('Lonsdale','AR'),('Lonsdale','MN'),('Loogootee','IL'),('Loogootee','IN'),('Lookeba','OK'),('Lookout','CA'),('Lookout','KY'),('Lookout','WV'),('Lookout Mountain','GA'),('Lookout Mountain','TN'),('Loomis','CA'),('Loomis','NE'),('Loomis','WA'),('Loon Lake','WA'),('Looneyville','WV'),('Loop','TX'),('Loose Creek','MO'),('Lopeno','TX'),('Lopez','PA'),('Lopez Island','WA'),('Lorado','WV'),('Lorain','OH'),('Loraine','IL'),('Loraine','TX'),('Lorane','OR'),('Loranger','LA'),('Lordsburg','NM'),('Lore City','OH'),('Loreauville','LA'),('Lorena','TX'),('Lorentz','WV'),('Lorenzo','TX'),('Loretto','KY'),('Loretto','MI'),('Loretto','MN'),('Loretto','PA'),('Loretto','TN'),('Loretto','VA'),('Lorida','FL'),('Lorimor','IA'),('Loring','MT'),('Loris','SC'),('Lorman','MS'),('Lorraine','KS'),('Lorraine','NY'),('Lorton','NE'),('Lorton','VA'),('Los Alamitos','CA'),('Los Alamos','CA'),('Los Alamos','NM'),('Los Altos','CA'),('Los Angeles','CA'),('Los Banos','CA'); + +INSERT INTO [cities] VALUES ('Los Ebanos','TX'),('Los Fresnos','TX'),('Los Gatos','CA'),('Los Indios','TX'),('Los Lunas','NM'),('Los Molinos','CA'),('Los Ojos','NM'),('Los Olivos','CA'),('Los Osos','CA'),('Losantville','IN'),('Lost City','WV'),('Lost Creek','KY'),('Lost Creek','PA'),('Lost Creek','WV'),('Lost Hills','CA'),('Lost Nation','IA'),('Lost Springs','KS'),('Lost Springs','WY'),('Lostant','IL'),('Lostine','OR'),('Lothair','MT'),('Lothian','MD'),('Lott','TX'),('Lottie','LA'),('Lottsburg','VA'),('Lotus','CA'),('Louann','AR'),('Loudon','NH'),('Loudon','TN'),('Loudonville','OH'),('Loughman','FL'),('Louin','MS'),('Louisa','KY'),('Louisa','VA'),('Louisburg','KS'),('Louisburg','MO'),('Louisburg','NC'),('Louise','MS'),('Louise','TX'),('Louisiana','MO'),('Louisville','AL'),('Louisville','CO'),('Louisville','GA'),('Louisville','IL'),('Louisville','KY'),('Louisville','MS'),('Louisville','NE'),('Louisville','OH'),('Louisville','TN'),('Loup City','NE'),('Louvale','GA'),('Louviers','CO'),('Lovejoy','GA'),('Lovejoy','IL'),('Lovelaceville','KY'),('Lovelady','TX'),('Loveland','CO'),('Loveland','OH'),('Loveland','OK'),('Lovell','ME'),('Lovell','WY'),('Lovelock','NV'),('Lovely','KY'),('Loves Park','IL'),('Lovettsville','VA'),('Loveville','MD'),('Lovilia','IA'),('Loving','NM'),('Loving','TX'),('Lovingston','VA'),('Lovington','IL'),('Lovington','NM'),('Low Moor','IA'),('Low Moor','VA'),('Lowake','TX'),('Lowber','PA'),('Lowden','IA'),('Lowder','IL'),('Lowell','AR'),('Lowell','FL'),('Lowell','IN'),('Lowell','MA'),('Lowell','MI'),('Lowell','NC'),('Lowell','OH'),('Lowell','OR'),('Lowell','VT'),('Lowell','WI'),('Lowellville','OH'),('Lower Brule','SD'),('Lower Kalskag','AK'),('Lower Lake','CA'),('Lower Peach Tree','AL'),('Lower Salem','OH'),('Lower Waterford','VT'),('Lowes','KY'),('Lowgap','NC'),('Lowland','NC'),('Lowland','TN'),('Lowman','ID'),('Lowman','NY'),('Lowmansville','KY'),('Lowndes','MO'),('Lowndesboro','AL'),('Lowndesville','SC'),('Lowpoint','IL'),('Lowry','MN'),('Lowry','VA'),('Lowry City','MO'),('Lowville','NY'),('Loxahatchee','FL'),('Loxley','AL'),('Loyal','OK'),('Loyal','WI'),('Loyalhanna','PA'),('Loyall','KY'),('Loyalton','CA'),('Loysburg','PA'),('Loysville','PA'),('Lozano','TX'),('Lu Verne','IA'),('Luana','IA'),('Lubbock','TX'),('Lubec','ME'),('Lublin','WI'),('Lucama','NC'),('Lucan','MN'),('Lucas','IA'),('Lucas','KS'),('Lucas','KY'),('Lucas','OH'),('Lucasville','OH'),('Lucedale','MS'),('Lucerne','CA'),('Lucerne','CO'),('Lucerne','IN'),('Lucerne','MO'),('Lucerne Valley','CA'),('Lucernemines','PA'),('Lucien','OK'),('Lucile','ID'),('Lucinda','PA'),('Luck','WI'),('Luckey','OH'),('Ludell','KS'),('Ludington','MI'),('Ludlow','CA'),('Ludlow','IL'),('Ludlow','MA'),('Ludlow','MO'),('Ludlow','MS'),('Ludlow','PA'),('Ludlow','SD'),('Ludlow','VT'),('Ludlow Falls','OH'),('Ludowici','GA'),('Luebbering','MO'),('Lueders','TX'),('Lufkin','TX'),('Lugoff','SC'),('Lukachukai','AZ'),('Luke','MD'),('Lukeville','AZ'),('Lula','GA'),('Lula','MS'),('Luling','LA'),('Luling','TX'),('Lulu','FL'),('Lumber Bridge','NC'),('Lumber City','GA'),('Lumberport','WV'),('Lumberton','MS'),('Lumberton','NC'),('Lumberton','NJ'),('Lumberton','TX'),('Lumberville','PA'),('Lummi Island','WA'),('Lumpkin','GA'),('Luna','NM'),('Luna Pier','MI'),('Lund','NV'),('Lunenburg','MA'),('Lunenburg','VA'),('Lunenburg','VT'),('Luning','NV'),('Lupton','AZ'),('Lupton','MI'),('Lupton City','TN'),('Luquillo','PR'),('Luray','KS'),('Luray','MO'),('Luray','SC'),('Luray','TN'),('Luray','VA'),('Lurgan','PA'),('Lusby','MD'),('Lusk','WY'),('Lutcher','LA'),('Luther','IA'),('Luther','MI'),('Luther','OK'),('Luthersburg','PA'),('Luthersville','GA'),('Lutherville Timonium','MD'),('Lutsen','MN'),('Luttrell','TN'),('Lutts','TN'),('Lutz','FL'),('Luverne','AL'),('Luverne','MN'),('Luverne','ND'),('Luxemburg','IA'),('Luxemburg','WI'),('Luxor','PA'),('Luxora','AR'),('Luzerne','IA'),('Luzerne','MI'),('Luzerne','PA'),('Lyburn','WV'),('Lycoming','NY'),('Lydia','LA'),('Lydia','SC'),('Lyerly','GA'),('Lyford','TX'),('Lykens','PA'),('Lyle','MN'),('Lyle','WA'),('Lyles','TN'),('Lyman','NE'),('Lyman','SC'),('Lyman','UT'),('Lyman','WA'),('Lyman','WY'),('Lyme','NH'),('Lyme Center','NH'),('Lynbrook','NY'),('Lynch','KY'),('Lynch','NE'),('Lynch Station','VA'),('Lynchburg','MO'),('Lynchburg','OH'),('Lynchburg','SC'),('Lynchburg','TN'),('Lynchburg','VA'),('Lynco','WV'),('Lynd','MN'),('Lyndeborough','NH'),('Lyndell','PA'),('Lynden','WA'),('Lyndhurst','NJ'),('Lyndhurst','VA'),('Lyndon','IL'),('Lyndon','KS'),('Lyndon','VT'),('Lyndon Center','VT'),('Lyndon Station','WI'),('Lyndonville','NY'),('Lyndonville','VT'),('Lyndora','PA'),('Lynn','AL'),('Lynn','AR'),('Lynn','IN'),('Lynn','MA'),('Lynn','NC'),('Lynn Center','IL'),('Lynn Haven','FL'),('Lynndyl','UT'),('Lynnfield','MA'),('Lynnville','IA'),('Lynnville','IN'),('Lynnville','KY'),('Lynnville','TN'),('Lynnwood','WA'),('Lynwood','CA'),('Lynx','OH'),('Lynxville','WI'),('Lyon','MS'),('Lyon Mountain','NY'),('Lyon Station','PA'),('Lyons','CO'),('Lyons','GA'),('Lyons','IL'),('Lyons','IN'),('Lyons','KS'),('Lyons','MI'),('Lyons','NE'),('Lyons','NJ'),('Lyons','NY'),('Lyons','OH'),('Lyons','OR'),('Lyons','SD'),('Lyons','TX'),('Lyons','WI'),('Lyons Falls','NY'),('Lysite','WY'),('Lytle','TX'),('Lytle Creek','CA'),('Lytton','IA'),('M C B H Kaneohe Bay','HI'),('Mabank','TX'),('Mabel','MN'),('Mabelvale','AR'),('Maben','MS'),('Maben','WV'),('Mabie','WV'),('Mableton','GA'),('Mabscott','WV'),('Mabton','WA'),('Mac Arthur','WV'),('Macatawa','MI'),('Macclenny','FL'),('Macclesfield','NC'),('Macdoel','CA'),('Macdona','TX'),('Macedon','NY'),('Macedonia','IA'),('Macedonia','IL'),('Macedonia','OH'),('Maceo','KY'),('Macfarlan','WV'),('Machesney Park','IL'),('Machias','ME'),('Machias','NY'),('Machiasport','ME'),('Machipongo','VA'),('Mack','CO'),('Mackay','ID'),('Mackey','IN'),('Mackeyville','PA'),('Mackinac Island','MI'),('Mackinaw','IL'),('Mackinaw City','MI'),('Macks Creek','MO'),('Macks Inn','ID'),('Macksburg','IA'),('Macksburg','OH'),('Macksville','KS'),('Mackville','KY'),('Macomb','IL'),('Macomb','MI'),('Macomb','MO'),('Macomb','OK'),('Macon','GA'),('Macon','IL'),('Macon','MO'),('Macon','MS'),('Macon','NC'),('Macon','TN'),('Macon','VA'),('Macungie','PA'),('Macy','IN'),('Macy','NE'),('Mad River','CA'),('Madawaska','ME'),('Madbury','NH'),('Madden','MS'),('Maddock','ND'),('Madelia','MN'),('Madeline','CA'),('Madera','CA'),('Madera','PA'),('Madill','OK'),('Madison','AL'),('Madison','AR'),('Madison','CA'),('Madison','CT'),('Madison','FL'),('Madison','GA'),('Madison','IL'),('Madison','IN'),('Madison','KS'),('Madison','MD'),('Madison','ME'),('Madison','MN'),('Madison','MO'),('Madison','MS'),('Madison','NC'),('Madison','NE'),('Madison','NH'),('Madison','NJ'),('Madison','NY'),('Madison','OH'),('Madison','PA'),('Madison','SD'),('Madison','TN'),('Madison','VA'),('Madison','WI'),('Madison','WV'),('Madison Heights','MI'),('Madison Heights','VA'),('Madison Lake','MN'),('Madisonburg','PA'),('Madisonville','KY'),('Madisonville','LA'),('Madisonville','TN'),('Madisonville','TX'),('Madras','OR'),('Madrid','IA'),('Madrid','NE'),('Madrid','NY'),('Maeystown','IL'),('Magalia','CA'),('Magazine','AR'),('Magdalena','NM'),('Magee','MS'),('Maggie Valley','NC'),('Magna','UT'),('Magness','AR'),('Magnet','NE'),('Magnetic Springs','OH'),('Magnolia','AL'),('Magnolia','AR'),('Magnolia','DE'),('Magnolia','IA'),('Magnolia','IL'),('Magnolia','KY'),('Magnolia','MN'),('Magnolia','MS'),('Magnolia','NC'),('Magnolia','NJ'),('Magnolia','OH'),('Magnolia','TX'),('Magnolia Springs','AL'),('Mahanoy City','PA'),('Mahanoy Plane','PA'),('Mahaska','KS'),('Mahnomen','MN'),('Mahomet','IL'),('Mahopac','NY'),('Mahopac Falls','NY'),('Mahwah','NJ'),('Maida','ND'),('Maiden','NC'),('Maiden Rock','WI'),('Maidens','VA'),('Maidsville','WV'),('Maine','NY'),('Mainesburg','PA'),('Maineville','OH'),('Mainland','PA'),('Maitland','FL'),('Maitland','MO'),('Maize','KS'),('Majestic','KY'),('Makanda','IL'),('Makawao','HI'),('Makaweli','HI'),('Makinen','MN'),('Makoti','ND'),('Malabar','FL'),('Malad City','ID'),('Malaga','NJ'),('Malaga','NM'),('Malaga','OH'),('Malaga','WA'),('Malakoff','TX'),('Malcolm','AL'),('Malcolm','NE'),('Malcom','IA'),('Malden','IL'),('Malden','MA'),('Malden','MO'),('Malden','WA'),('Malden Bridge','NY'),('Malden On Hudson','NY'),('Malibu','CA'),('Malin','OR'),('Malinta','OH'),('Maljamar','NM'),('Mallard','IA'),('Mallie','KY'),('Mallory','NY'),('Mallory','WV'),('Malmo','NE'),('Malmstrom A F B','MT'),('Malo','WA'),('Malone','FL'),('Malone','KY'),('Malone','NY'),('Malone','TX'),('Malone','WA'),('Malone','WI'),('Malott','WA'),('Malta','ID'),('Malta','IL'),('Malta','MT'),('Malta','OH'),('Malta Bend','MO'),('Malvern','AL'),('Malvern','AR'),('Malvern','IA'),('Malvern','OH'),('Malvern','PA'),('Malverne','NY'),('Mamaroneck','NY'),('Mamers','NC'),('Mammoth','AZ'),('Mammoth','PA'),('Mammoth','WV'),('Mammoth Cave','KY'); + +INSERT INTO [cities] VALUES ('Mammoth Lakes','CA'),('Mammoth Spring','AR'),('Mamou','LA'),('Man','WV'),('Manahawkin','NJ'),('Manakin Sabot','VA'),('Manasquan','NJ'),('Manassa','CO'),('Manassas','GA'),('Manassas','VA'),('Manati','PR'),('Manawa','WI'),('Mancelona','MI'),('Manchaca','TX'),('Manchaug','MA'),('Manchester','CA'),('Manchester','CT'),('Manchester','GA'),('Manchester','IA'),('Manchester','IL'),('Manchester','KY'),('Manchester','MA'),('Manchester','MD'),('Manchester','ME'),('Manchester','MI'),('Manchester','NH'),('Manchester','NY'),('Manchester','OH'),('Manchester','OK'),('Manchester','PA'),('Manchester','TN'),('Manchester','VT'),('Manchester','WA'),('Manchester Center','VT'),('Manchester Township','NJ'),('Mancos','CO'),('Mandan','ND'),('Mandaree','ND'),('Manderson','SD'),('Manderson','WY'),('Mandeville','LA'),('Mangham','LA'),('Mango','FL'),('Mangum','OK'),('Manhasset','NY'),('Manhattan','IL'),('Manhattan','KS'),('Manhattan','MT'),('Manhattan','NV'),('Manhattan Beach','CA'),('Manheim','PA'),('Manila','AR'),('Manila','UT'),('Manilla','IA'),('Manilla','IN'),('Manistee','MI'),('Manistique','MI'),('Manito','IL'),('Manitou','KY'),('Manitou','OK'),('Manitou Beach','MI'),('Manitou Springs','CO'),('Manitowish Waters','WI'),('Manitowoc','WI'),('Mankato','KS'),('Mankato','MN'),('Manley','NE'),('Manley Hot Springs','AK'),('Manlius','IL'),('Manlius','NY'),('Manly','IA'),('Mannboro','VA'),('Mannford','OK'),('Manning','IA'),('Manning','ND'),('Manning','OR'),('Manning','SC'),('Mannington','WV'),('Manns Choice','PA'),('Manns Harbor','NC'),('Mannsville','KY'),('Mannsville','NY'),('Mannsville','OK'),('Manokin','MD'),('Manokotak','AK'),('Manomet','MA'),('Manor','GA'),('Manor','PA'),('Manor','TX'),('Manorville','NY'),('Manorville','PA'),('Manquin','VA'),('Mansfield','AR'),('Mansfield','GA'),('Mansfield','IL'),('Mansfield','LA'),('Mansfield','MA'),('Mansfield','MO'),('Mansfield','OH'),('Mansfield','PA'),('Mansfield','SD'),('Mansfield','TN'),('Mansfield','TX'),('Mansfield','WA'),('Mansfield Center','CT'),('Mansfield Depot','CT'),('Manson','IA'),('Manson','NC'),('Manson','WA'),('Mansura','LA'),('Mantachie','MS'),('Mantador','ND'),('Manteca','CA'),('Mantee','MS'),('Manteno','IL'),('Manteo','NC'),('Manter','KS'),('Manti','UT'),('Mantoloking','NJ'),('Manton','CA'),('Manton','MI'),('Mantorville','MN'),('Mantua','NJ'),('Mantua','OH'),('Mantua','UT'),('Manvel','ND'),('Manvel','TX'),('Manville','NJ'),('Manville','RI'),('Manville','WY'),('Many','LA'),('Many Farms','AZ'),('Manzanita','OR'),('Manzanola','CO'),('Mapaville','MO'),('Maple','NC'),('Maple','TX'),('Maple','WI'),('Maple City','KS'),('Maple City','MI'),('Maple Falls','WA'),('Maple Heights','OH'),('Maple Hill','KS'),('Maple Hill','NC'),('Maple Lake','MN'),('Maple Mount','KY'),('Maple Park','IL'),('Maple Plain','MN'),('Maple Rapids','MI'),('Maple Shade','NJ'),('Maple Springs','NY'),('Maple Valley','WA'),('Maple View','NY'),('Maplecrest','NY'),('Maplesville','AL'),('Mapleton','IA'),('Mapleton','IL'),('Mapleton','KS'),('Mapleton','ME'),('Mapleton','MN'),('Mapleton','ND'),('Mapleton','OR'),('Mapleton','UT'),('Mapleton Depot','PA'),('Mapleville','RI'),('Maplewood','NJ'),('Maplewood','OH'),('Maplewood','WI'),('Mappsville','VA'),('Maquoketa','IA'),('Maquon','IL'),('Mar Lin','PA'),('Maramec','OK'),('Marana','AZ'),('Marathon','FL'),('Marathon','IA'),('Marathon','NY'),('Marathon','OH'),('Marathon','TX'),('Marathon','WI'),('Marathon Shores','FL'),('Marble','MN'),('Marble','NC'),('Marble','PA'),('Marble Canyon','AZ'),('Marble City','OK'),('Marble Falls','AR'),('Marble Falls','TX'),('Marble Hill','GA'),('Marble Hill','MO'),('Marble Rock','IA'),('Marblehead','MA'),('Marblemount','WA'),('Marbury','AL'),('Marbury','MD'),('Marceline','MO'),('Marcell','MN'),('Marcella','AR'),('Marcellus','MI'),('Marcellus','NY'),('March Air Reserve Base','CA'),('Marchand','PA'),('Marco Island','FL'),('Marcola','OR'),('Marcus','IA'),('Marcus','WA'),('Marcus Hook','PA'),('Marcy','NY'),('Mardela Springs','MD'),('Marengo','IA'),('Marengo','IL'),('Marengo','IN'),('Marengo','OH'),('Marengo','WI'),('Marenisco','MI'),('Marfa','TX'),('Margaret','AL'),('Margarettsville','NC'),('Margaretville','NY'),('Margate City','NJ'),('Margie','MN'),('Maria Stein','OH'),('Mariah Hill','IN'),('Marianna','AR'),('Marianna','FL'),('Marianna','PA'),('Marianna','WV'),('Maribel','WI'),('Maricao','PR'),('Maricopa','AZ'),('Maricopa','CA'),('Marienthal','KS'),('Marienville','PA'),('Marietta','GA'),('Marietta','IL'),('Marietta','MN'),('Marietta','MS'),('Marietta','NC'),('Marietta','NY'),('Marietta','OH'),('Marietta','OK'),('Marietta','PA'),('Marietta','SC'),('Marietta','TX'),('Marilla','NY'),('Marina','CA'),('Marina Del Rey','CA'),('Marine','IL'),('Marine City','MI'),('Marine On Saint Croix','MN'),('Marinette','WI'),('Maringouin','LA'),('Marion','AL'),('Marion','AR'),('Marion','CT'),('Marion','IA'),('Marion','IL'),('Marion','IN'),('Marion','KS'),('Marion','KY'),('Marion','LA'),('Marion','MA'),('Marion','MI'),('Marion','MS'),('Marion','MT'),('Marion','NC'),('Marion','ND'),('Marion','NY'),('Marion','OH'),('Marion','PA'),('Marion','SC'),('Marion','SD'),('Marion','TX'),('Marion','VA'),('Marion','WI'),('Marion Center','PA'),('Marion Heights','PA'),('Marion Junction','AL'),('Marion Station','MD'),('Marionville','MO'),('Marionville','VA'),('Mariposa','CA'),('Marissa','IL'),('Mark','IL'),('Mark Center','OH'),('Marked Tree','AR'),('Markesan','WI'),('Markham','IL'),('Markham','TX'),('Markham','VA'),('Markle','IN'),('Markleeville','CA'),('Markleton','PA'),('Markleville','IN'),('Markleysburg','PA'),('Marks','MS'),('Marksville','LA'),('Marland','OK'),('Marlboro','NJ'),('Marlboro','NY'),('Marlboro','VT'),('Marlborough','CT'),('Marlborough','MA'),('Marlborough','NH'),('Marlette','MI'),('Marlin','TX'),('Marlin','WA'),('Marlinton','WV'),('Marlow','NH'),('Marlow','OK'),('Marlton','NJ'),('Marmaduke','AR'),('Marmarth','ND'),('Marmora','NJ'),('Marne','IA'),('Marne','MI'),('Maroa','IL'),('Marquand','MO'),('Marquette','IA'),('Marquette','KS'),('Marquette','MI'),('Marquette','NE'),('Marquette','WI'),('Marquez','TX'),('Marrero','LA'),('Marriottsville','MD'),('Marrowbone','KY'),('Mars','PA'),('Mars Hill','ME'),('Mars Hill','NC'),('Marseilles','IL'),('Marshall','AK'),('Marshall','AR'),('Marshall','CA'),('Marshall','IL'),('Marshall','IN'),('Marshall','MI'),('Marshall','MN'),('Marshall','MO'),('Marshall','NC'),('Marshall','ND'),('Marshall','OK'),('Marshall','TX'),('Marshall','VA'),('Marshall','WA'),('Marshall','WI'),('Marshallberg','NC'),('Marshalls Creek','PA'),('Marshalltown','IA'),('Marshallville','GA'),('Marshallville','OH'),('Marshes Siding','KY'),('Marshfield','MA'),('Marshfield','MO'),('Marshfield','VT'),('Marshfield','WI'),('Marshfield Hills','MA'),('Marshville','NC'),('Marsing','ID'),('Marsland','NE'),('Marsteller','PA'),('Marston','MO'),('Marston','NC'),('Marstons Mills','MA'),('Mart','TX'),('Martel','OH'),('Martell','CA'),('Martell','NE'),('Martelle','IA'),('Martensdale','IA'),('Martha','KY'),('Martha','OK'),('Marthasville','MO'),('Marthaville','LA'),('Martin','GA'),('Martin','KY'),('Martin','MI'),('Martin','ND'),('Martin','OH'),('Martin','PA'),('Martin','SC'),('Martin','SD'),('Martin','TN'),('Martin City','MT'),('Martindale','PA'),('Martindale','TX'),('Martinez','CA'),('Martins Creek','PA'),('Martins Ferry','OH'),('Martinsburg','IA'),('Martinsburg','MO'),('Martinsburg','NY'),('Martinsburg','OH'),('Martinsburg','PA'),('Martinsburg','WV'),('Martinsdale','MT'),('Martinsville','IL'),('Martinsville','IN'),('Martinsville','MO'),('Martinsville','NJ'),('Martinsville','OH'),('Martinsville','TX'),('Martinsville','VA'),('Martinton','IL'),('Martville','NY'),('Marty','SD'),('Marvel','CO'),('Marvell','AR'),('Marvin','SD'),('Mary Alice','KY'),('Mary D','PA'),('Mary Esther','FL'),('Marydel','DE'),('Marydel','MD'),('Maryknoll','NY'),('Maryland','NY'),('Maryland Heights','MO'),('Maryland Line','MD'),('Marylhurst','OR'),('Maryneal','TX'),('Marysvale','UT'),('Marysville','CA'),('Marysville','IN'),('Marysville','KS'),('Marysville','MI'),('Marysville','MT'),('Marysville','OH'),('Marysville','PA'),('Marysville','WA'),('Maryus','VA'),('Maryville','IL'),('Maryville','MO'),('Maryville','TN'),('Mascot','TN'),('Mascot','VA'),('Mascotte','FL'),('Mascoutah','IL'),('Mashantucket','CT'),('Mashpee','MA'),('Maskell','NE'),('Mason','IL'),('Mason','KY'),('Mason','MI'),('Mason','OH'),('Mason','TN'),('Mason','TX'),('Mason','WI'),('Mason','WV'),('Mason City','IA'),('Mason City','IL'),('Mason City','NE'),('Masonic Home','KY'),('Masontown','PA'),('Masontown','WV'),('Masonville','CO'),('Masonville','IA'),('Masonville','NY'),('Maspeth','NY'),('Mass City','MI'),('Massapequa','NY'),('Massapequa Park','NY'),('Massena','IA'),('Massena','NY'),('Massey','MD'),('Massillon','OH'),('Masterson','TX'),('Mastic','NY'),('Mastic Beach','NY'),('Masury','OH'),('Matador','TX'),('Matagorda','TX'),('Matamoras','PA'),('Matawan','NJ'),('Matewan','WV'),('Matfield Green','KS'),('Matheny','WV'),('Mather','CA'),('Mather','PA'),('Mather','WI'),('Matherville','IL'),('Matheson','CO'),('Mathews','AL'),('Mathews','LA'),('Mathews','VA'),('Mathias','WV'),('Mathis','TX'),('Mathiston','MS'),('Matinicus','ME'),('Matlock','IA'),('Matlock','WA'),('Matoaka','WV'),('Mattapan','MA'),('Mattapoisett','MA'),('Mattaponi','VA'),('Mattawa','WA'),('Mattawamkeag','ME'),('Mattawan','MI'),('Mattawana','PA'),('Matteson','IL'),('Matthews','GA'); + +INSERT INTO [cities] VALUES ('Matthews','IN'),('Matthews','MO'),('Matthews','NC'),('Mattituck','NY'),('Mattoon','IL'),('Mattoon','WI'),('Mauckport','IN'),('Maud','OK'),('Maud','TX'),('Maugansville','MD'),('Mauk','GA'),('Mauldin','SC'),('Maumee','OH'),('Maumelle','AR'),('Maunabo','PR'),('Maunaloa','HI'),('Maunie','IL'),('Maupin','OR'),('Maurepas','LA'),('Maurertown','VA'),('Maurice','IA'),('Maurice','LA'),('Mauricetown','NJ'),('Mauriceville','TX'),('Maury','NC'),('Maury City','TN'),('Mauston','WI'),('Mavisdale','VA'),('Max','MN'),('Max','ND'),('Max','NE'),('Max Meadows','VA'),('Maxatawny','PA'),('Maxbass','ND'),('Maxeys','GA'),('Maxie','VA'),('Maximo','OH'),('Maxton','NC'),('Maxwell','CA'),('Maxwell','IA'),('Maxwell','IN'),('Maxwell','NE'),('Maxwell','NM'),('Maxwell','TX'),('Maxwelton','WV'),('May','ID'),('May','OK'),('May','TX'),('Mayaguez','PR'),('Maybee','MI'),('Maybell','CO'),('Maybeury','WV'),('Maybrook','NY'),('Maydelle','TX'),('Mayer','AZ'),('Mayer','MN'),('Mayersville','MS'),('Mayesville','SC'),('Mayetta','KS'),('Mayfield','KS'),('Mayfield','KY'),('Mayfield','MI'),('Mayfield','NY'),('Mayfield','UT'),('Mayflower','AR'),('Mayhew','MS'),('Mayhill','NM'),('Mayking','KY'),('Maylene','AL'),('Maynard','AR'),('Maynard','IA'),('Maynard','MA'),('Maynard','MN'),('Maynard','OH'),('Maynardville','TN'),('Mayo','FL'),('Mayo','MD'),('Mayo','SC'),('Mayodan','NC'),('Maypearl','TX'),('Mayport','PA'),('Mays','IN'),('Mays Landing','NJ'),('Maysel','WV'),('Mayslick','KY'),('Maysville','AR'),('Maysville','GA'),('Maysville','KY'),('Maysville','MO'),('Maysville','NC'),('Maysville','OK'),('Maysville','WV'),('Maytown','PA'),('Mayview','MO'),('Mayville','MI'),('Mayville','ND'),('Mayville','NY'),('Mayville','WI'),('Maywood','CA'),('Maywood','IL'),('Maywood','MO'),('Maywood','NE'),('Maywood','NJ'),('Mazama','WA'),('Mazeppa','MN'),('Mazie','KY'),('Mazomanie','WI'),('Mazon','IL'),('Mc Adams','MS'),('Mc Adenville','NC'),('Mc Afee','NJ'),('Mc Alister','NM'),('Mc Alisterville','PA'),('Mc Allister','MT'),('Mc Alpin','FL'),('Mc Andrews','KY'),('Mc Arthur','OH'),('Mc Bain','MI'),('Mc Bee','SC'),('Mc Bride','MO'),('Mc Call Creek','MS'),('Mc Calla','AL'),('Mc Callsburg','IA'),('Mc Camey','TX'),('Mc Carley','MS'),('Mc Carr','KY'),('Mc Caskill','AR'),('Mc Caulley','TX'),('Mc Causland','IA'),('Mc Caysville','GA'),('Mc Clave','CO'),('Mc Clelland','IA'),('Mc Clellandtown','PA'),('Mc Clellanville','SC'),('Mc Clure','IL'),('Mc Clure','OH'),('Mc Clure','PA'),('Mc Clure','VA'),('Mc Clurg','MO'),('Mc Coll','SC'),('Mc Comb','OH'),('Mc Connell','IL'),('Mc Connells','SC'),('Mc Connellsburg','PA'),('Mc Connellstown','PA'),('Mc Connellsville','NY'),('Mc Cook','NE'),('Mc Cool','MS'),('Mc Cool Junction','NE'),('Mc Cordsville','IN'),('Mc Cormick','SC'),('Mc Coy','CO'),('Mc Coy','VA'),('Mc Cracken','KS'),('Mc Crory','AR'),('Mc Cune','KS'),('Mc Cutchenville','OH'),('Mc Dade','TX'),('Mc Daniels','KY'),('Mc David','FL'),('Mc Dermitt','NV'),('Mc Dermott','OH'),('Mc Donald','KS'),('Mc Donald','NM'),('Mc Donald','OH'),('Mc Donald','PA'),('Mc Donald','TN'),('Mc Donough','NY'),('Mc Dougal','AR'),('Mc Dowell','KY'),('Mc Dowell','VA'),('Mc Elhattan','PA'),('Mc Ewen','TN'),('Mc Ewensville','PA'),('Mc Fall','MO'),('Mc Farlan','NC'),('Mc Farland','CA'),('Mc Farland','KS'),('Mc Farland','WI'),('Mc Gaheysville','VA'),('Mc Gee','MO'),('Mc Gehee','AR'),('Mc Gill','NV'),('Mc Girk','MO'),('Mc Grady','NC'),('Mc Grann','PA'),('Mc Grath','AK'),('Mc Grath','MN'),('Mc Graw','NY'),('Mc Graws','WV'),('Mc Gregor','IA'),('Mc Gregor','TX'),('Mc Guffey','OH'),('Mc Henry','KY'),('Mc Henry','MD'),('Mc Henry','MS'),('Mc Indoe Falls','VT'),('Mc Intire','IA'),('Mc Intosh','AL'),('Mc Intosh','FL'),('Mc Intosh','NM'),('Mc Intosh','SD'),('Mc Intyre','GA'),('Mc Intyre','PA'),('Mc Kean','PA'),('Mc Kee','KY'),('Mc Kees Rocks','PA'),('Mc Kenney','VA'),('Mc Kenzie','AL'),('Mc Kenzie','TN'),('Mc Kinney','KY'),('Mc Kinnon','WY'),('Mc Kittrick','CA'),('Mc Knightstown','PA'),('Mc Lain','MS'),('Mc Laughlin','SD'),('Mc Lean','IL'),('Mc Lean','NY'),('Mc Lean','VA'),('Mc Leansboro','IL'),('Mc Leansville','NC'),('Mc Lemoresville','TN'),('Mc Leod','MT'),('Mc Leod','TX'),('Mc Louth','KS'),('Mc Millan','MI'),('Mc Minnville','TN'),('Mc Nabb','IL'),('Mc Naughton','WI'),('Mc Neal','AZ'),('Mc Neil','AR'),('Mc Neil','TX'),('Mc Neill','MS'),('Mc Quady','KY'),('Mc Queeney','TX'),('Mc Rae','AR'),('Mc Rae','GA'),('Mc Roberts','KY'),('Mc Shan','AL'),('Mc Sherrystown','PA'),('Mc Veytown','PA'),('Mc Williams','AL'),('Mcadoo','PA'),('Mcadoo','TX'),('Mcalester','OK'),('Mcallen','TX'),('Mcarthur','CA'),('Mcbrides','MI'),('Mccall','ID'),('Mccammon','ID'),('Mcchord Afb','WA'),('Mccleary','WA'),('Mcclellan','CA'),('Mccloud','CA'),('Mcclusky','ND'),('Mccomb','MS'),('Mcconnell Afb','KS'),('Mcconnelsville','OH'),('Mccurtain','OK'),('Mccutcheon Field','NC'),('Mcdaniel','MD'),('Mcdonough','GA'),('Mcfaddin','TX'),('Mcgees Mills','PA'),('Mcgregor','MN'),('Mcgregor','ND'),('Mcgrew','NE'),('Mchenry','IL'),('Mchenry','ND'),('Mcintosh','MN'),('Mckeesport','PA'),('Mckenna','WA'),('Mckinleyville','CA'),('Mckinney','TX'),('Mclean','NE'),('Mclean','TX'),('Mcleod','ND'),('Mcloud','OK'),('Mcmechen','WV'),('Mcminnville','OR'),('Mcnary','AZ'),('Mcpherson','KS'),('Mcville','ND'),('Meacham','OR'),('Mead','CO'),('Mead','NE'),('Mead','OK'),('Mead','WA'),('Meade','KS'),('Meadow','SD'),('Meadow','TX'),('Meadow','UT'),('Meadow Bridge','WV'),('Meadow Creek','WV'),('Meadow Grove','NE'),('Meadow Lands','PA'),('Meadow Valley','CA'),('Meadow Vista','CA'),('Meadowbrook','WV'),('Meadowlands','MN'),('Meadows Of Dan','VA'),('Meadowview','VA'),('Meadview','AZ'),('Meadville','MO'),('Meadville','MS'),('Meadville','PA'),('Meally','KY'),('Means','KY'),('Meansville','GA'),('Mears','MI'),('Mears','VA'),('Mebane','NC'),('Mecca','CA'),('Mecca','IN'),('Mechanic Falls','ME'),('Mechanicsburg','IL'),('Mechanicsburg','OH'),('Mechanicsburg','PA'),('Mechanicstown','OH'),('Mechanicsville','IA'),('Mechanicsville','MD'),('Mechanicsville','PA'),('Mechanicsville','VA'),('Mechanicville','NY'),('Mecklenburg','NY'),('Mecosta','MI'),('Medanales','NM'),('Medaryville','IN'),('Meddybemps','ME'),('Medfield','MA'),('Medford','MA'),('Medford','MN'),('Medford','NJ'),('Medford','NY'),('Medford','OK'),('Medford','OR'),('Medford','WI'),('Media','IL'),('Media','PA'),('Mediapolis','IA'),('Medical Lake','WA'),('Medicine Bow','WY'),('Medicine Lake','MT'),('Medicine Lodge','KS'),('Medicine Park','OK'),('Medimont','ID'),('Medina','ND'),('Medina','NY'),('Medina','OH'),('Medina','TN'),('Medina','TX'),('Medina','WA'),('Medinah','IL'),('Medon','TN'),('Medora','IL'),('Medora','IN'),('Medora','ND'),('Medusa','NY'),('Medway','MA'),('Medway','ME'),('Medway','OH'),('Meeker','CO'),('Meeker','OK'),('Meers','OK'),('Meeteetse','WY'),('Megargel','AL'),('Megargel','TX'),('Mehama','OR'),('Meherrin','VA'),('Mehoopany','PA'),('Meigs','GA'),('Mekinock','ND'),('Mekoryuk','AK'),('Melba','ID'),('Melber','KY'),('Melbeta','NE'),('Melbourne','AR'),('Melbourne','FL'),('Melbourne','IA'),('Melbourne','KY'),('Melbourne Beach','FL'),('Melcher','IA'),('Melcroft','PA'),('Meldrim','GA'),('Melfa','VA'),('Melissa','TX'),('Mellen','WI'),('Mellenville','NY'),('Mellette','SD'),('Mellott','IN'),('Mellwood','AR'),('Melmore','OH'),('Melrose','FL'),('Melrose','IA'),('Melrose','LA'),('Melrose','MA'),('Melrose','MN'),('Melrose','MT'),('Melrose','NM'),('Melrose','NY'),('Melrose','OH'),('Melrose','WI'),('Melrose Park','IL'),('Melrude','MN'),('Melstone','MT'),('Melvern','KS'),('Melville','LA'),('Melville','MT'),('Melville','NY'),('Melvin','AL'),('Melvin','IA'),('Melvin','IL'),('Melvin','KY'),('Melvin','MI'),('Melvin','TX'),('Melvin Village','NH'),('Melvindale','MI'),('Memphis','IN'),('Memphis','MI'),('Memphis','MO'),('Memphis','NE'),('Memphis','NY'),('Memphis','TN'),('Memphis','TX'),('Mena','AR'),('Menahga','MN'),('Menan','ID'),('Menard','IL'),('Menard','TX'),('Menasha','WI'),('Mendenhall','MS'),('Mendenhall','PA'),('Mendham','NJ'),('Mendocino','CA'),('Mendon','IL'),('Mendon','MA'),('Mendon','MI'),('Mendon','MO'),('Mendon','NY'),('Mendon','OH'),('Mendon','UT'),('Mendota','CA'),('Mendota','IL'),('Mendota','MN'),('Mendota','VA'),('Menemsha','MA'),('Menifee','AR'),('Menifee','CA'),('Menlo','GA'),('Menlo','IA'),('Menlo','WA'),('Menlo Park','CA'),('Menno','SD'),('Meno','OK'),('Menoken','ND'),('Menominee','MI'),('Menomonee Falls','WI'),('Menomonie','WI'),('Mentcle','PA'),('Mentmore','NM'),('Mentone','AL'),('Mentone','CA'),('Mentone','IN'),('Mentone','TX'),('Mentor','MN'),('Mentor','OH'),('Mequon','WI'),('Mer Rouge','LA'),('Meraux','LA'),('Merced','CA'),('Mercedes','TX'),('Mercedita','PR'),('Mercer','MO'),('Mercer','ND'),('Mercer','PA'),('Mercer','TN'),('Mercer','WI'),('Mercer Island','WA'),('Mercersburg','PA'),('Merchantville','NJ'),('Mercury','NV'),('Meredith','CO'),('Meredith','NH'),('Meredithville','VA'),('Meredosia','IL'),('Mereta','TX'),('Meridale','NY'),('Meriden','CT'),('Meriden','IA'),('Meriden','KS'),('Meriden','NH'),('Meriden','WY'),('Meridian','CA'),('Meridian','GA'),('Meridian','ID'),('Meridian','MS'),('Meridian','NY'),('Meridian','OK'),('Meridian','TX'),('Meridianville','AL'); + +INSERT INTO [cities] VALUES ('Merigold','MS'),('Merino','CO'),('Merion Station','PA'),('Merit','TX'),('Merkel','TX'),('Merlin','OR'),('Mermentau','LA'),('Merna','IL'),('Merna','NE'),('Merom','IN'),('Merrick','NY'),('Merrifield','MN'),('Merrifield','VA'),('Merrill','IA'),('Merrill','MI'),('Merrill','OR'),('Merrill','WI'),('Merrillan','WI'),('Merrillville','IN'),('Merrimac','MA'),('Merrimac','WI'),('Merrimack','NH'),('Merriman','NE'),('Merritt','MI'),('Merritt','NC'),('Merritt Island','FL'),('Merrittstown','PA'),('Merry Hill','NC'),('Merry Point','VA'),('Merryville','LA'),('Mershon','GA'),('Mertens','TX'),('Merton','WI'),('Mertzon','TX'),('Mertztown','PA'),('Mesa','AZ'),('Mesa','CO'),('Mesa','ID'),('Mesa','WA'),('Mesa Verde National Park','CO'),('Mescalero','NM'),('Mesena','GA'),('Meservey','IA'),('Meshoppen','PA'),('Mesick','MI'),('Mesilla','NM'),('Mesilla Park','NM'),('Mesopotamia','OH'),('Mesquite','NM'),('Mesquite','NV'),('Mesquite','TX'),('Meta','MO'),('Metairie','LA'),('Metaline','WA'),('Metaline Falls','WA'),('Metamora','IL'),('Metamora','IN'),('Metamora','MI'),('Metamora','OH'),('Metcalf','IL'),('Metcalfe','MS'),('Methow','WA'),('Methuen','MA'),('Metlakatla','AK'),('Metropolis','IL'),('Metter','GA'),('Metuchen','NJ'),('Metz','MO'),('Metz','WV'),('Mexia','AL'),('Mexia','TX'),('Mexican Hat','UT'),('Mexican Springs','NM'),('Mexico','IN'),('Mexico','ME'),('Mexico','MO'),('Mexico','NY'),('Mexico','PA'),('Mexico Beach','FL'),('Meyers Chuck','AK'),('Meyersdale','PA'),('Meyersville','TX'),('Mi Wuk Village','CA'),('Miami','AZ'),('Miami','FL'),('Miami','IN'),('Miami','MO'),('Miami','NM'),('Miami','OK'),('Miami','TX'),('Miami','WV'),('Miami Beach','FL'),('Miamisburg','OH'),('Miamitown','OH'),('Miamiville','OH'),('Mica','WA'),('Micanopy','FL'),('Micaville','NC'),('Michael','IL'),('Michie','TN'),('Michigamme','MI'),('Michigan','ND'),('Michigan Center','MI'),('Michigan City','IN'),('Michigan City','MS'),('Michigantown','IN'),('Mickleton','NJ'),('Mico','TX'),('Micro','NC'),('Mid Florida','FL'),('Middle Amana','IA'),('Middle Bass','OH'),('Middle Brook','MO'),('Middle Falls','NY'),('Middle Granville','NY'),('Middle Grove','NY'),('Middle Haddam','CT'),('Middle Island','NY'),('Middle Point','OH'),('Middle River','MD'),('Middle River','MN'),('Middle Village','NY'),('Middleboro','MA'),('Middlebourne','WV'),('Middlebranch','OH'),('Middlebrook','VA'),('Middleburg','FL'),('Middleburg','KY'),('Middleburg','NC'),('Middleburg','OH'),('Middleburg','PA'),('Middleburg','VA'),('Middleburgh','NY'),('Middlebury','CT'),('Middlebury','IN'),('Middlebury','VT'),('Middlebury Center','PA'),('Middlefield','CT'),('Middlefield','MA'),('Middlefield','OH'),('Middleport','NY'),('Middleport','OH'),('Middleport','PA'),('Middlesboro','KY'),('Middlesex','NC'),('Middlesex','NJ'),('Middlesex','NY'),('Middleton','ID'),('Middleton','MA'),('Middleton','MI'),('Middleton','TN'),('Middleton','WI'),('Middletown','CA'),('Middletown','CT'),('Middletown','DE'),('Middletown','IA'),('Middletown','IL'),('Middletown','IN'),('Middletown','MD'),('Middletown','MO'),('Middletown','NJ'),('Middletown','NY'),('Middletown','OH'),('Middletown','PA'),('Middletown','RI'),('Middletown','VA'),('Middletown Springs','VT'),('Middleville','MI'),('Middleville','NJ'),('Middleville','NY'),('Midfield','TX'),('Midkiff','TX'),('Midkiff','WV'),('Midland','AR'),('Midland','GA'),('Midland','IN'),('Midland','MD'),('Midland','MI'),('Midland','NC'),('Midland','OH'),('Midland','OR'),('Midland','PA'),('Midland','SD'),('Midland','TX'),('Midland','VA'),('Midland City','AL'),('Midland Park','NJ'),('Midlothian','IL'),('Midlothian','MD'),('Midlothian','TX'),('Midlothian','VA'),('Midnight','MS'),('Midpines','CA'),('Midvale','ID'),('Midvale','OH'),('Midvale','UT'),('Midville','GA'),('Midway','AL'),('Midway','AR'),('Midway','FL'),('Midway','GA'),('Midway','KY'),('Midway','PA'),('Midway','TN'),('Midway','TX'),('Midway','UT'),('Midway','WV'),('Midway City','CA'),('Midway Park','NC'),('Midwest','WY'),('Mifflin','PA'),('Mifflinburg','PA'),('Mifflintown','PA'),('Mifflinville','PA'),('Mikado','MI'),('Mikana','WI'),('Mikkalo','OR'),('Milaca','MN'),('Milam','TX'),('Milam','WV'),('Milan','GA'),('Milan','IL'),('Milan','IN'),('Milan','KS'),('Milan','MI'),('Milan','MN'),('Milan','MO'),('Milan','NH'),('Milan','NM'),('Milan','OH'),('Milan','PA'),('Milan','TN'),('Milano','TX'),('Milanville','PA'),('Milbank','SD'),('Milbridge','ME'),('Milburn','KY'),('Milburn','OK'),('Mildred','MT'),('Mildred','PA'),('Miles','IA'),('Miles','TX'),('Miles City','MT'),('Milesburg','PA'),('Milesville','SD'),('Miley','SC'),('Milfay','OK'),('Milford','CA'),('Milford','CT'),('Milford','DE'),('Milford','IA'),('Milford','IL'),('Milford','IN'),('Milford','KS'),('Milford','KY'),('Milford','MA'),('Milford','ME'),('Milford','MI'),('Milford','MO'),('Milford','NE'),('Milford','NH'),('Milford','NJ'),('Milford','NY'),('Milford','OH'),('Milford','PA'),('Milford','TX'),('Milford','UT'),('Milford','VA'),('Milford Center','OH'),('Milford Square','PA'),('Mililani','HI'),('Mill City','OR'),('Mill Creek','CA'),('Mill Creek','IN'),('Mill Creek','OK'),('Mill Creek','PA'),('Mill Creek','WA'),('Mill Creek','WV'),('Mill Hall','PA'),('Mill Neck','NY'),('Mill River','MA'),('Mill Run','PA'),('Mill Shoals','IL'),('Mill Spring','MO'),('Mill Spring','NC'),('Mill Valley','CA'),('Mill Village','PA'),('Milladore','WI'),('Millboro','VA'),('Millbrae','CA'),('Millbrook','AL'),('Millbrook','IL'),('Millbrook','NY'),('Millburn','NJ'),('Millbury','MA'),('Millbury','OH'),('Millcreek','IL'),('Milldale','CT'),('Milledgeville','GA'),('Milledgeville','IL'),('Milledgeville','OH'),('Milledgeville','TN'),('Millen','GA'),('Miller','MO'),('Miller','NE'),('Miller','SD'),('Miller City','IL'),('Miller City','OH'),('Miller Place','NY'),('Millers Creek','NC'),('Millers Falls','MA'),('Millers Tavern','VA'),('Millersburg','IA'),('Millersburg','IN'),('Millersburg','KY'),('Millersburg','MI'),('Millersburg','OH'),('Millersburg','PA'),('Millersport','OH'),('Millerstown','PA'),('Millersview','TX'),('Millersville','MD'),('Millersville','MO'),('Millersville','PA'),('Millerton','IA'),('Millerton','NY'),('Millerton','OK'),('Millerton','PA'),('Millerville','AL'),('Millfield','OH'),('Millheim','PA'),('Millhousen','IN'),('Millican','TX'),('Milligan','FL'),('Milligan','NE'),('Milligan College','TN'),('Milliken','CO'),('Millington','IL'),('Millington','MD'),('Millington','MI'),('Millington','NJ'),('Millington','TN'),('Millinocket','ME'),('Millis','MA'),('Millmont','PA'),('Millport','AL'),('Millport','NY'),('Millrift','PA'),('Millry','AL'),('Mills','NE'),('Mills','NM'),('Mills','PA'),('Mills','WY'),('Mills River','NC'),('Millsap','TX'),('Millsboro','DE'),('Millsboro','PA'),('Millstadt','IL'),('Millston','WI'),('Millstone','KY'),('Millstone','WV'),('Millstone Township','NJ'),('Milltown','IN'),('Milltown','MT'),('Milltown','NJ'),('Milltown','WI'),('Millville','CA'),('Millville','DE'),('Millville','MA'),('Millville','MN'),('Millville','NJ'),('Millville','PA'),('Millville','UT'),('Millville','WV'),('Millwood','GA'),('Millwood','KY'),('Millwood','NY'),('Millwood','VA'),('Millwood','WV'),('Milmay','NJ'),('Milmine','IL'),('Milner','GA'),('Milnesand','NM'),('Milnesville','PA'),('Milnor','ND'),('Milo','IA'),('Milo','ME'),('Milo','MO'),('Milpitas','CA'),('Milroy','IN'),('Milroy','MN'),('Milroy','PA'),('Milton','DE'),('Milton','FL'),('Milton','IA'),('Milton','IL'),('Milton','IN'),('Milton','KS'),('Milton','KY'),('Milton','LA'),('Milton','MA'),('Milton','NC'),('Milton','ND'),('Milton','NH'),('Milton','NY'),('Milton','PA'),('Milton','TN'),('Milton','VT'),('Milton','WA'),('Milton','WI'),('Milton','WV'),('Milton Center','OH'),('Milton Freewater','OR'),('Milton Mills','NH'),('Milton Village','MA'),('Miltona','MN'),('Miltonvale','KS'),('Milwaukee','NC'),('Milwaukee','WI'),('Mimbres','NM'),('Mims','FL'),('Mina','NV'),('Minatare','NE'),('Minburn','IA'),('Minco','OK'),('Minden','IA'),('Minden','LA'),('Minden','NE'),('Minden','NV'),('Minden','TX'),('Minden','WV'),('Minden City','MI'),('Mindenmines','MO'),('Mindoro','WI'),('Mine Hill','NJ'),('Mineola','IA'),('Mineola','NY'),('Mineola','TX'),('Mineral','CA'),('Mineral','IL'),('Mineral','TX'),('Mineral','VA'),('Mineral','WA'),('Mineral Bluff','GA'),('Mineral City','OH'),('Mineral Point','MO'),('Mineral Point','PA'),('Mineral Point','WI'),('Mineral Ridge','OH'),('Mineral Springs','AR'),('Mineral Springs','NC'),('Mineral Springs','PA'),('Mineral Wells','TX'),('Mineral Wells','WV'),('Minersville','PA'),('Minersville','UT'),('Minerva','KY'),('Minerva','NY'),('Minerva','OH'),('Minetto','NY'),('Mineville','NY'),('Minford','OH'),('Mingo','IA'),('Mingo','OH'),('Mingo Junction','OH'),('Mingoville','PA'),('Mingus','TX'),('Minidoka','ID'),('Minier','IL'),('Minisink Hills','PA'),('Minneapolis','KS'),('Minneapolis','MN'),('Minneapolis','NC'),('Minneola','FL'),('Minneola','KS'),('Minneota','MN'),('Minnesota City','MN'),('Minnesota Lake','MN'),('Minnetonka','MN'),('Minnetonka Beach','MN'),('Minnewaukan','ND'),('Minnie','KY'),('Minoa','NY'),('Minocqua','WI'),('Minong','WI'),('Minonk','IL'),('Minooka','IL'),('Minor Hill','TN'),('Minot','MA'),('Minot','ME'),('Minot','ND'),('Minot Afb','ND'),('Minotola','NJ'),('Minster','OH'),('Mint Spring','VA'),('Minter','AL'),('Minter City','MS'),('Minto','AK'),('Minto','ND'),('Minturn','AR'); + +INSERT INTO [cities] VALUES ('Minturn','CO'),('Minturn','SC'),('Mio','MI'),('Mira Loma','CA'),('Miracle','KY'),('Miramar Beach','FL'),('Miramonte','CA'),('Miranda','CA'),('Mirando City','TX'),('Mirror Lake','NH'),('Misenheimer','NC'),('Mishawaka','IN'),('Mishicot','WI'),('Mission','KS'),('Mission','SD'),('Mission','TX'),('Mission Hill','SD'),('Mission Hills','CA'),('Mission Viejo','CA'),('Mississippi State','MS'),('Missoula','MT'),('Missouri City','MO'),('Missouri City','TX'),('Missouri Valley','IA'),('Mistletoe','KY'),('Mitchell','GA'),('Mitchell','IN'),('Mitchell','NE'),('Mitchell','OR'),('Mitchell','SD'),('Mitchells','VA'),('Mitchellsburg','KY'),('Mitchellville','IA'),('Mitchellville','TN'),('Mittie','LA'),('Mize','KY'),('Mize','MS'),('Mizpah','MN'),('Mizpah','NJ'),('Moab','UT'),('Moapa','NV'),('Moatsville','WV'),('Mobeetie','TX'),('Moberly','MO'),('Mobile','AL'),('Mobridge','SD'),('Moca','PR'),('Moccasin','CA'),('Moccasin','MT'),('Mocksville','NC'),('Moclips','WA'),('Modale','IA'),('Mode','IL'),('Model','CO'),('Model City','NY'),('Modena','NY'),('Modena','PA'),('Modena','UT'),('Modest Town','VA'),('Modesto','CA'),('Modesto','IL'),('Modoc','IL'),('Modoc','IN'),('Modoc','SC'),('Moffat','CO'),('Moffett','OK'),('Moffit','ND'),('Mogadore','OH'),('Mohall','ND'),('Mohave Valley','AZ'),('Mohawk','MI'),('Mohawk','NY'),('Mohawk','TN'),('Mohawk','WV'),('Mohegan Lake','NY'),('Mohler','WA'),('Mohnton','PA'),('Mohrsville','PA'),('Moira','NY'),('Mojave','CA'),('Mokane','MO'),('Mokelumne Hill','CA'),('Mokena','IL'),('Molalla','OR'),('Molena','GA'),('Molina','CO'),('Moline','IL'),('Moline','KS'),('Moline','MI'),('Molino','FL'),('Mollusk','VA'),('Molt','MT'),('Momence','IL'),('Mona','UT'),('Monaca','PA'),('Monahans','TX'),('Monarch','CO'),('Monarch','MT'),('Moncks Corner','SC'),('Monclova','OH'),('Moncure','NC'),('Mondamin','IA'),('Mondovi','WI'),('Monee','IL'),('Monessen','PA'),('Moneta','VA'),('Monett','MO'),('Monetta','SC'),('Monette','AR'),('Money','MS'),('Mongaup Valley','NY'),('Mongo','IN'),('Monhegan','ME'),('Monitor','WA'),('Monkton','MD'),('Monkton','VT'),('Monmouth','IA'),('Monmouth','IL'),('Monmouth','ME'),('Monmouth','OR'),('Monmouth Beach','NJ'),('Monmouth Junction','NJ'),('Mono Hot Springs','CA'),('Monocacy Station','PA'),('Monon','IN'),('Monona','IA'),('Monongahela','PA'),('Monponsett','MA'),('Monroe','AR'),('Monroe','CT'),('Monroe','GA'),('Monroe','IA'),('Monroe','IN'),('Monroe','LA'),('Monroe','ME'),('Monroe','MI'),('Monroe','NC'),('Monroe','NE'),('Monroe','NH'),('Monroe','NY'),('Monroe','OH'),('Monroe','OK'),('Monroe','OR'),('Monroe','SD'),('Monroe','TN'),('Monroe','UT'),('Monroe','VA'),('Monroe','WA'),('Monroe','WI'),('Monroe Bridge','MA'),('Monroe Center','IL'),('Monroe City','IN'),('Monroe City','MO'),('Monroe Township','NJ'),('Monroeton','PA'),('Monroeville','AL'),('Monroeville','IN'),('Monroeville','NJ'),('Monroeville','OH'),('Monroeville','PA'),('Monrovia','CA'),('Monrovia','IN'),('Monrovia','MD'),('Monsey','NY'),('Monson','MA'),('Monson','ME'),('Mont Alto','PA'),('Mont Belvieu','TX'),('Mont Clare','PA'),('Mont Vernon','NH'),('Montague','CA'),('Montague','MA'),('Montague','MI'),('Montague','NJ'),('Montague','TX'),('Montalba','TX'),('Montana Mines','WV'),('Montandon','PA'),('Montara','CA'),('Montauk','NY'),('Montcalm','WV'),('Montchanin','DE'),('Montclair','CA'),('Montclair','NJ'),('Monte Rio','CA'),('Monte Vista','CO'),('Monteagle','TN'),('Montebello','CA'),('Montebello','VA'),('Montegut','LA'),('Montello','NV'),('Montello','WI'),('Monterey','CA'),('Monterey','IN'),('Monterey','LA'),('Monterey','MA'),('Monterey','TN'),('Monterey','VA'),('Monterey Park','CA'),('Monterville','WV'),('Montesano','WA'),('Montevallo','AL'),('Montevideo','MN'),('Monteview','ID'),('Montezuma','GA'),('Montezuma','IA'),('Montezuma','IN'),('Montezuma','KS'),('Montezuma','NC'),('Montezuma','NM'),('Montezuma','NY'),('Montezuma','OH'),('Montezuma Creek','UT'),('Montfort','WI'),('Montgomery','AL'),('Montgomery','IL'),('Montgomery','IN'),('Montgomery','LA'),('Montgomery','MI'),('Montgomery','MN'),('Montgomery','NY'),('Montgomery','PA'),('Montgomery','TX'),('Montgomery','VT'),('Montgomery','WV'),('Montgomery Center','VT'),('Montgomery City','MO'),('Montgomery Creek','CA'),('Montgomery Village','MD'),('Montgomeryville','PA'),('Monticello','AR'),('Monticello','FL'),('Monticello','GA'),('Monticello','IA'),('Monticello','IL'),('Monticello','IN'),('Monticello','KY'),('Monticello','ME'),('Monticello','MN'),('Monticello','MO'),('Monticello','MS'),('Monticello','NM'),('Monticello','NY'),('Monticello','UT'),('Monticello','WI'),('Montier','MO'),('Montmorenci','IN'),('Montmorenci','SC'),('Montour','IA'),('Montour Falls','NY'),('Montoursville','PA'),('Montpelier','IA'),('Montpelier','ID'),('Montpelier','IN'),('Montpelier','MS'),('Montpelier','ND'),('Montpelier','OH'),('Montpelier','VA'),('Montpelier','VT'),('Montpelier Station','VA'),('Montreal','MO'),('Montreal','WI'),('Montreat','NC'),('Montrose','AL'),('Montrose','AR'),('Montrose','CA'),('Montrose','CO'),('Montrose','GA'),('Montrose','IA'),('Montrose','IL'),('Montrose','MI'),('Montrose','MN'),('Montrose','MO'),('Montrose','NY'),('Montrose','PA'),('Montrose','SD'),('Montrose','WV'),('Montross','VA'),('Montvale','NJ'),('Montvale','VA'),('Montverde','FL'),('Montville','CT'),('Montville','NJ'),('Montville','OH'),('Monument','CO'),('Monument','KS'),('Monument','NM'),('Monument','OR'),('Monument Beach','MA'),('Monument Valley','UT'),('Moodus','CT'),('Moody','AL'),('Moody','ME'),('Moody','MO'),('Moody','TX'),('Moody A F B','GA'),('Moodys','OK'),('Mooers','NY'),('Mooers Forks','NY'),('Moon','VA'),('Moonachie','NJ'),('Moorcroft','WY'),('Moore','ID'),('Moore','MT'),('Moore','SC'),('Moore','TX'),('Moore Haven','FL'),('Moorefield','KY'),('Moorefield','NE'),('Moorefield','WV'),('Mooreland','IN'),('Mooreland','OK'),('Moores Hill','IN'),('Mooresboro','NC'),('Mooresburg','TN'),('Moorestown','NJ'),('Mooresville','AL'),('Mooresville','IN'),('Mooresville','MO'),('Mooresville','NC'),('Mooreton','ND'),('Mooreville','MS'),('Moorhead','IA'),('Moorhead','MN'),('Moorhead','MS'),('Mooringsport','LA'),('Moorland','IA'),('Moorpark','CA'),('Moose','WY'),('Moose Lake','MN'),('Moose Pass','AK'),('Mooseheart','IL'),('Moosic','PA'),('Moosup','CT'),('Mora','LA'),('Mora','MN'),('Mora','MO'),('Mora','NM'),('Moraga','CA'),('Moran','KS'),('Moran','MI'),('Moran','TX'),('Moran','WY'),('Morann','PA'),('Morattico','VA'),('Moravia','IA'),('Moravia','NY'),('Moravian Falls','NC'),('Moreauville','LA'),('Morehead','KY'),('Morehead City','NC'),('Morehouse','MO'),('Moreland','GA'),('Moreland','ID'),('Morenci','AZ'),('Morenci','MI'),('Moreno Valley','CA'),('Moretown','VT'),('Morgan','GA'),('Morgan','MN'),('Morgan','PA'),('Morgan','TX'),('Morgan','UT'),('Morgan','VT'),('Morgan City','LA'),('Morgan City','MS'),('Morgan Hill','CA'),('Morgan Mill','TX'),('Morganfield','KY'),('Morganton','GA'),('Morganton','NC'),('Morgantown','IN'),('Morgantown','KY'),('Morgantown','PA'),('Morgantown','WV'),('Morganville','KS'),('Morganville','NJ'),('Morganza','LA'),('Morganza','MD'),('Moriah','NY'),('Moriah Center','NY'),('Moriarty','NM'),('Moriches','NY'),('Morland','KS'),('Morley','IA'),('Morley','MI'),('Morley','MO'),('Mormon Lake','AZ'),('Morning Sun','IA'),('Morning View','KY'),('Moro','AR'),('Moro','IL'),('Moro','OR'),('Morocco','IN'),('Morongo Valley','CA'),('Moroni','UT'),('Morovis','PR'),('Morral','OH'),('Morrice','MI'),('Morrill','KS'),('Morrill','ME'),('Morrill','NE'),('Morrilton','AR'),('Morris','AL'),('Morris','CT'),('Morris','GA'),('Morris','IL'),('Morris','IN'),('Morris','MN'),('Morris','NY'),('Morris','OK'),('Morris','PA'),('Morris Chapel','TN'),('Morris Run','PA'),('Morrisdale','PA'),('Morrison','CO'),('Morrison','IA'),('Morrison','IL'),('Morrison','MO'),('Morrison','OK'),('Morrison','TN'),('Morrisonville','IL'),('Morrisonville','NY'),('Morrisonville','WI'),('Morriston','FL'),('Morristown','AZ'),('Morristown','IN'),('Morristown','MN'),('Morristown','NJ'),('Morristown','NY'),('Morristown','OH'),('Morristown','SD'),('Morristown','TN'),('Morrisville','MO'),('Morrisville','NC'),('Morrisville','NY'),('Morrisville','PA'),('Morrisville','VT'),('Morro Bay','CA'),('Morrow','AR'),('Morrow','GA'),('Morrow','LA'),('Morrow','OH'),('Morrowville','KS'),('Morse','LA'),('Morse','TX'),('Morse Bluff','NE'),('Morse Mill','MO'),('Morton','IL'),('Morton','MN'),('Morton','MS'),('Morton','NY'),('Morton','PA'),('Morton','TX'),('Morton','WA'),('Morton Grove','IL'),('Mortons Gap','KY'),('Morven','GA'),('Morven','NC'),('Morvin','AL'),('Mosby','MO'),('Mosby','MT'),('Mosca','CO'),('Moscow','AR'),('Moscow','IA'),('Moscow','ID'),('Moscow','KS'),('Moscow','MI'),('Moscow','OH'),('Moscow','PA'),('Moscow','TN'),('Moscow','TX'),('Moscow','VT'),('Moscow Mills','MO'),('Moseley','VA'),('Moselle','MS'),('Moses Lake','WA'),('Moshannon','PA'),('Mosheim','TN'),('Mosherville','MI'),('Mosier','OR'),('Mosinee','WI'),('Mosquero','NM'),('Moss','MS'),('Moss','TN'),('Moss Beach','CA'),('Moss Landing','CA'),('Moss Point','MS'),('Mossville','IL'),('Mossy Head','FL'),('Mossyrock','WA'),('Motley','MN'),('Mott','ND'),('Mottville','NY'),('Moulton','AL'),('Moulton','IA'),('Moulton','TX'),('Moultonborough','NH'),('Moultrie','GA'),('Mound','MN'); + +INSERT INTO [cities] VALUES ('Mound','TX'),('Mound Bayou','MS'),('Mound City','IL'),('Mound City','KS'),('Mound City','MO'),('Mound City','SD'),('Mound Valley','KS'),('Moundridge','KS'),('Mounds','IL'),('Mounds','OK'),('Moundsville','WV'),('Moundville','AL'),('Moundville','MO'),('Mount Aetna','PA'),('Mount Airy','GA'),('Mount Airy','LA'),('Mount Airy','MD'),('Mount Airy','NC'),('Mount Alto','WV'),('Mount Angel','OR'),('Mount Arlington','NJ'),('Mount Auburn','IA'),('Mount Auburn','IL'),('Mount Aukum','CA'),('Mount Ayr','IA'),('Mount Ayr','IN'),('Mount Berry','GA'),('Mount Bethel','PA'),('Mount Blanchard','OH'),('Mount Braddock','PA'),('Mount Calm','TX'),('Mount Calvary','WI'),('Mount Carbon','WV'),('Mount Carmel','IL'),('Mount Carmel','PA'),('Mount Carmel','SC'),('Mount Carmel','TN'),('Mount Carmel','UT'),('Mount Carroll','IL'),('Mount Clare','WV'),('Mount Clemens','MI'),('Mount Cory','OH'),('Mount Crawford','VA'),('Mount Croghan','SC'),('Mount Desert','ME'),('Mount Dora','FL'),('Mount Eaton','OH'),('Mount Eden','KY'),('Mount Enterprise','TX'),('Mount Ephraim','NJ'),('Mount Erie','IL'),('Mount Freedom','NJ'),('Mount Gay','WV'),('Mount Gilead','NC'),('Mount Gilead','OH'),('Mount Gretna','PA'),('Mount Hamilton','CA'),('Mount Hermon','CA'),('Mount Hermon','KY'),('Mount Hermon','LA'),('Mount Holly','AR'),('Mount Holly','NC'),('Mount Holly','NJ'),('Mount Holly','VA'),('Mount Holly','VT'),('Mount Holly Springs','PA'),('Mount Hood Parkdale','OR'),('Mount Hope','AL'),('Mount Hope','KS'),('Mount Hope','OH'),('Mount Hope','WI'),('Mount Hope','WV'),('Mount Horeb','WI'),('Mount Ida','AR'),('Mount Jackson','VA'),('Mount Jewett','PA'),('Mount Joy','PA'),('Mount Judea','AR'),('Mount Juliet','TN'),('Mount Kisco','NY'),('Mount Laguna','CA'),('Mount Laurel','NJ'),('Mount Lemmon','AZ'),('Mount Liberty','OH'),('Mount Lookout','WV'),('Mount Marion','NY'),('Mount Meigs','AL'),('Mount Morris','IL'),('Mount Morris','MI'),('Mount Morris','NY'),('Mount Morris','PA'),('Mount Mourne','NC'),('Mount Nebo','WV'),('Mount Olive','AL'),('Mount Olive','IL'),('Mount Olive','MS'),('Mount Olive','NC'),('Mount Olive','WV'),('Mount Olivet','KY'),('Mount Orab','OH'),('Mount Perry','OH'),('Mount Pleasant','AR'),('Mount Pleasant','IA'),('Mount Pleasant','MI'),('Mount Pleasant','MS'),('Mount Pleasant','NC'),('Mount Pleasant','OH'),('Mount Pleasant','PA'),('Mount Pleasant','SC'),('Mount Pleasant','TN'),('Mount Pleasant','TX'),('Mount Pleasant','UT'),('Mount Pleasant Mills','PA'),('Mount Pocono','PA'),('Mount Prospect','IL'),('Mount Pulaski','IL'),('Mount Rainier','MD'),('Mount Royal','NJ'),('Mount Saint Francis','IN'),('Mount Saint Joseph','OH'),('Mount Savage','MD'),('Mount Shasta','CA'),('Mount Sherman','KY'),('Mount Sidney','VA'),('Mount Sinai','NY'),('Mount Solon','VA'),('Mount Sterling','IA'),('Mount Sterling','IL'),('Mount Sterling','KY'),('Mount Sterling','MO'),('Mount Sterling','OH'),('Mount Sterling','WI'),('Mount Storm','WV'),('Mount Summit','IN'),('Mount Tabor','NJ'),('Mount Tremper','NY'),('Mount Ulla','NC'),('Mount Union','IA'),('Mount Union','PA'),('Mount Upton','NY'),('Mount Vernon','AL'),('Mount Vernon','AR'),('Mount Vernon','GA'),('Mount Vernon','IA'),('Mount Vernon','IL'),('Mount Vernon','IN'),('Mount Vernon','KY'),('Mount Vernon','ME'),('Mount Vernon','MO'),('Mount Vernon','NY'),('Mount Vernon','OH'),('Mount Vernon','OR'),('Mount Vernon','SD'),('Mount Vernon','TX'),('Mount Vernon','VA'),('Mount Vernon','WA'),('Mount Victoria','MD'),('Mount Victory','OH'),('Mount Vision','NY'),('Mount Washington','KY'),('Mount Washington','NH'),('Mount Wilson','CA'),('Mount Wolf','PA'),('Mount Zion','GA'),('Mount Zion','WV'),('Mountain','ND'),('Mountain','WI'),('Mountain Center','CA'),('Mountain City','GA'),('Mountain City','NV'),('Mountain City','TN'),('Mountain Dale','NY'),('Mountain Grove','MO'),('Mountain Home','AR'),('Mountain Home','ID'),('Mountain Home','NC'),('Mountain Home','TN'),('Mountain Home','TX'),('Mountain Home','UT'),('Mountain Home A F B','ID'),('Mountain Iron','MN'),('Mountain Lake','MN'),('Mountain Lakes','NJ'),('Mountain Park','OK'),('Mountain Pass','CA'),('Mountain Pine','AR'),('Mountain Ranch','CA'),('Mountain Rest','SC'),('Mountain Top','PA'),('Mountain View','AR'),('Mountain View','CA'),('Mountain View','HI'),('Mountain View','MO'),('Mountain View','OK'),('Mountain View','WY'),('Mountain Village','AK'),('Mountainair','NM'),('Mountainburg','AR'),('Mountainhome','PA'),('Mountainside','NJ'),('Mountainville','NY'),('Mountlake Terrace','WA'),('Mountville','PA'),('Mountville','SC'),('Mousie','KY'),('Mouth Of Wilson','VA'),('Mouthcard','KY'),('Moville','IA'),('Moweaqua','IL'),('Mowrystown','OH'),('Moxahala','OH'),('Moxee','WA'),('Moyers','OK'),('Moyie Springs','ID'),('Moyock','NC'),('Mozelle','KY'),('Mozier','IL'),('Mt Baldy','CA'),('Mt Zion','IL'),('Mud Butte','SD'),('Muddy','IL'),('Muenster','TX'),('Muir','MI'),('Muir','PA'),('Mukilteo','WA'),('Mukwonago','WI'),('Mulberry','AR'),('Mulberry','FL'),('Mulberry','IN'),('Mulberry','KS'),('Mulberry','TN'),('Mulberry Grove','IL'),('Muldoon','TX'),('Muldraugh','KY'),('Muldrow','OK'),('Mule Creek','NM'),('Muleshoe','TX'),('Mulga','AL'),('Mulhall','OK'),('Mulino','OR'),('Mulkeytown','IL'),('Mullan','ID'),('Mullen','NE'),('Mullens','WV'),('Mullett Lake','MI'),('Mullica Hill','NJ'),('Mulliken','MI'),('Mullin','TX'),('Mullins','SC'),('Mullinville','KS'),('Mulvane','KS'),('Mumford','NY'),('Mumford','TX'),('Muncie','IL'),('Muncie','IN'),('Muncy','PA'),('Muncy Valley','PA'),('Munday','TX'),('Munday','WV'),('Mundelein','IL'),('Munden','KS'),('Munds Park','AZ'),('Munford','AL'),('Munford','TN'),('Munfordville','KY'),('Munger','MI'),('Munich','ND'),('Munising','MI'),('Munith','MI'),('Munnsville','NY'),('Munroe Falls','OH'),('Munson','PA'),('Munster','IN'),('Murchison','TX'),('Murdo','SD'),('Murdock','FL'),('Murdock','IL'),('Murdock','KS'),('Murdock','MN'),('Murdock','NE'),('Murfreesboro','AR'),('Murfreesboro','NC'),('Murfreesboro','TN'),('Murphy','ID'),('Murphy','NC'),('Murphy','OR'),('Murphys','CA'),('Murphysboro','IL'),('Murray','IA'),('Murray','ID'),('Murray','KY'),('Murray','NE'),('Murray City','OH'),('Murrayville','GA'),('Murrayville','IL'),('Murrells Inlet','SC'),('Murrieta','CA'),('Murrysville','PA'),('Murtaugh','ID'),('Muscadine','AL'),('Muscatine','IA'),('Muscle Shoals','AL'),('Muscoda','WI'),('Muscotah','KS'),('Muse','OK'),('Muse','PA'),('Musella','GA'),('Muses Mills','KY'),('Muskego','WI'),('Muskegon','MI'),('Muskogee','OK'),('Musselshell','MT'),('Mustang','OK'),('Mustoe','VA'),('Mutual','OK'),('Myakka City','FL'),('Myers Flat','CA'),('Myerstown','PA'),('Myersville','MD'),('Mylo','ND'),('Myra','KY'),('Myra','TX'),('Myra','WV'),('Myrtle','MO'),('Myrtle','MS'),('Myrtle Beach','SC'),('Myrtle Creek','OR'),('Myrtle Point','OR'),('Myrtlewood','AL'),('Mystic','CT'),('Mystic','GA'),('Mystic','IA'),('Myton','UT'),('Naalehu','HI'),('Nabb','IN'),('Naches','WA'),('Nachusa','IL'),('Naco','AZ'),('Nacogdoches','TX'),('Nada','TX'),('Nadeau','MI'),('Nageezi','NM'),('Nags Head','NC'),('Naguabo','PR'),('Nahant','MA'),('Nahcotta','WA'),('Nahma','MI'),('Nahunta','GA'),('Nakina','NC'),('Naknek','AK'),('Nalcrest','FL'),('Nallen','WV'),('Nampa','ID'),('Nanafalia','AL'),('Nancy','KY'),('Nanjemoy','MD'),('Nankin','OH'),('Nanticoke','MD'),('Nanticoke','PA'),('Nantucket','MA'),('Nanty Glo','PA'),('Nanuet','NY'),('Naoma','WV'),('Napa','CA'),('Napakiak','AK'),('Napanoch','NY'),('Napavine','WA'),('Naper','NE'),('Naperville','IL'),('Napier','WV'),('Naples','FL'),('Naples','ID'),('Naples','ME'),('Naples','NC'),('Naples','NY'),('Naples','TX'),('Napoleon','IN'),('Napoleon','MI'),('Napoleon','MO'),('Napoleon','ND'),('Napoleon','OH'),('Napoleonville','LA'),('Naponee','NE'),('Nappanee','IN'),('Nara Visa','NM'),('Naranjito','PR'),('Narberth','PA'),('Nardin','OK'),('Narka','KS'),('Narragansett','RI'),('Narrows','VA'),('Narrowsburg','NY'),('Naruna','VA'),('Narvon','PA'),('Naselle','WA'),('Nash','OK'),('Nash','TX'),('Nashoba','OK'),('Nashotah','WI'),('Nashport','OH'),('Nashua','IA'),('Nashua','MN'),('Nashua','MT'),('Nashua','NH'),('Nashville','AR'),('Nashville','GA'),('Nashville','IL'),('Nashville','IN'),('Nashville','KS'),('Nashville','MI'),('Nashville','NC'),('Nashville','OH'),('Nashville','TN'),('Nashwauk','MN'),('Nason','IL'),('Nassau','DE'),('Nassau','NY'),('Nassawadox','VA'),('Natalbany','LA'),('Natalia','TX'),('Natchez','LA'),('Natchez','MS'),('Natchitoches','LA'),('Nathalie','VA'),('Nathrop','CO'),('Natick','MA'),('National City','CA'),('National City','MI'),('National Mine','MI'),('National Park','NJ'),('National Stock Yards','IL'),('Natoma','KS'),('Natrona','WY'),('Natrona Heights','PA'),('Natural Bridge','AL'),('Natural Bridge','NY'),('Natural Bridge','VA'),('Natural Bridge Station','VA'),('Natural Dam','AR'),('Naturita','CO'),('Naubinway','MI'),('Naugatuck','CT'),('Naugatuck','WV'),('Nauvoo','AL'),('Nauvoo','IL'),('Navajo','NM'),('Navajo Dam','NM'),('Naval Air Station/ Jrb','TX'),('Naval Anacost Annex','DC'),('Navarre','FL'),('Navarre','MN'),('Navarre','OH'),('Navarro','CA'),('Navasota','TX'),('Navesink','NJ'),('Naylor','GA'),('Naylor','MO'),('Naytahwaush','MN'),('Nazareth','KY'),('Nazareth','MI'),('Nazareth','PA'),('Nazareth','TX'),('Nazlini','AZ'),('Neah Bay','WA'),('Neal','KS'),('Neapolis','OH'),('Neavitt','MD'),('Nebo','IL'),('Nebo','KY'),('Nebo','NC'),('Nebo','WV'),('Nebraska City','NE'),('Necedah','WI'),('Neche','ND'),('Neches','TX'),('Neck City','MO'),('Nederland','CO'),('Nederland','TX'),('Nedrow','NY'),('Needham','AL'),('Needham','IN'),('Needham','MA'),('Needham Heights','MA'),('Needles','CA'),('Needmore','PA'),('Needville','TX'),('Neely','MS'),('Neelyton','PA'),('Neelyville','MO'),('Neenah','WI'),('Neeses','SC'),('Neffs','OH'),('Neffs','PA'),('Negaunee','MI'),('Negley','OH'),('Negreet','LA'),('Nehalem','OR'),('Nehawka','NE'),('Neihart','MT'),('Neillsville','WI'); + +INSERT INTO [cities] VALUES ('Neilton','WA'),('Nekoma','KS'),('Nekoma','ND'),('Nekoosa','WI'),('Neligh','NE'),('Nellis','WV'),('Nellis Afb','NV'),('Nelliston','NY'),('Nellysford','VA'),('Nelson','CA'),('Nelson','GA'),('Nelson','MN'),('Nelson','MO'),('Nelson','NE'),('Nelson','NH'),('Nelson','PA'),('Nelson','VA'),('Nelson','WI'),('Nelsonia','VA'),('Nelsonville','OH'),('Nelsonville','WI'),('Nemacolin','PA'),('Nemaha','IA'),('Nemaha','NE'),('Nemo','SD'),('Nemo','TX'),('Nemours','WV'),('Nenana','AK'),('Nenzel','NE'),('Neodesha','KS'),('Neoga','IL'),('Neola','IA'),('Neola','UT'),('Neola','WV'),('Neon','KY'),('Neopit','WI'),('Neosho','MO'),('Neosho','WI'),('Neosho Falls','KS'),('Neosho Rapids','KS'),('Neotsu','OR'),('Nephi','UT'),('Neponset','IL'),('Neptune','NJ'),('Neptune Beach','FL'),('Nerinx','KY'),('Nerstrand','MN'),('Nesbit','MS'),('Nesconset','NY'),('Nescopeck','PA'),('Neshanic Station','NJ'),('Neshkoro','WI'),('Neskowin','OR'),('Nesmith','SC'),('Nespelem','WA'),('Nesquehoning','PA'),('Ness City','KS'),('Netarts','OR'),('Netawaka','KS'),('Netcong','NJ'),('Nett Lake','MN'),('Nettie','WV'),('Nettleton','MS'),('Nevada','IA'),('Nevada','MO'),('Nevada','OH'),('Nevada','TX'),('Nevada City','CA'),('Neversink','NY'),('Neville','OH'),('Nevis','MN'),('Nevisdale','KY'),('New Albany','IN'),('New Albany','KS'),('New Albany','MS'),('New Albany','OH'),('New Albany','PA'),('New Albin','IA'),('New Alexandria','PA'),('New Almaden','CA'),('New Athens','IL'),('New Athens','OH'),('New Auburn','MN'),('New Auburn','WI'),('New Augusta','MS'),('New Baden','IL'),('New Baden','TX'),('New Baltimore','MI'),('New Baltimore','NY'),('New Baltimore','PA'),('New Bavaria','OH'),('New Bedford','IL'),('New Bedford','MA'),('New Bedford','PA'),('New Berlin','IL'),('New Berlin','NY'),('New Berlin','PA'),('New Berlin','WI'),('New Berlinville','PA'),('New Bern','NC'),('New Bethlehem','PA'),('New Blaine','AR'),('New Bloomfield','MO'),('New Bloomfield','PA'),('New Bloomington','OH'),('New Boston','IL'),('New Boston','MI'),('New Boston','MO'),('New Boston','NH'),('New Boston','TX'),('New Braintree','MA'),('New Braunfels','TX'),('New Bremen','OH'),('New Brighton','PA'),('New Britain','CT'),('New Brockton','AL'),('New Brunswick','NJ'),('New Buffalo','MI'),('New Buffalo','PA'),('New Burnside','IL'),('New Cambria','KS'),('New Cambria','MO'),('New Canaan','CT'),('New Caney','TX'),('New Canton','IL'),('New Canton','VA'),('New Carlisle','IN'),('New Carlisle','OH'),('New Castle','AL'),('New Castle','CO'),('New Castle','DE'),('New Castle','IN'),('New Castle','KY'),('New Castle','NH'),('New Castle','PA'),('New Castle','VA'),('New Century','KS'),('New Church','VA'),('New City','NY'),('New Columbia','PA'),('New Concord','KY'),('New Concord','OH'),('New Creek','WV'),('New Cumberland','PA'),('New Cumberland','WV'),('New Cuyama','CA'),('New Deal','TX'),('New Derry','PA'),('New Douglas','IL'),('New Durham','NH'),('New Eagle','PA'),('New Edinburg','AR'),('New Effington','SD'),('New Egypt','NJ'),('New Ellenton','SC'),('New England','ND'),('New Enterprise','PA'),('New Era','MI'),('New Fairfield','CT'),('New Florence','MO'),('New Florence','PA'),('New Franken','WI'),('New Franklin','MO'),('New Freedom','PA'),('New Freeport','PA'),('New Galilee','PA'),('New Geneva','PA'),('New Germantown','PA'),('New Germany','MN'),('New Glarus','WI'),('New Gloucester','ME'),('New Goshen','IN'),('New Gretna','NJ'),('New Hampshire','OH'),('New Hampton','IA'),('New Hampton','MO'),('New Hampton','NH'),('New Hampton','NY'),('New Harbor','ME'),('New Harmony','IN'),('New Harmony','UT'),('New Hartford','CT'),('New Hartford','IA'),('New Hartford','NY'),('New Haven','CT'),('New Haven','IL'),('New Haven','IN'),('New Haven','KY'),('New Haven','MI'),('New Haven','MO'),('New Haven','NY'),('New Haven','OH'),('New Haven','VT'),('New Haven','WV'),('New Hill','NC'),('New Holland','IL'),('New Holland','OH'),('New Holland','PA'),('New Holland','SD'),('New Holstein','WI'),('New Home','TX'),('New Hope','AL'),('New Hope','KY'),('New Hope','PA'),('New Hope','VA'),('New Hudson','MI'),('New Hyde Park','NY'),('New Iberia','LA'),('New Ipswich','NH'),('New Johnsonville','TN'),('New Kensington','PA'),('New Kent','VA'),('New Kingston','NY'),('New Kingstown','PA'),('New Knoxville','OH'),('New Laguna','NM'),('New Lebanon','IN'),('New Lebanon','NY'),('New Lebanon','OH'),('New Leipzig','ND'),('New Lenox','IL'),('New Lexington','OH'),('New Liberty','IA'),('New Liberty','KY'),('New Limerick','ME'),('New Lisbon','IN'),('New Lisbon','NJ'),('New Lisbon','NY'),('New Lisbon','WI'),('New London','CT'),('New London','IA'),('New London','MN'),('New London','MO'),('New London','NC'),('New London','NH'),('New London','OH'),('New London','PA'),('New London','TX'),('New London','WI'),('New Lothrop','MI'),('New Madison','OH'),('New Madrid','MO'),('New Manchester','WV'),('New Market','AL'),('New Market','IA'),('New Market','IN'),('New Market','MD'),('New Market','MN'),('New Market','TN'),('New Market','VA'),('New Marshfield','OH'),('New Martinsville','WV'),('New Matamoras','OH'),('New Meadows','ID'),('New Melle','MO'),('New Memphis','IL'),('New Middletown','IN'),('New Middletown','OH'),('New Midway','MD'),('New Milford','CT'),('New Milford','NJ'),('New Milford','NY'),('New Milford','PA'),('New Millport','PA'),('New Milton','WV'),('New Munich','MN'),('New Munster','WI'),('New Orleans','LA'),('New Oxford','PA'),('New Palestine','IN'),('New Paltz','NY'),('New Paris','IN'),('New Paris','OH'),('New Paris','PA'),('New Park','PA'),('New Philadelphia','OH'),('New Philadelphia','PA'),('New Pine Creek','OR'),('New Plymouth','ID'),('New Plymouth','OH'),('New Point','IN'),('New Point','VA'),('New Port Richey','FL'),('New Portland','ME'),('New Prague','MN'),('New Preston Marble Dale','CT'),('New Providence','IA'),('New Providence','NJ'),('New Providence','PA'),('New Raymer','CO'),('New Richland','MN'),('New Richmond','IN'),('New Richmond','OH'),('New Richmond','WI'),('New Richmond','WV'),('New Riegel','OH'),('New Ringgold','PA'),('New River','AZ'),('New River','VA'),('New Roads','LA'),('New Rochelle','NY'),('New Rockford','ND'),('New Ross','IN'),('New Rumley','OH'),('New Russia','NY'),('New Salem','IL'),('New Salem','MA'),('New Salem','ND'),('New Salem','PA'),('New Salisbury','IN'),('New Sarpy','LA'),('New Sharon','IA'),('New Sharon','ME'),('New Site','MS'),('New Smyrna Beach','FL'),('New Springfield','OH'),('New Stanton','PA'),('New Straitsville','OH'),('New Stuyahok','AK'),('New Suffolk','NY'),('New Summerfield','TX'),('New Sweden','ME'),('New Tazewell','TN'),('New Town','MA'),('New Town','ND'),('New Trenton','IN'),('New Tripoli','PA'),('New Troy','MI'),('New Ulm','MN'),('New Ulm','TX'),('New Underwood','SD'),('New Vernon','NJ'),('New Vienna','IA'),('New Vienna','OH'),('New Vineyard','ME'),('New Virginia','IA'),('New Washington','IN'),('New Washington','OH'),('New Waterford','OH'),('New Waverly','IN'),('New Waverly','TX'),('New Weston','OH'),('New Wilmington','PA'),('New Windsor','IL'),('New Windsor','MD'),('New Windsor','NY'),('New Woodstock','NY'),('New York','NY'),('New York Mills','MN'),('New York Mills','NY'),('New Zion','SC'),('Newalla','OK'),('Newark','AR'),('Newark','CA'),('Newark','DE'),('Newark','IL'),('Newark','MD'),('Newark','MO'),('Newark','NJ'),('Newark','NY'),('Newark','OH'),('Newark','TX'),('Newark Valley','NY'),('Newaygo','MI'),('Newberg','OR'),('Newbern','AL'),('Newbern','TN'),('Newbern','VA'),('Newberry','FL'),('Newberry','IN'),('Newberry','MI'),('Newberry','SC'),('Newberry Springs','CA'),('Newborn','GA'),('Newburg','MD'),('Newburg','MO'),('Newburg','ND'),('Newburg','PA'),('Newburg','WI'),('Newburg','WV'),('Newburgh','IN'),('Newburgh','NY'),('Newbury','MA'),('Newbury','NH'),('Newbury','OH'),('Newbury','VT'),('Newbury Park','CA'),('Newburyport','MA'),('Newcastle','CA'),('Newcastle','ME'),('Newcastle','NE'),('Newcastle','OK'),('Newcastle','TX'),('Newcastle','UT'),('Newcastle','WY'),('Newcomb','MD'),('Newcomb','NM'),('Newcomb','NY'),('Newcomb','TN'),('Newcomerstown','OH'),('Newdale','ID'),('Newell','IA'),('Newell','NC'),('Newell','PA'),('Newell','SD'),('Newell','WV'),('Newellton','LA'),('Newfane','NY'),('Newfane','VT'),('Newfield','ME'),('Newfield','NJ'),('Newfield','NY'),('Newfields','NH'),('Newfolden','MN'),('Newfoundland','NJ'),('Newfoundland','PA'),('Newhall','CA'),('Newhall','IA'),('Newhall','WV'),('Newhebron','MS'),('Newhope','AR'),('Newington','CT'),('Newington','GA'),('Newington','NH'),('Newington','VA'),('Newkirk','NM'),('Newkirk','OK'),('Newland','NC'),('Newllano','LA'),('Newman','CA'),('Newman','IL'),('Newman Grove','NE'),('Newman Lake','WA'),('Newmanstown','PA'),('Newmarket','NH'),('Newnan','GA'),('Newport','AR'),('Newport','IN'),('Newport','KY'),('Newport','ME'),('Newport','MI'),('Newport','MN'),('Newport','NC'),('Newport','NE'),('Newport','NH'),('Newport','NJ'),('Newport','NY'),('Newport','OH'),('Newport','OR'),('Newport','PA'),('Newport','RI'),('Newport','TN'),('Newport','VA'),('Newport','VT'),('Newport','WA'),('Newport Beach','CA'),('Newport Center','VT'),('Newport Coast','CA'),('Newport News','VA'),('Newry','ME'),('Newry','PA'),('Newry','SC'),('Newsoms','VA'),('Newton','AL'),('Newton','GA'),('Newton','IA'),('Newton','IL'),('Newton','KS'),('Newton','MA'),('Newton','MS'),('Newton','NC'),('Newton','NH'),('Newton','NJ'),('Newton','TX'),('Newton','UT'),('Newton','WI'),('Newton','WV'),('Newton Center','MA'),('Newton Falls','NY'),('Newton Falls','OH'),('Newton Grove','NC'),('Newton Hamilton','PA'),('Newton Highlands','MA'),('Newton Junction','NH'),('Newton Lower Falls','MA'),('Newton Upper Falls','MA'),('Newtonia','MO'),('Newtonsville','OH'),('Newtonville','MA'),('Newtonville','NJ'),('Newtonville','NY'),('Newtown','CT'),('Newtown','IN'),('Newtown','MO'),('Newtown','PA'),('Newtown','VA'),('Newtown','WV'),('Newtown Square','PA'),('Newville','AL'),('Newville','PA'); + +INSERT INTO [cities] VALUES ('Ney','OH'),('Nezperce','ID'),('Niagara','ND'),('Niagara','WI'),('Niagara Falls','NY'),('Niagara University','NY'),('Niangua','MO'),('Niantic','CT'),('Niantic','IL'),('Nicasio','CA'),('Nice','CA'),('Niceville','FL'),('Nicholasville','KY'),('Nicholls','GA'),('Nichols','FL'),('Nichols','IA'),('Nichols','NY'),('Nichols','SC'),('Nichols','WI'),('Nicholson','GA'),('Nicholson','MS'),('Nicholson','PA'),('Nicholville','NY'),('Nickelsville','VA'),('Nickerson','KS'),('Nickerson','NE'),('Nicktown','PA'),('Nicolaus','CA'),('Nicollet','MN'),('Nicoma Park','OK'),('Nielsville','MN'),('Nightmute','AK'),('Nikiski','AK'),('Nikolai','AK'),('Nikolski','AK'),('Niland','CA'),('Niles','IL'),('Niles','MI'),('Niles','OH'),('Nilwood','IL'),('Nimitz','WV'),('Nimrod','MN'),('Ninde','VA'),('Nine Mile Falls','WA'),('Ninety Six','SC'),('Nineveh','IN'),('Nineveh','NY'),('Nineveh','PA'),('Ninilchik','AK'),('Ninnekah','OK'),('Ninole','HI'),('Niobe','NY'),('Niobrara','NE'),('Niota','IL'),('Niota','TN'),('Niotaze','KS'),('Nipomo','CA'),('Nipton','CA'),('Nisland','SD'),('Nisswa','MN'),('Nisula','MI'),('Nitro','WV'),('Niverville','NY'),('Niwot','CO'),('Nixa','MO'),('Nixon','NV'),('Nixon','TX'),('Noatak','AK'),('Noble','IL'),('Noble','LA'),('Noble','MO'),('Noble','OK'),('Nobleboro','ME'),('Noblesville','IN'),('Nobleton','FL'),('Nocatee','FL'),('Nocona','TX'),('Nodaway','IA'),('Noel','MO'),('Nogal','NM'),('Nogales','AZ'),('Nokesville','VA'),('Nokomis','FL'),('Nokomis','IL'),('Nolan','TX'),('Nolanville','TX'),('Nolensville','TN'),('Noma','FL'),('Nome','AK'),('Nome','ND'),('Nome','TX'),('Nonantum','MA'),('Nondalton','AK'),('Nooksack','WA'),('Noonan','ND'),('Noorvik','AK'),('Nora','IL'),('Nora','VA'),('Nora Springs','IA'),('Norborne','MO'),('Norcatur','KS'),('Norco','CA'),('Norco','LA'),('Norcross','GA'),('Norcross','MN'),('Norden','CA'),('Nordheim','TX'),('Nordland','WA'),('Nordman','ID'),('Norene','TN'),('Norfolk','CT'),('Norfolk','MA'),('Norfolk','NE'),('Norfolk','NY'),('Norfolk','VA'),('Norfork','AR'),('Norge','VA'),('Norlina','NC'),('Norma','NJ'),('Normal','AL'),('Normal','IL'),('Normalville','PA'),('Norman','AR'),('Norman','IN'),('Norman','NC'),('Norman','OK'),('Norman Park','GA'),('Normandy','TN'),('Normandy Beach','NJ'),('Normangee','TX'),('Normanna','TX'),('Normantown','WV'),('Norphlet','AR'),('Norridgewock','ME'),('Norris','IL'),('Norris','MT'),('Norris','SC'),('Norris','SD'),('Norris','TN'),('Norris City','IL'),('Norristown','GA'),('Norristown','PA'),('North','SC'),('North','VA'),('North Adams','MA'),('North Adams','MI'),('North Amherst','MA'),('North Andover','MA'),('North Anson','ME'),('North Apollo','PA'),('North Arlington','NJ'),('North Attleboro','MA'),('North Augusta','SC'),('North Aurora','IL'),('North Babylon','NY'),('North Baltimore','OH'),('North Bangor','NY'),('North Bay','NY'),('North Beach','MD'),('North Bend','NE'),('North Bend','OH'),('North Bend','OR'),('North Bend','PA'),('North Bend','WA'),('North Bennington','VT'),('North Benton','OH'),('North Bergen','NJ'),('North Berwick','ME'),('North Billerica','MA'),('North Blenheim','NY'),('North Bloomfield','OH'),('North Bonneville','WA'),('North Boston','NY'),('North Branch','MI'),('North Branch','MN'),('North Branch','NY'),('North Branford','CT'),('North Bridgton','ME'),('North Brookfield','MA'),('North Brookfield','NY'),('North Brunswick','NJ'),('North Buena Vista','IA'),('North Canton','CT'),('North Canton','OH'),('North Carrollton','MS'),('North Carver','MA'),('North Charleston','SC'),('North Chatham','MA'),('North Chatham','NY'),('North Chelmsford','MA'),('North Chicago','IL'),('North Chili','NY'),('North Clarendon','VT'),('North Collins','NY'),('North Concord','VT'),('North Conway','NH'),('North Creek','NY'),('North Dartmouth','MA'),('North Dighton','MA'),('North East','MD'),('North East','PA'),('North Eastham','MA'),('North Easton','MA'),('North Egremont','MA'),('North English','IA'),('North Evans','NY'),('North Fairfield','OH'),('North Falmouth','MA'),('North Ferrisburgh','VT'),('North Fork','CA'),('North Fork','ID'),('North Fort Myers','FL'),('North Franklin','CT'),('North Freedom','WI'),('North Garden','VA'),('North Georgetown','OH'),('North Grafton','MA'),('North Granby','CT'),('North Granville','NY'),('North Greece','NY'),('North Grosvenordale','CT'),('North Hampton','NH'),('North Hampton','OH'),('North Hartland','VT'),('North Hatfield','MA'),('North Haven','CT'),('North Haven','ME'),('North Haverhill','NH'),('North Henderson','IL'),('North Hero','VT'),('North Highlands','CA'),('North Hills','CA'),('North Hollywood','CA'),('North Hoosick','NY'),('North Houston','TX'),('North Hudson','NY'),('North Hyde Park','VT'),('North Jackson','OH'),('North Java','NY'),('North Jay','ME'),('North Judson','IN'),('North Kingstown','RI'),('North Kingsville','OH'),('North Lake','WI'),('North Lakewood','WA'),('North Las Vegas','NV'),('North Lawrence','NY'),('North Lawrence','OH'),('North Lewisburg','OH'),('North Liberty','IA'),('North Liberty','IN'),('North Lima','OH'),('North Little Rock','AR'),('North Loup','NE'),('North Manchester','IN'),('North Marshfield','MA'),('North Matewan','WV'),('North Metro','GA'),('North Miami','OK'),('North Miami Beach','FL'),('North Middletown','KY'),('North Monmouth','ME'),('North Montpelier','VT'),('North Myrtle Beach','SC'),('North Newton','KS'),('North Norwich','NY'),('North Olmsted','OH'),('North Oxford','MA'),('North Palm Beach','FL'),('North Palm Springs','CA'),('North Pembroke','MA'),('North Pitcher','NY'),('North Plains','OR'),('North Platte','NE'),('North Pole','AK'),('North Pomfret','VT'),('North Port','FL'),('North Powder','OR'),('North Pownal','VT'),('North Prairie','WI'),('North Providence','RI'),('North Reading','MA'),('North Richland Hills','TX'),('North Ridgeville','OH'),('North Rim','AZ'),('North River','NY'),('North Robinson','OH'),('North Rose','NY'),('North Royalton','OH'),('North Salem','IN'),('North Salem','NH'),('North Salem','NY'),('North Salt Lake','UT'),('North San Juan','CA'),('North Sandwich','NH'),('North Scituate','MA'),('North Scituate','RI'),('North Sioux City','SD'),('North Smithfield','RI'),('North Spring','WV'),('North Springfield','PA'),('North Springfield','VT'),('North Star','MI'),('North Star','OH'),('North Stonington','CT'),('North Stratford','NH'),('North Street','MI'),('North Sutton','NH'),('North Tazewell','VA'),('North Thetford','VT'),('North Tonawanda','NY'),('North Troy','VT'),('North Truro','MA'),('North Turner','ME'),('North Uxbridge','MA'),('North Vassalboro','ME'),('North Vernon','IN'),('North Versailles','PA'),('North Wales','PA'),('North Walpole','NH'),('North Waltham','MA'),('North Washington','IA'),('North Washington','PA'),('North Waterboro','ME'),('North Waterford','ME'),('North Webster','IN'),('North Westchester','CT'),('North Weymouth','MA'),('North Wilkesboro','NC'),('North Windham','CT'),('North Woodstock','NH'),('North Yarmouth','ME'),('North Zulch','TX'),('Northampton','MA'),('Northampton','PA'),('Northboro','IA'),('Northborough','MA'),('Northbridge','MA'),('Northbrook','IL'),('Northeast Harbor','ME'),('Northern Cambria','PA'),('Northfield','CT'),('Northfield','MA'),('Northfield','MN'),('Northfield','NJ'),('Northfield','OH'),('Northfield','VT'),('Northfield Falls','VT'),('Northford','CT'),('Northfork','WV'),('Northome','MN'),('Northpoint','PA'),('Northport','AL'),('Northport','MI'),('Northport','NY'),('Northport','WA'),('Northridge','CA'),('Northrop','MN'),('Northumberland','NH'),('Northumberland','PA'),('Northvale','NJ'),('Northville','MI'),('Northville','NY'),('Northville','SD'),('Northway','AK'),('Northwood','IA'),('Northwood','ND'),('Northwood','NH'),('Northwood','OH'),('Norton','KS'),('Norton','MA'),('Norton','TX'),('Norton','VA'),('Norton','VT'),('Norton','WV'),('Nortonville','KS'),('Nortonville','KY'),('Norvell','MI'),('Norvelt','PA'),('Norwalk','CA'),('Norwalk','CT'),('Norwalk','IA'),('Norwalk','OH'),('Norwalk','WI'),('Norway','IA'),('Norway','KS'),('Norway','ME'),('Norway','MI'),('Norway','SC'),('Norwell','MA'),('Norwich','CT'),('Norwich','KS'),('Norwich','ND'),('Norwich','NY'),('Norwich','OH'),('Norwich','VT'),('Norwood','CO'),('Norwood','GA'),('Norwood','LA'),('Norwood','MA'),('Norwood','MN'),('Norwood','MO'),('Norwood','NC'),('Norwood','NJ'),('Norwood','NY'),('Norwood','PA'),('Norwood','VA'),('Norwood Young America','MN'),('Notasulga','AL'),('Noti','OR'),('Notre Dame','IN'),('Notrees','TX'),('Nottawa','MI'),('Nottingham','MD'),('Nottingham','NH'),('Nottingham','PA'),('Nottoway','VA'),('Notus','ID'),('Nova','OH'),('Novato','CA'),('Novelty','MO'),('Novelty','OH'),('Novi','MI'),('Novice','TX'),('Novinger','MO'),('Nowata','OK'),('Noxapater','MS'),('Noxen','PA'),('Noxon','MT'),('Noyes','MN'),('Nu Mine','PA'),('Nubieber','CA'),('Nucla','CO'),('Nuevo','CA'),('Nuiqsut','AK'),('Nulato','AK'),('Numidia','PA'),('Nunam Iqua','AK'),('Nunapitchuk','AK'),('Nunda','NY'),('Nunda','SD'),('Nunez','GA'),('Nunica','MI'),('Nunn','CO'),('Nunnelly','TN'),('Nuremberg','PA'),('Nursery','TX'),('Nutley','NJ'),('Nutrioso','AZ'),('Nutting Lake','MA'),('Nuttsville','VA'),('Nyack','NY'),('Nye','MT'),('Nyssa','OR'),('O Brien','FL'),('O Brien','OR'),('O Brien','TX'),('O Fallon','IL'),('O Fallon','MO'),('O Kean','AR'),('O Neals','CA'),('Oacoma','SD'),('Oak','NE'),('Oak Bluffs','MA'),('Oak Brook','IL'),('Oak City','NC'),('Oak City','UT'),('Oak Creek','CO'),('Oak Creek','WI'),('Oak Forest','IL'),('Oak Grove','AR'),('Oak Grove','KY'),('Oak Grove','LA'),('Oak Grove','MO'),('Oak Hall','VA'),('Oak Harbor','OH'),('Oak Harbor','WA'),('Oak Hill','AL'),('Oak Hill','FL'),('Oak Hill','NY'),('Oak Hill','OH'),('Oak Island','MN'),('Oak Island','NC'),('Oak Lawn','IL'),('Oak Park','CA'),('Oak Park','IL'),('Oak Park','MI'),('Oak Park','MN'),('Oak Ridge','LA'),('Oak Ridge','MO'),('Oak Ridge','NC'),('Oak Ridge','NJ'),('Oak Ridge','PA'),('Oak Ridge','TN'),('Oak Run','CA'),('Oak Vale','MS'),('Oak View','CA'),('Oakboro','NC'),('Oakdale','CA'),('Oakdale','CT'); + +INSERT INTO [cities] VALUES ('Oakdale','IA'),('Oakdale','IL'),('Oakdale','LA'),('Oakdale','NE'),('Oakdale','NY'),('Oakdale','PA'),('Oakdale','TN'),('Oakdale','WI'),('Oakes','ND'),('Oakesdale','WA'),('Oakfield','GA'),('Oakfield','ME'),('Oakfield','NY'),('Oakfield','TN'),('Oakfield','WI'),('Oakford','IL'),('Oakford','IN'),('Oakham','MA'),('Oakhurst','CA'),('Oakhurst','NJ'),('Oakhurst','OK'),('Oakhurst','TX'),('Oakland','AR'),('Oakland','CA'),('Oakland','FL'),('Oakland','IA'),('Oakland','IL'),('Oakland','KY'),('Oakland','MD'),('Oakland','ME'),('Oakland','MI'),('Oakland','MS'),('Oakland','NE'),('Oakland','NJ'),('Oakland','OR'),('Oakland','RI'),('Oakland','TN'),('Oakland','TX'),('Oakland City','IN'),('Oakland Gardens','NY'),('Oakland Mills','PA'),('Oakley','CA'),('Oakley','ID'),('Oakley','KS'),('Oakley','MI'),('Oakley','UT'),('Oakman','AL'),('Oakman','GA'),('Oakmont','PA'),('Oakpark','VA'),('Oakridge','OR'),('Oaks','OK'),('Oaks','PA'),('Oaks Corners','NY'),('Oakton','VA'),('Oaktown','IN'),('Oakvale','WV'),('Oakville','CA'),('Oakville','CT'),('Oakville','IA'),('Oakville','IN'),('Oakville','TX'),('Oakville','WA'),('Oakwood','GA'),('Oakwood','IL'),('Oakwood','OH'),('Oakwood','OK'),('Oakwood','TX'),('Oakwood','VA'),('Oark','AR'),('Oatman','AZ'),('Oberlin','KS'),('Oberlin','LA'),('Oberlin','OH'),('Obernburg','NY'),('Oberon','ND'),('Obion','TN'),('Oblong','IL'),('Obrien','CA'),('Ocala','FL'),('Ocate','NM'),('Occidental','CA'),('Occoquan','VA'),('Ocean Beach','NY'),('Ocean Bluff','MA'),('Ocean City','MD'),('Ocean City','NJ'),('Ocean Gate','NJ'),('Ocean Grove','NJ'),('Ocean Isle Beach','NC'),('Ocean Park','ME'),('Ocean Park','WA'),('Ocean Shores','WA'),('Ocean Springs','MS'),('Ocean View','DE'),('Ocean View','HI'),('Ocean View','NJ'),('Oceana','WV'),('Oceano','CA'),('Oceanport','NJ'),('Oceanside','CA'),('Oceanside','NY'),('Oceanside','OR'),('Oceanville','NJ'),('Oceola','OH'),('Ochelata','OK'),('Ocheyedan','IA'),('Ochlocknee','GA'),('Ochopee','FL'),('Ocilla','GA'),('Ocklawaha','FL'),('Ocoee','FL'),('Ocoee','TN'),('Oconee','GA'),('Oconee','IL'),('Oconomowoc','WI'),('Oconto','NE'),('Oconto','WI'),('Oconto Falls','WI'),('Ocotillo','CA'),('Ocracoke','NC'),('Odanah','WI'),('Odd','WV'),('Odebolt','IA'),('Odell','IL'),('Odell','NE'),('Odell','OR'),('Odell','TX'),('Odem','TX'),('Oden','AR'),('Oden','MI'),('Odenton','MD'),('Odenville','AL'),('Odessa','DE'),('Odessa','FL'),('Odessa','MN'),('Odessa','MO'),('Odessa','NE'),('Odessa','NY'),('Odessa','TX'),('Odessa','WA'),('Odin','IL'),('Odin','MN'),('Odon','IN'),('Odonnell','TX'),('Odum','GA'),('Oelrichs','SD'),('Oelwein','IA'),('Offerle','KS'),('Offerman','GA'),('Offutt A F B','NE'),('Ogallah','KS'),('Ogallala','NE'),('Ogden','AR'),('Ogden','IA'),('Ogden','IL'),('Ogden','KS'),('Ogden','UT'),('Ogdensburg','NJ'),('Ogdensburg','NY'),('Ogdensburg','WI'),('Ogema','MN'),('Ogema','WI'),('Ogilvie','MN'),('Oglala','SD'),('Oglesby','IL'),('Oglesby','TX'),('Oglethorpe','GA'),('Ogunquit','ME'),('Ohatchee','AL'),('Ohio','IL'),('Ohio City','CO'),('Ohio City','OH'),('Ohiopyle','PA'),('Ohiowa','NE'),('Ohlman','IL'),('Oil City','LA'),('Oil City','PA'),('Oil Springs','KY'),('Oil Trough','AR'),('Oilmont','MT'),('Oilton','OK'),('Oilton','TX'),('Oilville','VA'),('Ojai','CA'),('Ojibwa','WI'),('Ojo Caliente','NM'),('Ojo Feliz','NM'),('Okabena','MN'),('Okahumpka','FL'),('Okanogan','WA'),('Okarche','OK'),('Okatie','SC'),('Okaton','SD'),('Okauchee','WI'),('Okawville','IL'),('Okay','OK'),('Okeana','OH'),('Okeechobee','FL'),('Okeene','OK'),('Okemah','OK'),('Okemos','MI'),('Oketo','KS'),('Oklahoma City','OK'),('Oklaunion','TX'),('Oklee','MN'),('Okmulgee','OK'),('Okoboji','IA'),('Okolona','AR'),('Okolona','MS'),('Okolona','OH'),('Okreek','SD'),('Oktaha','OK'),('Ola','AR'),('Ola','ID'),('Olalla','WA'),('Olamon','ME'),('Olancha','CA'),('Olanta','PA'),('Olanta','SC'),('Olar','SC'),('Olathe','CO'),('Olathe','KS'),('Olaton','KY'),('Olcott','NY'),('Old Appleton','MO'),('Old Bethpage','NY'),('Old Bridge','NJ'),('Old Chatham','NY'),('Old Fields','WV'),('Old Forge','NY'),('Old Forge','PA'),('Old Fort','NC'),('Old Fort','OH'),('Old Glory','TX'),('Old Greenwich','CT'),('Old Harbor','AK'),('Old Hickory','TN'),('Old Lyme','CT'),('Old Mission','MI'),('Old Monroe','MO'),('Old Mystic','CT'),('Old Ocean','TX'),('Old Orchard Beach','ME'),('Old Saybrook','CT'),('Old Station','CA'),('Old Town','FL'),('Old Town','ME'),('Old Washington','OH'),('Old Westbury','NY'),('Old Zionsville','PA'),('Olden','TX'),('Oldenburg','IN'),('Oldfield','MO'),('Oldfort','TN'),('Oldham','SD'),('Oldhams','VA'),('Olds','IA'),('Oldsmar','FL'),('Oldtown','ID'),('Oldtown','MD'),('Oldwick','NJ'),('Olean','MO'),('Olean','NY'),('Olema','CA'),('Oley','PA'),('Olga','WA'),('Olin','IA'),('Olin','NC'),('Olive','MT'),('Olive Branch','IL'),('Olive Branch','MS'),('Olive Hill','KY'),('Olivebridge','NY'),('Oliveburg','PA'),('Olivehill','TN'),('Olivehurst','CA'),('Oliver','GA'),('Oliver','PA'),('Oliver Springs','TN'),('Olivet','MI'),('Olivet','SD'),('Olivia','MN'),('Olivia','NC'),('Olla','LA'),('Ollie','IA'),('Olmito','TX'),('Olmitz','KS'),('Olmstead','KY'),('Olmsted','IL'),('Olmsted Falls','OH'),('Olmstedville','NY'),('Olney','IL'),('Olney','MD'),('Olney','MO'),('Olney','MT'),('Olney','TX'),('Olney Springs','CO'),('Olpe','KS'),('Olsburg','KS'),('Olton','TX'),('Olustee','FL'),('Olustee','OK'),('Olympia','KY'),('Olympia','WA'),('Olympia Fields','IL'),('Olympic Valley','CA'),('Olyphant','PA'),('Omaha','AR'),('Omaha','GA'),('Omaha','IL'),('Omaha','NE'),('Omaha','TX'),('Omak','WA'),('Omar','WV'),('Omega','GA'),('Omega','OK'),('Omena','MI'),('Omer','MI'),('Omro','WI'),('Ona','FL'),('Ona','WV'),('Onaga','KS'),('Onaka','SD'),('Onalaska','TX'),('Onalaska','WA'),('Onalaska','WI'),('Onamia','MN'),('Onancock','VA'),('Onarga','IL'),('Onawa','IA'),('Onaway','MI'),('Oneco','CT'),('Oneco','FL'),('Onego','WV'),('Oneida','AR'),('Oneida','IL'),('Oneida','KS'),('Oneida','KY'),('Oneida','NY'),('Oneida','PA'),('Oneida','TN'),('Oneida','WI'),('Oneill','NE'),('Onekama','MI'),('Onemo','VA'),('Oneonta','AL'),('Oneonta','NY'),('Ong','NE'),('Onia','AR'),('Onida','SD'),('Onley','VA'),('Only','TN'),('Ono','PA'),('Onondaga','MI'),('Onset','MA'),('Onslow','IA'),('Onsted','MI'),('Ontario','CA'),('Ontario','NY'),('Ontario','OH'),('Ontario','OR'),('Ontario','WI'),('Ontario Center','NY'),('Ontonagon','MI'),('Onward','IN'),('Onyx','CA'),('Ookala','HI'),('Oolitic','IN'),('Oologah','OK'),('Ooltewah','TN'),('Oostburg','WI'),('Opa Locka','FL'),('Opal','WY'),('Opdyke','IL'),('Opelika','AL'),('Opelousas','LA'),('Opheim','MT'),('Ophelia','VA'),('Ophiem','IL'),('Ophir','CO'),('Ophir','KY'),('Ophir','OR'),('Opolis','KS'),('Opp','AL'),('Oquawka','IL'),('Oquossoc','ME'),('Ora','IN'),('Oracle','AZ'),('Oradell','NJ'),('Oral','SD'),('Oran','IA'),('Oran','MO'),('Orange','CA'),('Orange','CT'),('Orange','MA'),('Orange','NJ'),('Orange','TX'),('Orange','VA'),('Orange Beach','AL'),('Orange City','FL'),('Orange City','IA'),('Orange Cove','CA'),('Orange Grove','TX'),('Orange Lake','FL'),('Orange Park','FL'),('Orange Springs','FL'),('Orangeburg','NY'),('Orangeburg','SC'),('Orangefield','TX'),('Orangevale','CA'),('Orangeville','IL'),('Orangeville','OH'),('Orangeville','PA'),('Orangeville','UT'),('Oraville','IL'),('Orbisonia','PA'),('Orcas','WA'),('Orchard','CO'),('Orchard','IA'),('Orchard','NE'),('Orchard','TX'),('Orchard Hill','GA'),('Orchard Park','NY'),('Ord','NE'),('Orderville','UT'),('Ordinary','VA'),('Ordway','CO'),('Ore City','TX'),('Oreana','IL'),('Orefield','PA'),('Oregon','IL'),('Oregon','MO'),('Oregon','OH'),('Oregon','WI'),('Oregon City','OR'),('Oregon House','CA'),('Oregonia','OH'),('Oreland','PA'),('Orem','UT'),('Orestes','IN'),('Orford','NH'),('Orfordville','WI'),('Organ','NM'),('Orgas','WV'),('Orick','CA'),('Orient','IA'),('Orient','IL'),('Orient','ME'),('Orient','NY'),('Orient','OH'),('Orient','SD'),('Orient','WA'),('Oriental','NC'),('Orinda','CA'),('Orion','IL'),('Oriska','ND'),('Oriskany','NY'),('Oriskany','VA'),('Oriskany Falls','NY'),('Orkney Springs','VA'),('Orla','TX'),('Orland','CA'),('Orland','IN'),('Orland','ME'),('Orland Park','IL'),('Orlando','FL'),('Orlando','KY'),('Orlando','OK'),('Orlando','WV'),('Orlean','VA'),('Orleans','CA'),('Orleans','IN'),('Orleans','MA'),('Orleans','MI'),('Orleans','NE'),('Orleans','VT'),('Orlinda','TN'),('Orma','WV'),('Ormond Beach','FL'),('Ormsby','MN'),('Oro Grande','CA'),('Orocovis','PR'),('Orofino','ID'),('Orogrande','NM'),('Orondo','WA'),('Orono','ME'),('Oronoco','MN'),('Oronogo','MO'),('Orosi','CA'),('Orovada','NV'),('Oroville','CA'),('Oroville','WA'),('Orr','MN'),('Orrick','MO'),('Orrington','ME'),('Orrs Island','ME'),('Orrstown','PA'),('Orrtanna','PA'),('Orrum','NC'); + +INSERT INTO [cities] VALUES ('Orrville','AL'),('Orrville','OH'),('Orson','PA'),('Orting','WA'),('Ortley','SD'),('Ortonville','MI'),('Ortonville','MN'),('Orviston','PA'),('Orwell','NY'),('Orwell','OH'),('Orwell','VT'),('Orwigsburg','PA'),('Osage','IA'),('Osage','MN'),('Osage','OK'),('Osage','WV'),('Osage','WY'),('Osage Beach','MO'),('Osage City','KS'),('Osakis','MN'),('Osawatomie','KS'),('Osborn','MO'),('Osborne','KS'),('Osburn','ID'),('Oscar','LA'),('Oscar','OK'),('Osceola','AR'),('Osceola','IA'),('Osceola','IN'),('Osceola','MO'),('Osceola','NE'),('Osceola','PA'),('Osceola','WI'),('Osceola Mills','PA'),('Osco','IL'),('Oscoda','MI'),('Osgood','IN'),('Osgood','OH'),('Oshkosh','NE'),('Oshkosh','WI'),('Oshtemo','MI'),('Oskaloosa','IA'),('Oskaloosa','KS'),('Oslo','MN'),('Osmond','NE'),('Osnabrock','ND'),('Osprey','FL'),('Osseo','MI'),('Osseo','MN'),('Osseo','WI'),('Ossian','IA'),('Ossian','IN'),('Ossineke','MI'),('Ossining','NY'),('Ossipee','NH'),('Osteen','FL'),('Osterville','MA'),('Ostrander','MN'),('Ostrander','OH'),('Oswegatchie','NY'),('Oswego','IL'),('Oswego','KS'),('Oswego','NY'),('Osyka','MS'),('Otego','NY'),('Othello','WA'),('Otho','IA'),('Otis','CO'),('Otis','KS'),('Otis','LA'),('Otis','MA'),('Otis','OR'),('Otis Orchards','WA'),('Otisco','IN'),('Otisville','MI'),('Otisville','NY'),('Otley','IA'),('Oto','IA'),('Otoe','NE'),('Otsego','MI'),('Ottawa','IL'),('Ottawa','KS'),('Ottawa','OH'),('Ottawa','WV'),('Ottawa Lake','MI'),('Otter','MT'),('Otter Creek','FL'),('Otter Lake','MI'),('Otter Rock','OR'),('Otterbein','IN'),('Ottertail','MN'),('Otterville','MO'),('Ottine','TX'),('Otto','NC'),('Otto','NY'),('Otto','WY'),('Ottosen','IA'),('Ottoville','OH'),('Ottsville','PA'),('Ottumwa','IA'),('Otway','OH'),('Otwell','IN'),('Ouaquaga','NY'),('Ouray','CO'),('Outing','MN'),('Outlook','MT'),('Outlook','WA'),('Ouzinkie','AK'),('Ovalo','TX'),('Ovando','MT'),('Overbrook','KS'),('Overbrook','OK'),('Overgaard','AZ'),('Overland Park','KS'),('Overpeck','OH'),('Overton','NE'),('Overton','NV'),('Overton','TX'),('Ovett','MS'),('Ovid','CO'),('Ovid','MI'),('Ovid','NY'),('Oviedo','FL'),('Owaneco','IL'),('Owanka','SD'),('Owasso','OK'),('Owatonna','MN'),('Owego','NY'),('Owen','WI'),('Owendale','MI'),('Owens Cross Roads','AL'),('Owensboro','KY'),('Owensburg','IN'),('Owensville','IN'),('Owensville','MO'),('Owensville','OH'),('Owenton','KY'),('Owings','MD'),('Owings Mills','MD'),('Owingsville','KY'),('Owls Head','ME'),('Owls Head','NY'),('Owosso','MI'),('Owyhee','NV'),('Oxbow','ME'),('Oxbow','NY'),('Oxbow','OR'),('Oxford','AL'),('Oxford','AR'),('Oxford','CT'),('Oxford','FL'),('Oxford','GA'),('Oxford','IA'),('Oxford','IN'),('Oxford','KS'),('Oxford','MA'),('Oxford','MD'),('Oxford','ME'),('Oxford','MI'),('Oxford','MS'),('Oxford','NC'),('Oxford','NE'),('Oxford','NJ'),('Oxford','NY'),('Oxford','OH'),('Oxford','PA'),('Oxford','WI'),('Oxford Junction','IA'),('Oxly','MO'),('Oxnard','CA'),('Oxon Hill','MD'),('Oyens','IA'),('Oyster','VA'),('Oyster Bay','NY'),('Oysterville','WA'),('Ozan','AR'),('Ozark','AL'),('Ozark','AR'),('Ozark','IL'),('Ozark','MO'),('Ozawkie','KS'),('Ozona','FL'),('Ozona','TX'),('Ozone','AR'),('Ozone Park','NY'),('Paauilo','HI'),('Pablo','MT'),('Pace','MS'),('Pachuta','MS'),('Pacific','MO'),('Pacific','WA'),('Pacific Beach','WA'),('Pacific City','OR'),('Pacific Grove','CA'),('Pacific Junction','IA'),('Pacific Palisades','CA'),('Pacifica','CA'),('Packwaukee','WI'),('Packwood','IA'),('Packwood','WA'),('Pacoima','CA'),('Pacolet','SC'),('Pacolet Mills','SC'),('Paden','OK'),('Paden City','WV'),('Padroni','CO'),('Paducah','KY'),('Paducah','TX'),('Paeonian Springs','VA'),('Page','AZ'),('Page','ND'),('Page','NE'),('Page','WV'),('Pageland','SC'),('Pageton','WV'),('Pagosa Springs','CO'),('Paguate','NM'),('Pahala','HI'),('Pahoa','HI'),('Pahokee','FL'),('Pahrump','NV'),('Paia','HI'),('Paicines','CA'),('Paige','TX'),('Paincourtville','LA'),('Painesdale','MI'),('Painesville','OH'),('Paint Bank','VA'),('Paint Lick','KY'),('Paint Rock','AL'),('Paint Rock','TX'),('Painted Post','NY'),('Painter','VA'),('Paintsville','KY'),('Paisley','FL'),('Paisley','OR'),('Pala','CA'),('Palacios','TX'),('Palatine','IL'),('Palatine Bridge','NY'),('Palatka','FL'),('Palco','KS'),('Palenville','NY'),('Palermo','CA'),('Palermo','ME'),('Palermo','ND'),('Palestine','AR'),('Palestine','IL'),('Palestine','OH'),('Palestine','TX'),('Palestine','WV'),('Palisade','CO'),('Palisade','MN'),('Palisade','NE'),('Palisades','NY'),('Palisades','WA'),('Palisades Park','NJ'),('Pall Mall','TN'),('Palm','PA'),('Palm Bay','FL'),('Palm Beach','FL'),('Palm Beach Gardens','FL'),('Palm City','FL'),('Palm Coast','FL'),('Palm Desert','CA'),('Palm Harbor','FL'),('Palm Springs','CA'),('Palmdale','CA'),('Palmdale','FL'),('Palmer','AK'),('Palmer','IA'),('Palmer','IL'),('Palmer','KS'),('Palmer','MA'),('Palmer','MI'),('Palmer','NE'),('Palmer','PR'),('Palmer','TN'),('Palmer','TX'),('Palmer Lake','CO'),('Palmerdale','AL'),('Palmersville','TN'),('Palmerton','PA'),('Palmetto','FL'),('Palmetto','GA'),('Palmetto','LA'),('Palms','MI'),('Palmyra','IL'),('Palmyra','IN'),('Palmyra','ME'),('Palmyra','MI'),('Palmyra','MO'),('Palmyra','NE'),('Palmyra','NJ'),('Palmyra','NY'),('Palmyra','PA'),('Palmyra','TN'),('Palmyra','VA'),('Palmyra','WI'),('Palo','IA'),('Palo','MI'),('Palo Alto','CA'),('Palo Cedro','CA'),('Palo Pinto','TX'),('Palo Verde','AZ'),('Palo Verde','CA'),('Paloma','IL'),('Palomar Mountain','CA'),('Palos Heights','IL'),('Palos Hills','IL'),('Palos Park','IL'),('Palos Verdes Peninsula','CA'),('Palouse','WA'),('Paluxy','TX'),('Pampa','TX'),('Pamplico','SC'),('Pamplin','VA'),('Pana','IL'),('Panaca','NV'),('Panacea','FL'),('Panama','IA'),('Panama','IL'),('Panama','NE'),('Panama','NY'),('Panama','OK'),('Panama City','FL'),('Panama City Beach','FL'),('Pandora','OH'),('Pandora','TX'),('Pangburn','AR'),('Panguitch','UT'),('Panhandle','TX'),('Panna Maria','TX'),('Panola','AL'),('Panola','OK'),('Panola','TX'),('Panora','IA'),('Panorama City','CA'),('Pansey','AL'),('Pantego','NC'),('Panther','WV'),('Panther Burn','MS'),('Paola','KS'),('Paoli','CO'),('Paoli','IN'),('Paoli','OK'),('Paoli','PA'),('Paonia','CO'),('Papaaloa','HI'),('Papaikou','HI'),('Papillion','NE'),('Papineau','IL'),('Parachute','CO'),('Paradis','LA'),('Paradise','CA'),('Paradise','KS'),('Paradise','MI'),('Paradise','MT'),('Paradise','PA'),('Paradise','TX'),('Paradise','UT'),('Paradise Inn','WA'),('Paradise Valley','AZ'),('Paradise Valley','NV'),('Paradox','CO'),('Paradox','NY'),('Paragon','IN'),('Paragonah','UT'),('Paragould','AR'),('Paramount','CA'),('Paramus','NJ'),('Parchman','MS'),('Pardeeville','WI'),('Paris','AR'),('Paris','ID'),('Paris','IL'),('Paris','KY'),('Paris','ME'),('Paris','MI'),('Paris','MO'),('Paris','MS'),('Paris','OH'),('Paris','TN'),('Paris','TX'),('Paris','VA'),('Paris Crossing','IN'),('Parish','NY'),('Parishville','NY'),('Park','KS'),('Park City','KY'),('Park City','MT'),('Park City','UT'),('Park Falls','WI'),('Park Forest','IL'),('Park Hall','MD'),('Park Hill','OK'),('Park Hills','MO'),('Park Rapids','MN'),('Park Ridge','IL'),('Park Ridge','NJ'),('Park River','ND'),('Park Valley','UT'),('Parkdale','AR'),('Parker','AZ'),('Parker','CO'),('Parker','ID'),('Parker','KS'),('Parker','PA'),('Parker','SD'),('Parker','WA'),('Parker City','IN'),('Parker Dam','CA'),('Parker Ford','PA'),('Parkers Lake','KY'),('Parkers Prairie','MN'),('Parkersburg','IA'),('Parkersburg','IL'),('Parkersburg','WV'),('Parkesburg','PA'),('Parkhill','PA'),('Parkin','AR'),('Parkman','OH'),('Parkman','WY'),('Parks','AR'),('Parks','AZ'),('Parks','NE'),('Parksley','VA'),('Parkston','SD'),('Parksville','KY'),('Parksville','NY'),('Parksville','SC'),('Parkton','MD'),('Parkton','NC'),('Parkville','MD'),('Parlier','CA'),('Parlin','CO'),('Parlin','NJ'),('Parma','ID'),('Parma','MI'),('Parma','MO'),('Parmele','NC'),('Parmelee','SD'),('Parnell','IA'),('Parnell','MO'),('Paron','AR'),('Parowan','UT'),('Parrish','AL'),('Parrish','FL'),('Parrott','GA'),('Parrott','VA'),('Parrottsville','TN'),('Parryville','PA'),('Parshall','CO'),('Parshall','ND'),('Parsippany','NJ'),('Parsons','KS'),('Parsons','TN'),('Parsons','WV'),('Parsonsburg','MD'),('Parsonsfield','ME'),('Parthenon','AR'),('Partlow','VA'),('Partridge','KS'),('Partridge','KY'),('Pasadena','CA'),('Pasadena','MD'),('Pasadena','TX'),('Pascagoula','MS'),('Pasco','WA'),('Pascoag','RI'),('Paskenta','CA'),('Paso Robles','CA'),('Pass Christian','MS'),('Passadumkeag','ME'),('Passaic','NJ'),('Passumpsic','VT'),('Patagonia','AZ'),('Pataskala','OH'),('Patch Grove','WI'),('Patchogue','NY'),('Pateros','WA'),('Paterson','NJ'),('Paterson','WA'),('Pathfork','KY'),('Patillas','PR'),('Patoka','IL'),('Patoka','IN'),('Paton','IA'),('Patrick','SC'),('Patrick Afb','FL'),('Patrick Springs','VA'),('Patricksburg','IN'),('Patriot','IN'),('Patriot','OH'),('Patten','ME'),('Patterson','AR'),('Patterson','CA'),('Patterson','GA'),('Patterson','IA'),('Patterson','IL'); + +INSERT INTO [cities] VALUES ('Patterson','LA'),('Patterson','MO'),('Patterson','NC'),('Patterson','NY'),('Pattersonville','NY'),('Pattison','MS'),('Pattison','TX'),('Patton','CA'),('Patton','MO'),('Patton','PA'),('Pattonsburg','MO'),('Pattonville','TX'),('Patuxent River','MD'),('Paul','ID'),('Paul Smiths','NY'),('Paulden','AZ'),('Paulding','MS'),('Paulding','OH'),('Paulina','LA'),('Paulina','OR'),('Pauline','SC'),('Paullina','IA'),('Pauls Valley','OK'),('Paulsboro','NJ'),('Pauma Valley','CA'),('Paupack','PA'),('Pavilion','NY'),('Pavillion','WY'),('Pavo','GA'),('Paw Creek','NC'),('Paw Paw','IL'),('Paw Paw','MI'),('Paw Paw','WV'),('Pawcatuck','CT'),('Pawhuska','OK'),('Pawlet','VT'),('Pawleys Island','SC'),('Pawling','NY'),('Pawnee','IL'),('Pawnee','OK'),('Pawnee','TX'),('Pawnee City','NE'),('Pawnee Rock','KS'),('Pawtucket','RI'),('Pax','WV'),('Paxico','KS'),('Paxinos','PA'),('Paxton','FL'),('Paxton','IL'),('Paxton','IN'),('Paxton','MA'),('Paxton','NE'),('Paxtonville','PA'),('Payette','ID'),('Payne','OH'),('Paynes Creek','CA'),('Paynesville','MN'),('Paynesville','WV'),('Payneville','KY'),('Payson','AZ'),('Payson','IL'),('Payson','UT'),('Pe Ell','WA'),('Pea Ridge','AR'),('Peabody','KS'),('Peabody','MA'),('Peace Dale','RI'),('Peace Valley','MO'),('Peach Bottom','PA'),('Peach Creek','WV'),('Peach Glen','PA'),('Peach Orchard','AR'),('Peach Springs','AZ'),('Peacham','VT'),('Peachland','NC'),('Peachtree City','GA'),('Peak','SC'),('Peaks Island','ME'),('Peapack','NJ'),('Pearblossom','CA'),('Pearce','AZ'),('Pearces','MI'),('Pearcy','AR'),('Pearisburg','VA'),('Pearl','IL'),('Pearl','MS'),('Pearl City','HI'),('Pearl City','IL'),('Pearl Harbor','HI'),('Pearl River','LA'),('Pearl River','NY'),('Pearland','TX'),('Pearlington','MS'),('Pearsall','TX'),('Pearson','GA'),('Pearson','WI'),('Pease','MN'),('Peaster','TX'),('Pebble Beach','CA'),('Pecan Gap','TX'),('Pecatonica','IL'),('Peck','ID'),('Peck','KS'),('Peck','MI'),('Pecks Mill','WV'),('Peckville','PA'),('Peconic','NY'),('Pecos','NM'),('Pecos','TX'),('Peculiar','MO'),('Pedricktown','NJ'),('Pedro','OH'),('Pedro Bay','AK'),('Peebles','OH'),('Peekskill','NY'),('Peel','AR'),('Peerless','MT'),('Peetz','CO'),('Peever','SD'),('Peggs','OK'),('Peggy','TX'),('Pegram','TN'),('Pekin','IL'),('Pekin','IN'),('Pekin','ND'),('Pelahatchie','MS'),('Pelham','AL'),('Pelham','GA'),('Pelham','NC'),('Pelham','NH'),('Pelham','NY'),('Pelham','TN'),('Pelican','AK'),('Pelican','LA'),('Pelican Lake','WI'),('Pelican Rapids','MN'),('Pelion','SC'),('Pelkie','MI'),('Pell City','AL'),('Pell Lake','WI'),('Pella','IA'),('Pellston','MI'),('Pellville','KY'),('Pelsor','AR'),('Pelzer','SC'),('Pemaquid','ME'),('Pemberton','MN'),('Pemberton','NJ'),('Pemberton','OH'),('Pemberville','OH'),('Pembina','ND'),('Pembine','WI'),('Pembroke','GA'),('Pembroke','KY'),('Pembroke','MA'),('Pembroke','ME'),('Pembroke','NC'),('Pembroke','VA'),('Pembroke Pines','FL'),('Pen Argyl','PA'),('Pena Blanca','NM'),('Penasco','NM'),('Pence Springs','WV'),('Pencil Bluff','AR'),('Pender','NE'),('Pendergrass','GA'),('Pendleton','IN'),('Pendleton','KY'),('Pendleton','NC'),('Pendleton','OR'),('Pendleton','SC'),('Pendleton','TX'),('Pendroy','MT'),('Penelope','TX'),('Penfield','IL'),('Penfield','NY'),('Penfield','PA'),('Pengilly','MN'),('Penhook','VA'),('Peninsula','OH'),('Penitas','TX'),('Penland','NC'),('Penn','ND'),('Penn','PA'),('Penn Laird','VA'),('Penn Run','PA'),('Penn Valley','CA'),('Penn Yan','NY'),('Pennellville','NY'),('Penney Farms','FL'),('Penngrove','CA'),('Pennington','AL'),('Pennington','MN'),('Pennington','NJ'),('Pennington','TX'),('Pennington Gap','VA'),('Pennock','MN'),('Penns Creek','PA'),('Penns Grove','NJ'),('Penns Park','PA'),('Pennsauken','NJ'),('Pennsboro','WV'),('Pennsburg','PA'),('Pennsville','NJ'),('Pennsylvania Furnace','PA'),('Pennville','IN'),('Penobscot','ME'),('Penokee','KS'),('Penrose','CO'),('Penrose','NC'),('Penryn','CA'),('Penryn','PA'),('Pensacola','FL'),('Pentress','WV'),('Pentwater','MI'),('Penuelas','PR'),('Penwell','TX'),('Peoa','UT'),('Peoria','AZ'),('Peoria','IL'),('Peoria Heights','IL'),('Peosta','IA'),('Peotone','IL'),('Pep','NM'),('Pep','TX'),('Pepeekeo','HI'),('Pepin','WI'),('Pepperell','MA'),('Pequabuck','CT'),('Pequannock','NJ'),('Pequea','PA'),('Pequot Lakes','MN'),('Peralta','NM'),('Percival','IA'),('Percy','IL'),('Perdido','AL'),('Perdue Hill','AL'),('Perham','ME'),('Perham','MN'),('Peridot','AZ'),('Perkasie','PA'),('Perkins','GA'),('Perkins','MI'),('Perkins','MO'),('Perkins','OK'),('Perkinston','MS'),('Perkinsville','NY'),('Perkinsville','VT'),('Perkiomenville','PA'),('Perks','IL'),('Perley','MN'),('Perote','AL'),('Perrin','TX'),('Perrinton','MI'),('Perris','CA'),('Perronville','MI'),('Perry','AR'),('Perry','FL'),('Perry','GA'),('Perry','IA'),('Perry','IL'),('Perry','KS'),('Perry','LA'),('Perry','ME'),('Perry','MI'),('Perry','MO'),('Perry','NY'),('Perry','OH'),('Perry','OK'),('Perry Hall','MD'),('Perry Park','KY'),('Perry Point','MD'),('Perryman','MD'),('Perryopolis','PA'),('Perrysburg','NY'),('Perrysburg','OH'),('Perrysville','IN'),('Perrysville','OH'),('Perryton','TX'),('Perryville','AK'),('Perryville','AR'),('Perryville','KY'),('Perryville','MD'),('Perryville','MO'),('Pershing','IN'),('Persia','IA'),('Perth','ND'),('Perth Amboy','NJ'),('Peru','IA'),('Peru','IL'),('Peru','IN'),('Peru','KS'),('Peru','ME'),('Peru','NE'),('Peru','NY'),('Peru','VT'),('Pescadero','CA'),('Peshastin','WA'),('Peshtigo','WI'),('Pesotum','IL'),('Petaca','NM'),('Petal','MS'),('Petaluma','CA'),('Peterboro','NY'),('Peterborough','NH'),('Peterman','AL'),('Petersburg','AK'),('Petersburg','IL'),('Petersburg','IN'),('Petersburg','KY'),('Petersburg','MI'),('Petersburg','ND'),('Petersburg','NE'),('Petersburg','NY'),('Petersburg','OH'),('Petersburg','PA'),('Petersburg','TN'),('Petersburg','TX'),('Petersburg','VA'),('Petersburg','WV'),('Petersham','MA'),('Peterson','AL'),('Peterson','IA'),('Peterson','MN'),('Peterstown','WV'),('Petoskey','MI'),('Petrey','AL'),('Petrified Forest Natl Pk','AZ'),('Petroleum','IN'),('Petroleum','WV'),('Petrolia','CA'),('Petrolia','PA'),('Petrolia','TX'),('Petros','TN'),('Pettibone','ND'),('Pettigrew','AR'),('Pettisville','OH'),('Pettus','TX'),('Petty','TX'),('Pevely','MO'),('Pewamo','MI'),('Pewaukee','WI'),('Pewee Valley','KY'),('Peyton','CO'),('Peytona','WV'),('Pfafftown','NC'),('Pfeifer','KS'),('Pflugerville','TX'),('Pharr','TX'),('Pheba','MS'),('Phelan','CA'),('Phelps','KY'),('Phelps','NY'),('Phelps','WI'),('Phenix','VA'),('Phenix City','AL'),('Phil Campbell','AL'),('Philadelphia','MO'),('Philadelphia','MS'),('Philadelphia','NY'),('Philadelphia','PA'),('Philadelphia','TN'),('Philip','SD'),('Philipp','MS'),('Philippi','WV'),('Philipsburg','MT'),('Philipsburg','PA'),('Phillips','ME'),('Phillips','NE'),('Phillips','WI'),('Phillipsburg','KS'),('Phillipsburg','MO'),('Phillipsburg','NJ'),('Phillipsburg','OH'),('Phillipsport','NY'),('Phillipsville','CA'),('Philmont','NY'),('Philo','CA'),('Philo','IL'),('Philo','OH'),('Philomath','OR'),('Philomont','VA'),('Philpot','KY'),('Phippsburg','CO'),('Phippsburg','ME'),('Phlox','WI'),('Phoenicia','NY'),('Phoenix','AZ'),('Phoenix','MD'),('Phoenix','NY'),('Phoenix','OR'),('Phoenixville','PA'),('Phyllis','KY'),('Piasa','IL'),('Picabo','ID'),('Picacho','AZ'),('Picacho','NM'),('Picatinny Arsenal','NJ'),('Picayune','MS'),('Picher','OK'),('Pickens','AR'),('Pickens','MS'),('Pickens','OK'),('Pickens','SC'),('Pickens','WV'),('Pickerel','WI'),('Pickering','MO'),('Pickerington','OH'),('Pickett','WI'),('Pickford','MI'),('Pickrell','NE'),('Pickstown','SD'),('Pickton','TX'),('Pickwick Dam','TN'),('Pico Rivera','CA'),('Picture Rocks','PA'),('Pie Town','NM'),('Piedmont','AL'),('Piedmont','CA'),('Piedmont','KS'),('Piedmont','MO'),('Piedmont','OH'),('Piedmont','OK'),('Piedmont','SC'),('Piedmont','SD'),('Piedmont','WV'),('Piedra','CA'),('Pierce','CO'),('Pierce','ID'),('Pierce','NE'),('Pierce','TX'),('Pierce City','MO'),('Piercefield','NY'),('Pierceton','IN'),('Pierceville','IN'),('Pierceville','KS'),('Piercy','CA'),('Piermont','NH'),('Piermont','NY'),('Pierpont','OH'),('Pierpont','SD'),('Pierre','SD'),('Pierre Part','LA'),('Pierrepont Manor','NY'),('Pierron','IL'),('Pierson','FL'),('Pierson','IA'),('Pierson','MI'),('Pierz','MN'),('Piffard','NY'),('Pigeon','MI'),('Pigeon Falls','WI'),('Pigeon Forge','TN'),('Piggott','AR'),('Pike','NH'),('Pike','NY'),('Pike Road','AL'),('Pikesville','MD'),('Piketon','OH'),('Pikeville','KY'),('Pikeville','NC'),('Pikeville','TN'),('Pilger','NE'),('Pilgrim','KY'),('Pilgrims Knob','VA'),('Pillager','MN'),('Pillow','PA'),('Pillsbury','ND'),('Pilot','VA'),('Pilot Grove','IA'),('Pilot Grove','MO'),('Pilot Hill','CA'),('Pilot Knob','MO'),('Pilot Mound','IA'),('Pilot Mountain','NC'),('Pilot Point','AK'),('Pilot Point','TX'),('Pilot Rock','OR'),('Pilot Station','AK'),('Pilottown','LA'),('Pima','AZ'),('Pimento','IN'),('Pinch','WV'),('Pinckard','AL'),('Pinckney','MI'),('Pinckneyville','IL'),('Pinconning','MI'),('Pindall','AR'),('Pine','AZ'),('Pine','CO'),('Pine Apple','AL'),('Pine Beach','NJ'),('Pine Bluff','AR'),('Pine Bluffs','WY'),('Pine Brook','NJ'),('Pine Bush','NY'),('Pine City','MN'),('Pine City','NY'),('Pine Forge','PA'),('Pine Grove','CA'); + +INSERT INTO [cities] VALUES ('Pine Grove','LA'),('Pine Grove','PA'),('Pine Grove','WV'),('Pine Grove Mills','PA'),('Pine Hall','NC'),('Pine Hill','AL'),('Pine Hill','NY'),('Pine Island','MN'),('Pine Island','NY'),('Pine Knot','KY'),('Pine Lake','GA'),('Pine Level','AL'),('Pine Level','NC'),('Pine Meadow','CT'),('Pine Mountain','GA'),('Pine Mountain Valley','GA'),('Pine Plains','NY'),('Pine Prairie','LA'),('Pine Ridge','KY'),('Pine Ridge','SD'),('Pine River','MN'),('Pine River','WI'),('Pine Top','KY'),('Pine Valley','CA'),('Pine Valley','NY'),('Pine Valley','UT'),('Pine Village','IN'),('Pinebluff','NC'),('Pinecliffe','CO'),('Pinecrest','CA'),('Pinedale','AZ'),('Pinedale','WY'),('Pinehill','NM'),('Pinehurst','GA'),('Pinehurst','ID'),('Pinehurst','MA'),('Pinehurst','NC'),('Pinehurst','TX'),('Pineland','FL'),('Pineland','SC'),('Pineland','TX'),('Pinellas Park','FL'),('Pineola','NC'),('Pinesdale','MT'),('Pinetop','AZ'),('Pinetops','NC'),('Pinetown','NC'),('Pinetta','FL'),('Pineview','GA'),('Pineville','AR'),('Pineville','KY'),('Pineville','LA'),('Pineville','MO'),('Pineville','NC'),('Pineville','PA'),('Pineville','SC'),('Pineville','WV'),('Pinewood','SC'),('Piney Creek','NC'),('Piney Flats','TN'),('Piney Fork','OH'),('Piney Point','MD'),('Piney River','VA'),('Piney View','WV'),('Piney Woods','MS'),('Pingree','ID'),('Pingree','ND'),('Pink Hill','NC'),('Pinnacle','NC'),('Pinola','MS'),('Pinole','CA'),('Pinon','AZ'),('Pinon','NM'),('Pinon Hills','CA'),('Pinopolis','SC'),('Pinos Altos','NM'),('Pinson','AL'),('Pinson','TN'),('Pinsonfork','KY'),('Pinto','MD'),('Pioche','NV'),('Pioneer','CA'),('Pioneer','LA'),('Pioneer','OH'),('Pioneer','TN'),('Pioneertown','CA'),('Piper City','IL'),('Pipersville','PA'),('Pipestem','WV'),('Pipestone','MN'),('Pippa Passes','KY'),('Piqua','KS'),('Piqua','OH'),('Pirtleville','AZ'),('Piru','CA'),('Piscataway','NJ'),('Piseco','NY'),('Pisek','ND'),('Pisgah','AL'),('Pisgah','IA'),('Pisgah Forest','NC'),('Pismo Beach','CA'),('Pitcairn','PA'),('Pitcher','NY'),('Pitkin','CO'),('Pitkin','LA'),('Pitman','NJ'),('Pitman','PA'),('Pitsburg','OH'),('Pitts','GA'),('Pittsboro','IN'),('Pittsboro','MS'),('Pittsboro','NC'),('Pittsburg','CA'),('Pittsburg','IL'),('Pittsburg','KS'),('Pittsburg','KY'),('Pittsburg','MO'),('Pittsburg','NH'),('Pittsburg','OK'),('Pittsburg','TX'),('Pittsburgh','PA'),('Pittsfield','IL'),('Pittsfield','MA'),('Pittsfield','ME'),('Pittsfield','NH'),('Pittsfield','PA'),('Pittsfield','VT'),('Pittsford','MI'),('Pittsford','NY'),('Pittsford','VT'),('Pittston','PA'),('Pittstown','NJ'),('Pittsview','AL'),('Pittsville','MD'),('Pittsville','VA'),('Pittsville','WI'),('Pixley','CA'),('Placedo','TX'),('Placentia','CA'),('Placerville','CA'),('Placerville','CO'),('Placerville','ID'),('Placida','FL'),('Placitas','NM'),('Plain','WI'),('Plain City','OH'),('Plain Dealing','LA'),('Plainfield','CT'),('Plainfield','IA'),('Plainfield','IL'),('Plainfield','IN'),('Plainfield','MA'),('Plainfield','NH'),('Plainfield','NJ'),('Plainfield','OH'),('Plainfield','PA'),('Plainfield','VT'),('Plainfield','WI'),('Plains','GA'),('Plains','KS'),('Plains','MT'),('Plains','TX'),('Plainsboro','NJ'),('Plainview','AR'),('Plainview','MN'),('Plainview','NE'),('Plainview','NY'),('Plainview','TX'),('Plainville','CT'),('Plainville','GA'),('Plainville','IL'),('Plainville','IN'),('Plainville','KS'),('Plainville','MA'),('Plainville','NY'),('Plainwell','MI'),('Plaistow','NH'),('Planada','CA'),('Plankinton','SD'),('Plano','IA'),('Plano','IL'),('Plano','TX'),('Plant City','FL'),('Plantersville','AL'),('Plantersville','MS'),('Plantersville','TX'),('Plantsville','CT'),('Plaquemine','LA'),('Platina','CA'),('Platinum','AK'),('Plato','MN'),('Plato','MO'),('Plato Center','IL'),('Platte','SD'),('Platte Center','NE'),('Platte City','MO'),('Plattekill','NY'),('Plattenville','LA'),('Platter','OK'),('Platteville','CO'),('Platteville','WI'),('Plattsburg','MO'),('Plattsburgh','NY'),('Plattsmouth','NE'),('Plaucheville','LA'),('Playa Del Rey','CA'),('Playas','NM'),('Plaza','ND'),('Pleasant City','OH'),('Pleasant Dale','NE'),('Pleasant Garden','NC'),('Pleasant Grove','AL'),('Pleasant Grove','AR'),('Pleasant Grove','CA'),('Pleasant Grove','UT'),('Pleasant Hall','PA'),('Pleasant Hill','CA'),('Pleasant Hill','IL'),('Pleasant Hill','LA'),('Pleasant Hill','MO'),('Pleasant Hill','NC'),('Pleasant Hill','OH'),('Pleasant Hill','OR'),('Pleasant Hill','TN'),('Pleasant Hope','MO'),('Pleasant Lake','IN'),('Pleasant Lake','MI'),('Pleasant Mills','IN'),('Pleasant Mount','PA'),('Pleasant Plain','OH'),('Pleasant Plains','AR'),('Pleasant Plains','IL'),('Pleasant Prairie','WI'),('Pleasant Ridge','MI'),('Pleasant Shade','TN'),('Pleasant Unity','PA'),('Pleasant Valley','IA'),('Pleasant Valley','NY'),('Pleasant Valley','VA'),('Pleasant View','CO'),('Pleasant View','TN'),('Pleasanton','CA'),('Pleasanton','KS'),('Pleasanton','NE'),('Pleasanton','TX'),('Pleasantville','IA'),('Pleasantville','NJ'),('Pleasantville','NY'),('Pleasantville','OH'),('Pleasantville','PA'),('Pleasureville','KY'),('Pledger','TX'),('Plentywood','MT'),('Plessis','NY'),('Plevna','KS'),('Plevna','MO'),('Plevna','MT'),('Plover','IA'),('Plover','WI'),('Pluckemin','NJ'),('Plum','TX'),('Plum Branch','SC'),('Plum City','WI'),('Plumerville','AR'),('Plummer','ID'),('Plummer','MN'),('Plummers Landing','KY'),('Plumsteadville','PA'),('Plumtree','NC'),('Plumville','PA'),('Plush','OR'),('Plymouth','CA'),('Plymouth','CT'),('Plymouth','FL'),('Plymouth','IA'),('Plymouth','IL'),('Plymouth','IN'),('Plymouth','MA'),('Plymouth','ME'),('Plymouth','MI'),('Plymouth','NC'),('Plymouth','NE'),('Plymouth','NH'),('Plymouth','NY'),('Plymouth','OH'),('Plymouth','PA'),('Plymouth','UT'),('Plymouth','VT'),('Plymouth','WA'),('Plymouth','WI'),('Plymouth Meeting','PA'),('Plympton','MA'),('Poca','WV'),('Pocahontas','AR'),('Pocahontas','IA'),('Pocahontas','IL'),('Pocahontas','MO'),('Pocahontas','MS'),('Pocahontas','TN'),('Pocahontas','VA'),('Pocasset','MA'),('Pocasset','OK'),('Pocatello','ID'),('Pocola','OK'),('Pocomoke City','MD'),('Pocono Lake','PA'),('Pocono Lake Preserve','PA'),('Pocono Manor','PA'),('Pocono Pines','PA'),('Pocono Summit','PA'),('Pocopson','PA'),('Poestenkill','NY'),('Point','TX'),('Point Arena','CA'),('Point Baker','AK'),('Point Clear','AL'),('Point Comfort','TX'),('Point Harbor','NC'),('Point Hope','AK'),('Point Lay','AK'),('Point Lookout','MO'),('Point Lookout','NY'),('Point Marion','PA'),('Point Mugu Nawc','CA'),('Point Of Rocks','MD'),('Point Of Rocks','WY'),('Point Pleasant','PA'),('Point Pleasant','WV'),('Point Pleasant Beach','NJ'),('Point Reyes Station','CA'),('Point Roberts','WA'),('Point Washington','FL'),('Pointblank','TX'),('Pointe A La Hache','LA'),('Pointe Aux Pins','MI'),('Points','WV'),('Polacca','AZ'),('Poland','IN'),('Poland','ME'),('Poland','NY'),('Polaris','MT'),('Polebridge','MT'),('Polk','MO'),('Polk','NE'),('Polk','OH'),('Polk','PA'),('Polk City','FL'),('Polk City','IA'),('Polkton','NC'),('Polkville','NC'),('Pollard','AR'),('Pollock','ID'),('Pollock','LA'),('Pollock','MO'),('Pollock','SD'),('Pollock Pines','CA'),('Pollocksville','NC'),('Pollok','TX'),('Polo','IL'),('Polo','MO'),('Polson','MT'),('Polvadera','NM'),('Pomaria','SC'),('Pomerene','AZ'),('Pomeroy','IA'),('Pomeroy','OH'),('Pomeroy','PA'),('Pomeroy','WA'),('Pomfret','CT'),('Pomfret','MD'),('Pomfret Center','CT'),('Pomona','CA'),('Pomona','IL'),('Pomona','KS'),('Pomona','MO'),('Pomona','NJ'),('Pomona','NY'),('Pomona Park','FL'),('Pompano Beach','FL'),('Pompeii','MI'),('Pompey','NY'),('Pompeys Pillar','MT'),('Pompton Lakes','NJ'),('Pompton Plains','NJ'),('Ponca','AR'),('Ponca','NE'),('Ponca City','OK'),('Ponce','PR'),('Ponce De Leon','FL'),('Ponce De Leon','MO'),('Poncha Springs','CO'),('Ponchatoula','LA'),('Pond Creek','OK'),('Pond Eddy','NY'),('Pond Gap','WV'),('Ponder','TX'),('Ponderay','ID'),('Ponderosa','NM'),('Ponemah','MN'),('Poneto','IN'),('Ponsford','MN'),('Ponte Vedra','FL'),('Ponte Vedra Beach','FL'),('Pontiac','IL'),('Pontiac','MI'),('Pontiac','MO'),('Pontotoc','MS'),('Pontotoc','TX'),('Pony','MT'),('Pool','WV'),('Poole','KY'),('Pooler','GA'),('Poolesville','MD'),('Poolville','TX'),('Pope','MS'),('Pope A F B','NC'),('Pope Valley','CA'),('Popejoy','IA'),('Poplar','MT'),('Poplar','WI'),('Poplar Bluff','MO'),('Poplar Branch','NC'),('Poplar Grove','AR'),('Poplar Grove','IL'),('Poplar Ridge','NY'),('Poplarville','MS'),('Poquonock','CT'),('Poquoson','VA'),('Porcupine','SD'),('Port Alexander','AK'),('Port Allegany','PA'),('Port Allen','LA'),('Port Alsworth','AK'),('Port Angeles','WA'),('Port Aransas','TX'),('Port Arthur','TX'),('Port Austin','MI'),('Port Barre','LA'),('Port Bolivar','TX'),('Port Byron','IL'),('Port Byron','NY'),('Port Carbon','PA'),('Port Charlotte','FL'),('Port Chester','NY'),('Port Clinton','OH'),('Port Clinton','PA'),('Port Clyde','ME'),('Port Costa','CA'),('Port Crane','NY'),('Port Deposit','MD'),('Port Edwards','WI'),('Port Elizabeth','NJ'),('Port Ewen','NY'),('Port Gamble','WA'),('Port Gibson','MS'),('Port Gibson','NY'),('Port Hadlock','WA'),('Port Haywood','VA'),('Port Heiden','AK'),('Port Henry','NY'),('Port Hope','MI'),('Port Hueneme','CA'),('Port Hueneme Cbc Base','CA'),('Port Huron','MI'),('Port Isabel','TX'),('Port Jefferson','NY'),('Port Jefferson','OH'),('Port Jefferson Station','NY'),('Port Jervis','NY'),('Port Kent','NY'),('Port Lavaca','TX'),('Port Leyden','NY'),('Port Lions','AK'),('Port Ludlow','WA'),('Port Mansfield','TX'),('Port Matilda','PA'),('Port Monmouth','NJ'),('Port Murray','NJ'),('Port Neches','TX'),('Port Norris','NJ'),('Port O Connor','TX'),('Port Orange','FL'),('Port Orchard','WA'),('Port Orford','OR'),('Port Penn','DE'),('Port Reading','NJ'),('Port Republic','MD'),('Port Republic','NJ'),('Port Republic','VA'),('Port Richey','FL'),('Port Royal','KY'),('Port Royal','PA'),('Port Royal','SC'),('Port Royal','VA'),('Port Saint Joe','FL'),('Port Saint Lucie','FL'),('Port Salerno','FL'),('Port Sanilac','MI'),('Port Sulphur','LA'); + +INSERT INTO [cities] VALUES ('Port Tobacco','MD'),('Port Townsend','WA'),('Port Trevorton','PA'),('Port Washington','NY'),('Port Washington','OH'),('Port Washington','WI'),('Port William','OH'),('Port Wing','WI'),('Portage','IN'),('Portage','ME'),('Portage','MI'),('Portage','OH'),('Portage','PA'),('Portage','UT'),('Portage','WI'),('Portage Des Sioux','MO'),('Portageville','MO'),('Portageville','NY'),('Portal','GA'),('Portal','ND'),('Portales','NM'),('Porter','ME'),('Porter','MN'),('Porter','OK'),('Porter','TX'),('Porter Corners','NY'),('Porter Ranch','CA'),('Porterdale','GA'),('Porterfield','WI'),('Porters Falls','WV'),('Porters Sideling','PA'),('Portersville','PA'),('Porterville','CA'),('Porterville','MS'),('Porthill','ID'),('Portia','AR'),('Portis','KS'),('Portland','AR'),('Portland','CT'),('Portland','IN'),('Portland','ME'),('Portland','MI'),('Portland','MO'),('Portland','ND'),('Portland','NY'),('Portland','OH'),('Portland','OR'),('Portland','PA'),('Portland','TN'),('Portland','TX'),('Portlandville','NY'),('Portola','CA'),('Portola Vally','CA'),('Portsmouth','IA'),('Portsmouth','NH'),('Portsmouth','OH'),('Portsmouth','RI'),('Portsmouth','VA'),('Portville','NY'),('Porum','OK'),('Posen','IL'),('Posen','MI'),('Posey','CA'),('Poseyville','IN'),('Post','OR'),('Post','TX'),('Post Falls','ID'),('Post Mills','VT'),('Poston','AZ'),('Postville','IA'),('Poteau','OK'),('Potecasi','NC'),('Poteet','TX'),('Poth','TX'),('Potlatch','ID'),('Potomac','IL'),('Potomac','MD'),('Potosi','MO'),('Potosi','WI'),('Potrero','CA'),('Potsdam','NY'),('Potsdam','OH'),('Potter','KS'),('Potter','NE'),('Potter','WI'),('Potter Valley','CA'),('Pottersdale','PA'),('Pottersville','MO'),('Pottersville','NJ'),('Pottersville','NY'),('Potterville','MI'),('Potts Camp','MS'),('Potts Grove','PA'),('Pottsboro','TX'),('Pottstown','PA'),('Pottsville','AR'),('Pottsville','PA'),('Pottsville','TX'),('Potwin','KS'),('Poughkeepsie','AR'),('Poughkeepsie','NY'),('Poughquag','NY'),('Poulan','GA'),('Poulsbo','WA'),('Poultney','VT'),('Pound','VA'),('Pound','WI'),('Pound Ridge','NY'),('Pounding Mill','VA'),('Poway','CA'),('Powder River','WY'),('Powder Springs','GA'),('Powder Springs','TN'),('Powderhorn','CO'),('Powderly','KY'),('Powderly','TX'),('Powderville','MT'),('Powell','MO'),('Powell','OH'),('Powell','TN'),('Powell','TX'),('Powell','WY'),('Powell Butte','OR'),('Powells Point','NC'),('Powellsville','NC'),('Powellton','WV'),('Powellville','MD'),('Power','MT'),('Powers','MI'),('Powers','OR'),('Powers Lake','ND'),('Powers Lake','WI'),('Powersite','MO'),('Powersville','MO'),('Powhatan','AR'),('Powhatan','LA'),('Powhatan','VA'),('Powhatan Point','OH'),('Powhattan','KS'),('Pownal','ME'),('Pownal','VT'),('Poy Sippi','WI'),('Poyen','AR'),('Poynette','WI'),('Poynor','TX'),('Poyntelle','PA'),('Prague','NE'),('Prague','OK'),('Prairie','MS'),('Prairie City','IA'),('Prairie City','IL'),('Prairie City','OR'),('Prairie City','SD'),('Prairie Creek','IN'),('Prairie Du Chien','WI'),('Prairie Du Rocher','IL'),('Prairie Du Sac','WI'),('Prairie Farm','WI'),('Prairie Grove','AR'),('Prairie Hill','TX'),('Prairie Home','MO'),('Prairie Lea','TX'),('Prairie View','KS'),('Prairie View','TX'),('Prairie Village','KS'),('Prairieburg','IA'),('Prairieton','IN'),('Prairieville','LA'),('Prather','CA'),('Pratt','KS'),('Pratt','WV'),('Pratts','VA'),('Prattsburgh','NY'),('Prattsville','AR'),('Prattsville','NY'),('Prattville','AL'),('Pray','MT'),('Preble','IN'),('Preble','NY'),('Preemption','IL'),('Premier','WV'),('Premium','KY'),('Premont','TX'),('Prentice','WI'),('Prentiss','MS'),('Prescott','AR'),('Prescott','AZ'),('Prescott','IA'),('Prescott','KS'),('Prescott','MI'),('Prescott','WA'),('Prescott','WI'),('Prescott Valley','AZ'),('Presho','SD'),('Presidio','TX'),('Presque Isle','ME'),('Presque Isle','MI'),('Presque Isle','WI'),('Presto','PA'),('Preston','CT'),('Preston','GA'),('Preston','IA'),('Preston','ID'),('Preston','KY'),('Preston','MD'),('Preston','MN'),('Preston','MO'),('Preston','MS'),('Preston','OK'),('Preston','WA'),('Preston Hollow','NY'),('Preston Park','PA'),('Prestonsburg','KY'),('Pretty Prairie','KS'),('Prewitt','NM'),('Price','MD'),('Price','TX'),('Price','UT'),('Pricedale','PA'),('Prichard','WV'),('Priddy','TX'),('Pride','LA'),('Prides Crossing','MA'),('Priest River','ID'),('Prim','AR'),('Primghar','IA'),('Primm Springs','TN'),('Primrose','KY'),('Primrose','NE'),('Prince','WV'),('Prince Frederick','MD'),('Prince George','VA'),('Princess Anne','MD'),('Princeton','AL'),('Princeton','CA'),('Princeton','IA'),('Princeton','ID'),('Princeton','IL'),('Princeton','IN'),('Princeton','KS'),('Princeton','KY'),('Princeton','LA'),('Princeton','MA'),('Princeton','ME'),('Princeton','MN'),('Princeton','MO'),('Princeton','NC'),('Princeton','NJ'),('Princeton','OR'),('Princeton','TX'),('Princeton','WI'),('Princeton','WV'),('Princeville','HI'),('Princeville','IL'),('Princewick','WV'),('Prineville','OR'),('Pringle','SD'),('Prinsburg','MN'),('Printer','KY'),('Prior Lake','MN'),('Pritchett','CO'),('Proberta','CA'),('Procious','WV'),('Proctor','AR'),('Proctor','MT'),('Proctor','OK'),('Proctor','TX'),('Proctor','VT'),('Proctor','WV'),('Proctorsville','VT'),('Proctorville','NC'),('Proctorville','OH'),('Progreso','TX'),('Prole','IA'),('Promise City','IA'),('Prompton','PA'),('Prophetstown','IL'),('Prospect','CT'),('Prospect','KY'),('Prospect','NY'),('Prospect','OH'),('Prospect','OR'),('Prospect','PA'),('Prospect','TN'),('Prospect','VA'),('Prospect Harbor','ME'),('Prospect Heights','IL'),('Prospect Hill','NC'),('Prospect Park','PA'),('Prosper','TX'),('Prosperity','PA'),('Prosperity','SC'),('Prosperity','WV'),('Prosser','WA'),('Protection','KS'),('Protem','MO'),('Protivin','IA'),('Provencal','LA'),('Providence','KY'),('Providence','NC'),('Providence','RI'),('Providence','UT'),('Providence Forge','VA'),('Provincetown','MA'),('Provo','UT'),('Pruden','TN'),('Prudence Island','RI'),('Prudenville','MI'),('Prudhoe Bay','AK'),('Prue','OK'),('Pryor','MT'),('Pryor','OK'),('Puckett','MS'),('Pueblo','CO'),('Pueblo Of Acoma','NM'),('Puerto Real','PR'),('Pukalani','HI'),('Pukwana','SD'),('Pulaski','GA'),('Pulaski','IA'),('Pulaski','IL'),('Pulaski','MS'),('Pulaski','NY'),('Pulaski','PA'),('Pulaski','TN'),('Pulaski','VA'),('Pulaski','WI'),('Pullman','MI'),('Pullman','WA'),('Pullman','WV'),('Pulteney','NY'),('Pultneyville','NY'),('Pungoteague','VA'),('Punta Gorda','FL'),('Punta Santiago','PR'),('Punxsutawney','PA'),('Puposky','MN'),('Purcell','MO'),('Purcell','OK'),('Purcellville','VA'),('Purchase','NY'),('Purdin','MO'),('Purdon','TX'),('Purdum','NE'),('Purdy','MO'),('Purdys','NY'),('Purgitsville','WV'),('Purlear','NC'),('Purling','NY'),('Purmela','TX'),('Pursglove','WV'),('Purvis','MS'),('Puryear','TN'),('Put In Bay','OH'),('Putnam','CT'),('Putnam','IL'),('Putnam','OK'),('Putnam','TX'),('Putnam Hall','FL'),('Putnam Station','NY'),('Putnam Valley','NY'),('Putnamville','IN'),('Putney','GA'),('Putney','KY'),('Putney','VT'),('Puunene','HI'),('Puxico','MO'),('Puyallup','WA'),('Pyatt','AR'),('Pylesville','MD'),('Pyote','TX'),('Pyrites','NY'),('Quail','TX'),('Quakake','PA'),('Quaker City','OH'),('Quaker Hill','CT'),('Quaker Street','NY'),('Quakertown','NJ'),('Quakertown','PA'),('Quanah','TX'),('Quantico','MD'),('Quantico','VA'),('Quapaw','OK'),('Quarryville','PA'),('Quartzsite','AZ'),('Quasqueton','IA'),('Quay','NM'),('Quebeck','TN'),('Quebradillas','PR'),('Quechee','VT'),('Quecreek','PA'),('Queen','PA'),('Queen Anne','MD'),('Queen City','MO'),('Queen City','TX'),('Queen Creek','AZ'),('Queens Village','NY'),('Queensbury','NY'),('Queenstown','MD'),('Quemado','NM'),('Quemado','TX'),('Quenemo','KS'),('Quentin','PA'),('Questa','NM'),('Quicksburg','VA'),('Quilcene','WA'),('Quimby','IA'),('Quinault','WA'),('Quinby','VA'),('Quincy','CA'),('Quincy','FL'),('Quincy','IL'),('Quincy','IN'),('Quincy','KY'),('Quincy','MA'),('Quincy','MI'),('Quincy','MO'),('Quincy','OH'),('Quincy','PA'),('Quincy','WA'),('Quinebaug','CT'),('Quinhagak','AK'),('Quinlan','TX'),('Quinn','SD'),('Quinnesec','MI'),('Quinque','VA'),('Quinter','KS'),('Quinton','AL'),('Quinton','NJ'),('Quinton','OK'),('Quinton','VA'),('Quinwood','WV'),('Quitaque','TX'),('Quitman','AR'),('Quitman','GA'),('Quitman','LA'),('Quitman','MS'),('Quitman','TX'),('Qulin','MO'),('Quogue','NY'),('Rabun Gap','GA'),('Raccoon','KY'),('Raceland','LA'),('Rachel','WV'),('Racine','MN'),('Racine','MO'),('Racine','OH'),('Racine','WI'),('Racine','WV'),('Rackerby','CA'),('Radcliff','KY'),('Radcliffe','IA'),('Radersburg','MT'),('Radford','VA'),('Radiant','VA'),('Radisson','WI'),('Radium Springs','NM'),('Radnor','OH'),('Radom','IL'),('Raeford','NC'),('Ragan','NE'),('Ragland','AL'),('Ragland','WV'),('Ragley','LA'),('Ragsdale','IN'),('Rahway','NJ'),('Raiford','FL'),('Rail Road Flat','CA'),('Railroad','PA'),('Rainbow','TX'),('Rainbow Lake','NY'),('Rainelle','WV'),('Rainier','OR'),('Rainier','WA'),('Rains','SC'),('Rainsville','AL'),('Rainsville','NM'),('Raisin City','CA'),('Rake','IA'),('Raleigh','IL'),('Raleigh','MS'),('Raleigh','NC'),('Raleigh','ND'),('Raleigh','WV'),('Ralls','TX'),('Ralph','AL'),('Ralph','MI'),('Ralph','SD'),('Ralston','IA'),('Ralston','OK'),('Ralston','PA'),('Ralston','WY'),('Ramah','CO'),('Ramah','NM'),('Ramer','AL'),('Ramer','TN'),('Ramey','PA'),('Ramona','CA'),('Ramona','KS'),('Ramona','OK'); + +INSERT INTO [cities] VALUES ('Ramona','SD'),('Rampart','AK'),('Ramsay','MI'),('Ramsay','MT'),('Ramseur','NC'),('Ramsey','IL'),('Ramsey','IN'),('Ramsey','NJ'),('Ranburne','AL'),('Ranchester','WY'),('Ranchita','CA'),('Rancho Cordova','CA'),('Rancho Cucamonga','CA'),('Rancho Mirage','CA'),('Rancho Palos Verdes','CA'),('Rancho Santa Fe','CA'),('Rancho Santa Margarita','CA'),('Ranchos De Taos','NM'),('Rancocas','NJ'),('Rand','CO'),('Randalia','IA'),('Randall','IA'),('Randall','KS'),('Randall','MN'),('Randallstown','MD'),('Randle','WA'),('Randleman','NC'),('Randlett','OK'),('Randlett','UT'),('Randolph','AL'),('Randolph','IA'),('Randolph','KS'),('Randolph','MA'),('Randolph','ME'),('Randolph','MN'),('Randolph','MS'),('Randolph','NE'),('Randolph','NH'),('Randolph','NJ'),('Randolph','NY'),('Randolph','OH'),('Randolph','TX'),('Randolph','UT'),('Randolph','VA'),('Randolph','VT'),('Randolph','WI'),('Randolph Center','VT'),('Random Lake','WI'),('Randsburg','CA'),('Range','AL'),('Rangeley','ME'),('Rangely','CO'),('Ranger','GA'),('Ranger','TX'),('Ranger','WV'),('Ranier','MN'),('Rankin','IL'),('Rankin','TX'),('Ransom','IL'),('Ransom','KS'),('Ransom','KY'),('Ransom','PA'),('Ransom Canyon','TX'),('Ransomville','NY'),('Ranson','WV'),('Rantoul','IL'),('Rantoul','KS'),('Rapelje','MT'),('Raphine','VA'),('Rapid City','MI'),('Rapid City','SD'),('Rapid River','MI'),('Rapidan','VA'),('Rapids City','IL'),('Rappahannock Academy','VA'),('Raquette Lake','NY'),('Rarden','OH'),('Raritan','IL'),('Raritan','NJ'),('Ratcliff','AR'),('Ratcliff','TX'),('Rathdrum','ID'),('Ratliff City','OK'),('Raton','NM'),('Rattan','OK'),('Ravalli','MT'),('Raven','KY'),('Raven','VA'),('Ravena','NY'),('Ravencliff','WV'),('Ravendale','CA'),('Ravenden','AR'),('Ravenden Springs','AR'),('Ravenel','SC'),('Ravenna','KY'),('Ravenna','MI'),('Ravenna','NE'),('Ravenna','OH'),('Ravenna','TX'),('Ravensdale','WA'),('Ravenswood','WV'),('Ravenwood','MO'),('Ravia','OK'),('Ravine','PA'),('Rawl','WV'),('Rawlings','MD'),('Rawlings','VA'),('Rawlins','WY'),('Rawson','OH'),('Ray','MI'),('Ray','ND'),('Ray','OH'),('Ray Brook','NY'),('Ray City','GA'),('Rayland','OH'),('Rayle','GA'),('Raymond','CA'),('Raymond','IA'),('Raymond','IL'),('Raymond','KS'),('Raymond','ME'),('Raymond','MN'),('Raymond','MS'),('Raymond','MT'),('Raymond','NE'),('Raymond','NH'),('Raymond','OH'),('Raymond','SD'),('Raymond','WA'),('Raymondville','MO'),('Raymondville','NY'),('Raymondville','TX'),('Raymore','MO'),('Rayne','LA'),('Raynesford','MT'),('Raynham','MA'),('Raynham Center','MA'),('Raysal','WV'),('Rayville','LA'),('Rayville','MO'),('Raywick','KY'),('Raywood','TX'),('Rea','MO'),('Reader','WV'),('Readfield','ME'),('Readfield','WI'),('Reading','KS'),('Reading','MA'),('Reading','MI'),('Reading','MN'),('Reading','PA'),('Reading','VT'),('Reading Center','NY'),('Readington','NJ'),('Readlyn','IA'),('Reads Landing','MN'),('Readsboro','VT'),('Readstown','WI'),('Readville','MA'),('Readyville','TN'),('Reagan','TN'),('Reagan','TX'),('Realitos','TX'),('Reamstown','PA'),('Reardan','WA'),('Reasnor','IA'),('Rebecca','GA'),('Rebersburg','PA'),('Rebuck','PA'),('Recluse','WY'),('Rector','AR'),('Rector','PA'),('Rectortown','VA'),('Red Ash','VA'),('Red Bank','NJ'),('Red Banks','MS'),('Red Bay','AL'),('Red Bluff','CA'),('Red Boiling Springs','TN'),('Red Bud','IL'),('Red Cliff','CO'),('Red Cloud','NE'),('Red Creek','NY'),('Red Creek','WV'),('Red Devil','AK'),('Red Feather Lakes','CO'),('Red Hill','PA'),('Red Hook','NY'),('Red House','VA'),('Red House','WV'),('Red Jacket','WV'),('Red Lake Falls','MN'),('Red Level','AL'),('Red Lion','PA'),('Red Lodge','MT'),('Red Mountain','CA'),('Red Oak','GA'),('Red Oak','IA'),('Red Oak','NC'),('Red Oak','OK'),('Red Oak','TX'),('Red Oak','VA'),('Red River','NM'),('Red Rock','AZ'),('Red Rock','OK'),('Red Rock','TX'),('Red Springs','NC'),('Red Valley','AZ'),('Red Wing','MN'),('Redan','GA'),('Redbird','OK'),('Redby','MN'),('Redcrest','CA'),('Reddell','LA'),('Reddick','FL'),('Reddick','IL'),('Redding','CA'),('Redding','CT'),('Redding','IA'),('Redding Center','CT'),('Redding Ridge','CT'),('Redfield','AR'),('Redfield','IA'),('Redfield','KS'),('Redfield','NY'),('Redfield','SD'),('Redford','MI'),('Redford','MO'),('Redford','NY'),('Redford','TX'),('Redfox','KY'),('Redgranite','WI'),('Redig','SD'),('Redkey','IN'),('Redlake','MN'),('Redlands','CA'),('Redmon','IL'),('Redmond','OR'),('Redmond','UT'),('Redmond','WA'),('Redondo','WA'),('Redondo Beach','CA'),('Redrock','NM'),('Redstar','WV'),('Redstone','MT'),('Redvale','CO'),('Redwater','TX'),('Redway','CA'),('Redwood','MS'),('Redwood','NY'),('Redwood','VA'),('Redwood City','CA'),('Redwood Estates','CA'),('Redwood Falls','MN'),('Redwood Valley','CA'),('Ree Heights','SD'),('Reed','KY'),('Reed City','MI'),('Reed Point','MT'),('Reeder','ND'),('Reeders','PA'),('Reedley','CA'),('Reeds','MO'),('Reeds Spring','MO'),('Reedsburg','WI'),('Reedsport','OR'),('Reedsville','OH'),('Reedsville','PA'),('Reedsville','WI'),('Reedsville','WV'),('Reedville','VA'),('Reedy','WV'),('Reelsville','IN'),('Reese','MI'),('Reeseville','WI'),('Reesville','OH'),('Reeves','LA'),('Reevesville','SC'),('Reform','AL'),('Refton','PA'),('Refugio','TX'),('Regan','ND'),('Regent','ND'),('Regina','KY'),('Regina','NM'),('Register','GA'),('Rego Park','NY'),('Rehobeth','MD'),('Rehoboth','MA'),('Rehoboth','NM'),('Rehoboth Beach','DE'),('Rehrersburg','PA'),('Reidsville','GA'),('Reidsville','NC'),('Reidville','SC'),('Reinbeck','IA'),('Reinholds','PA'),('Reisterstown','MD'),('Reklaw','TX'),('Reliance','SD'),('Reliance','TN'),('Reliance','WY'),('Rembert','SC'),('Rembrandt','IA'),('Remer','MN'),('Remington','IN'),('Remington','VA'),('Remlap','AL'),('Remsen','IA'),('Remsen','NY'),('Remsenburg','NY'),('Remus','MI'),('Rena Lara','MS'),('Renault','IL'),('Renfrew','PA'),('Renfro Valley','KY'),('Renick','MO'),('Renick','WV'),('Renner','SD'),('Reno','NV'),('Reno','OH'),('Reno','PA'),('Renovo','PA'),('Rensselaer','IN'),('Rensselaer','NY'),('Rensselaer Falls','NY'),('Rensselaerville','NY'),('Rentiesville','OK'),('Renton','WA'),('Rentz','GA'),('Renville','MN'),('Renwick','IA'),('Represa','CA'),('Repton','AL'),('Republic','KS'),('Republic','MI'),('Republic','MO'),('Republic','OH'),('Republic','PA'),('Republic','WA'),('Republican City','NE'),('Resaca','GA'),('Rescue','CA'),('Rescue','VA'),('Research Triangle Park','NC'),('Reseda','CA'),('Reserve','LA'),('Reserve','MT'),('Reserve','NM'),('Reston','VA'),('Retsil','WA'),('Retsof','NY'),('Reubens','ID'),('Reva','SD'),('Reva','VA'),('Revelo','KY'),('Revere','MA'),('Revere','MN'),('Revere','MO'),('Revere','PA'),('Revillo','SD'),('Revloc','PA'),('Rew','PA'),('Rewey','WI'),('Rex','GA'),('Rex','NC'),('Rexburg','ID'),('Rexford','KS'),('Rexford','MT'),('Rexford','NY'),('Rexmont','PA'),('Rexville','NY'),('Reydell','AR'),('Reydon','OK'),('Reyno','AR'),('Reynolds','GA'),('Reynolds','IL'),('Reynolds','IN'),('Reynolds','MO'),('Reynolds','ND'),('Reynolds','NE'),('Reynolds Station','KY'),('Reynoldsburg','OH'),('Reynoldsville','PA'),('Reynoldsville','WV'),('Rhame','ND'),('Rheems','PA'),('Rhine','GA'),('Rhinebeck','NY'),('Rhinecliff','NY'),('Rhinehart','LA'),('Rhineland','MO'),('Rhinelander','WI'),('Rhoadesville','VA'),('Rhodelia','KY'),('Rhodell','WV'),('Rhodes','IA'),('Rhodes','MI'),('Rhodesdale','MD'),('Rhodhiss','NC'),('Rhododendron','OR'),('Rhome','TX'),('Rialto','CA'),('Rib Lake','WI'),('Ribera','NM'),('Rice','MN'),('Rice','TX'),('Rice','VA'),('Rice','WA'),('Rice Lake','WI'),('Riceboro','GA'),('Rices Landing','PA'),('Ricetown','KY'),('Riceville','IA'),('Riceville','PA'),('Riceville','TN'),('Rich Creek','VA'),('Rich Hill','MO'),('Rich Square','NC'),('Richards','MO'),('Richards','TX'),('Richardson','TX'),('Richardsville','VA'),('Richardton','ND'),('Richboro','PA'),('Richburg','NY'),('Richburg','SC'),('Richey','MT'),('Richeyville','PA'),('Richfield','ID'),('Richfield','KS'),('Richfield','NC'),('Richfield','OH'),('Richfield','PA'),('Richfield','UT'),('Richfield','WI'),('Richfield Springs','NY'),('Richford','NY'),('Richford','VT'),('Richgrove','CA'),('Richland','GA'),('Richland','IA'),('Richland','IN'),('Richland','MI'),('Richland','MO'),('Richland','MS'),('Richland','MT'),('Richland','NJ'),('Richland','NY'),('Richland','OR'),('Richland','PA'),('Richland','SC'),('Richland','TX'),('Richland','WA'),('Richland Center','WI'),('Richland Springs','TX'),('Richlands','NC'),('Richlands','VA'),('Richlandtown','PA'),('Richmond','CA'),('Richmond','IL'),('Richmond','IN'),('Richmond','KS'),('Richmond','KY'),('Richmond','MA'),('Richmond','ME'),('Richmond','MI'),('Richmond','MN'),('Richmond','MO'),('Richmond','OH'),('Richmond','TX'),('Richmond','UT'),('Richmond','VA'),('Richmond','VT'),('Richmond Dale','OH'),('Richmond Hill','GA'),('Richmond Hill','NY'),('Richmondville','NY'),('Richton','MS'),('Richton Park','IL'),('Richvale','CA'),('Richview','IL'),('Richville','MI'),('Richville','MN'),('Richville','NY'),('Richwood','MN'),('Richwood','NJ'),('Richwood','OH'),('Richwood','WV'),('Richwoods','MO'),('Ricketts','IA'),('Rickman','TN'),('Rickreall','OR'),('Rico','CO'),('Riddle','OR'),('Riddlesburg','PA'),('Riddleton','TN'),('Riderwood','MD'),('Ridge','MD'),('Ridge','NY'); + +INSERT INTO [cities] VALUES ('Ridge Farm','IL'),('Ridge Spring','SC'),('Ridgecrest','CA'),('Ridgecrest','NC'),('Ridgedale','MO'),('Ridgefield','CT'),('Ridgefield','NJ'),('Ridgefield','WA'),('Ridgefield Park','NJ'),('Ridgeland','MS'),('Ridgeland','SC'),('Ridgeland','WI'),('Ridgeley','WV'),('Ridgely','MD'),('Ridgely','TN'),('Ridgetop','TN'),('Ridgeview','SD'),('Ridgeview','WV'),('Ridgeville','IN'),('Ridgeville','SC'),('Ridgeville Corners','OH'),('Ridgeway','IA'),('Ridgeway','MO'),('Ridgeway','NC'),('Ridgeway','OH'),('Ridgeway','SC'),('Ridgeway','VA'),('Ridgeway','WI'),('Ridgeway','WV'),('Ridgewood','NJ'),('Ridgewood','NY'),('Ridgway','CO'),('Ridgway','IL'),('Ridgway','PA'),('Ridley Park','PA'),('Ridott','IL'),('Riegelsville','PA'),('Riegelwood','NC'),('Rienzi','MS'),('Riesel','TX'),('Rifle','CO'),('Rifton','NY'),('Riga','MI'),('Rigby','ID'),('Riggins','ID'),('Riley','IN'),('Riley','KS'),('Riley','OR'),('Rileyville','VA'),('Rillito','AZ'),('Rillton','PA'),('Rimersburg','PA'),('Rimforest','CA'),('Rimrock','AZ'),('Rinard','IL'),('Rincon','GA'),('Rincon','NM'),('Rincon','PR'),('Rindge','NH'),('Riner','VA'),('Rineyville','KY'),('Ringgold','GA'),('Ringgold','LA'),('Ringgold','PA'),('Ringgold','TX'),('Ringgold','VA'),('Ringle','WI'),('Ringling','MT'),('Ringling','OK'),('Ringoes','NJ'),('Ringold','OK'),('Ringsted','IA'),('Ringtown','PA'),('Ringwood','IL'),('Ringwood','NJ'),('Ringwood','OK'),('Rio','IL'),('Rio','WI'),('Rio','WV'),('Rio Blanco','PR'),('Rio Dell','CA'),('Rio Frio','TX'),('Rio Grande','NJ'),('Rio Grande','OH'),('Rio Grande','PR'),('Rio Grande City','TX'),('Rio Hondo','TX'),('Rio Linda','CA'),('Rio Medina','TX'),('Rio Nido','CA'),('Rio Oso','CA'),('Rio Rancho','NM'),('Rio Rico','AZ'),('Rio Verde','AZ'),('Rio Vista','CA'),('Rio Vista','TX'),('Rion','SC'),('Riparius','NY'),('Ripley','MS'),('Ripley','NY'),('Ripley','OH'),('Ripley','OK'),('Ripley','TN'),('Ripley','WV'),('Ripon','CA'),('Ripon','WI'),('Rippey','IA'),('Ripplemead','VA'),('Rippon','WV'),('Ripton','VT'),('Ririe','ID'),('Risco','MO'),('Rising City','NE'),('Rising Fawn','GA'),('Rising Star','TX'),('Rising Sun','IN'),('Rising Sun','MD'),('Risingsun','OH'),('Rison','AR'),('Rittman','OH'),('Ritzville','WA'),('Riva','MD'),('River','KY'),('River Edge','NJ'),('River Falls','AL'),('River Falls','WI'),('River Forest','IL'),('River Grove','IL'),('River Pines','CA'),('River Ranch','FL'),('River Rouge','MI'),('Riverbank','CA'),('Riverdale','CA'),('Riverdale','GA'),('Riverdale','IL'),('Riverdale','MD'),('Riverdale','MI'),('Riverdale','ND'),('Riverdale','NE'),('Riverdale','NJ'),('Riverhead','NY'),('Riverside','AL'),('Riverside','CA'),('Riverside','CT'),('Riverside','IA'),('Riverside','IL'),('Riverside','MI'),('Riverside','MO'),('Riverside','OR'),('Riverside','PA'),('Riverside','RI'),('Riverside','TX'),('Riverside','UT'),('Riverside','WA'),('Riverton','CT'),('Riverton','IA'),('Riverton','IL'),('Riverton','KS'),('Riverton','NE'),('Riverton','NJ'),('Riverton','UT'),('Riverton','WV'),('Riverton','WY'),('Rivervale','AR'),('Riverview','FL'),('Riverview','MI'),('Rives','MO'),('Rives','TN'),('Rives Junction','MI'),('Rivesville','WV'),('Riviera','TX'),('Rixeyville','VA'),('Rixford','PA'),('Roach','MO'),('Roachdale','IN'),('Roan Mountain','TN'),('Roann','IN'),('Roanoke','AL'),('Roanoke','IL'),('Roanoke','IN'),('Roanoke','LA'),('Roanoke','TX'),('Roanoke','VA'),('Roanoke Rapids','NC'),('Roans Prairie','TX'),('Roaring Branch','PA'),('Roaring Gap','NC'),('Roaring River','NC'),('Roaring Spring','PA'),('Roaring Springs','TX'),('Roark','KY'),('Robards','KY'),('Robbins','CA'),('Robbins','IL'),('Robbins','NC'),('Robbins','TN'),('Robbinston','ME'),('Robbinsville','NC'),('Robeline','LA'),('Robersonville','NC'),('Robert','LA'),('Robert Lee','TX'),('Roberta','GA'),('Roberts','ID'),('Roberts','IL'),('Roberts','MT'),('Roberts','WI'),('Robertsdale','AL'),('Robertsdale','PA'),('Robertson','WY'),('Robertsville','MO'),('Robertsville','OH'),('Robesonia','PA'),('Robins','IA'),('Robinson','IL'),('Robinson','KS'),('Robinson','ND'),('Robinson','PA'),('Robinson Creek','KY'),('Robinsonville','MS'),('Robson','WV'),('Robstown','TX'),('Roby','MO'),('Roby','TX'),('Roca','NE'),('Rochdale','MA'),('Rochelle','GA'),('Rochelle','IL'),('Rochelle','TX'),('Rochelle','VA'),('Rochelle Park','NJ'),('Rocheport','MO'),('Rochert','MN'),('Rochester','IL'),('Rochester','IN'),('Rochester','KY'),('Rochester','MA'),('Rochester','MI'),('Rochester','MN'),('Rochester','NH'),('Rochester','NY'),('Rochester','PA'),('Rochester','TX'),('Rochester','VT'),('Rochester','WA'),('Rochester','WI'),('Rochester Mills','PA'),('Rociada','NM'),('Rock','KS'),('Rock','MI'),('Rock','WV'),('Rock Camp','OH'),('Rock Cave','WV'),('Rock City','IL'),('Rock City Falls','NY'),('Rock Creek','MN'),('Rock Creek','OH'),('Rock Creek','WV'),('Rock Falls','IA'),('Rock Falls','IL'),('Rock Falls','WI'),('Rock Glen','PA'),('Rock Hall','MD'),('Rock Hill','NY'),('Rock Hill','SC'),('Rock Island','IL'),('Rock Island','TN'),('Rock Island','TX'),('Rock Island','WA'),('Rock Point','AZ'),('Rock Point','MD'),('Rock Port','MO'),('Rock Rapids','IA'),('Rock River','WY'),('Rock Spring','GA'),('Rock Springs','WI'),('Rock Springs','WY'),('Rock Stream','NY'),('Rock Tavern','NY'),('Rock Valley','IA'),('Rock View','WV'),('Rockaway','NJ'),('Rockaway Beach','MO'),('Rockaway Beach','OR'),('Rockaway Park','NY'),('Rockbridge','IL'),('Rockbridge','MO'),('Rockbridge','OH'),('Rockbridge Baths','VA'),('Rockdale','TX'),('Rockfall','CT'),('Rockfield','IN'),('Rockfield','KY'),('Rockford','AL'),('Rockford','IA'),('Rockford','IL'),('Rockford','MI'),('Rockford','MN'),('Rockford','OH'),('Rockford','TN'),('Rockford','WA'),('Rockham','SD'),('Rockhill Furnace','PA'),('Rockholds','KY'),('Rockhouse','KY'),('Rockingham','NC'),('Rocklake','ND'),('Rockland','DE'),('Rockland','ID'),('Rockland','MA'),('Rockland','ME'),('Rockland','MI'),('Rockland','WI'),('Rockledge','FL'),('Rockledge','GA'),('Rocklin','CA'),('Rockmart','GA'),('Rockport','IL'),('Rockport','IN'),('Rockport','KY'),('Rockport','MA'),('Rockport','ME'),('Rockport','TX'),('Rockport','WA'),('Rockport','WV'),('Rocksprings','TX'),('Rockton','IL'),('Rockton','PA'),('Rockvale','CO'),('Rockvale','TN'),('Rockville','IN'),('Rockville','MD'),('Rockville','MN'),('Rockville','MO'),('Rockville','NE'),('Rockville','RI'),('Rockville','UT'),('Rockville','VA'),('Rockville Centre','NY'),('Rockwall','TX'),('Rockwell','IA'),('Rockwell','NC'),('Rockwell City','IA'),('Rockwood','IL'),('Rockwood','ME'),('Rockwood','MI'),('Rockwood','PA'),('Rockwood','TN'),('Rockwood','TX'),('Rocky','OK'),('Rocky Comfort','MO'),('Rocky Face','GA'),('Rocky Ford','CO'),('Rocky Ford','GA'),('Rocky Gap','VA'),('Rocky Hill','CT'),('Rocky Hill','KY'),('Rocky Hill','NJ'),('Rocky Mount','MO'),('Rocky Mount','NC'),('Rocky Mount','VA'),('Rocky Point','NC'),('Rocky Point','NY'),('Rocky Ridge','MD'),('Rocky Ridge','OH'),('Rocky River','OH'),('Rodanthe','NC'),('Rodeo','CA'),('Rodeo','NM'),('Roderfield','WV'),('Rodessa','LA'),('Rodman','NY'),('Rodney','IA'),('Rodney','MI'),('Roduco','NC'),('Roe','AR'),('Roebling','NJ'),('Roebuck','SC'),('Roff','OK'),('Rogers','AR'),('Rogers','CT'),('Rogers','KY'),('Rogers','MN'),('Rogers','ND'),('Rogers','NE'),('Rogers','NM'),('Rogers','OH'),('Rogers','TX'),('Rogers City','MI'),('Rogerson','ID'),('Rogersville','AL'),('Rogersville','MO'),('Rogersville','PA'),('Rogersville','TN'),('Roggen','CO'),('Rogue River','OR'),('Rohnert Park','CA'),('Rohrersville','MD'),('Roland','AR'),('Roland','IA'),('Roland','OK'),('Rolesville','NC'),('Rolette','ND'),('Rolfe','IA'),('Roll','AZ'),('Rolla','KS'),('Rolla','MO'),('Rolla','ND'),('Rolling Fork','MS'),('Rolling Meadows','IL'),('Rolling Prairie','IN'),('Rollingbay','WA'),('Rollingstone','MN'),('Rollins','MT'),('Rollins Fork','VA'),('Rollinsford','NH'),('Rollinsville','CO'),('Roma','TX'),('Romance','AR'),('Romayor','TX'),('Rombauer','MO'),('Rome','GA'),('Rome','IA'),('Rome','IL'),('Rome','IN'),('Rome','MS'),('Rome','NY'),('Rome','OH'),('Rome','PA'),('Rome City','IN'),('Romeo','CO'),('Romeo','MI'),('Romeoville','IL'),('Romney','IN'),('Romney','WV'),('Romulus','MI'),('Romulus','NY'),('Ronald','WA'),('Ronan','MT'),('Ronceverte','WV'),('Ronco','PA'),('Ronda','NC'),('Ronkonkoma','NY'),('Ronks','PA'),('Roodhouse','IL'),('Roopville','GA'),('Roosevelt','AZ'),('Roosevelt','MN'),('Roosevelt','NJ'),('Roosevelt','NY'),('Roosevelt','OK'),('Roosevelt','TX'),('Roosevelt','UT'),('Roosevelt','WA'),('Roosevelt Roads','PR'),('Rooseveltown','NY'),('Rootstown','OH'),('Roper','NC'),('Ropesville','TX'),('Rosalia','KS'),('Rosalia','WA'),('Rosalie','NE'),('Rosamond','CA'),('Rosamond','IL'),('Rosanky','TX'),('Rosario','PR'),('Rosburg','WA'),('Roscoe','IL'),('Roscoe','MN'),('Roscoe','MO'),('Roscoe','MT'),('Roscoe','NY'),('Roscoe','PA'),('Roscoe','SD'),('Roscoe','TX'),('Roscommon','MI'),('Rose','NY'),('Rose','OK'),('Rose Bud','AR'),('Rose City','MI'),('Rose Creek','MN'),('Rose Hill','IA'),('Rose Hill','KS'),('Rose Hill','MS'),('Rose Hill','NC'),('Rose Hill','VA'),('Rose Lodge','OR'),('Roseau','MN'),('Roseboom','NY'),('Roseboro','NC'),('Rosebud','MO'),('Rosebud','MT'),('Rosebud','SD'),('Rosebud','TX'),('Roseburg','OR'),('Rosebush','MI'),('Rosedale','IN'),('Rosedale','LA'),('Rosedale','MD'),('Rosedale','MS'); + +INSERT INTO [cities] VALUES ('Rosedale','NY'),('Rosedale','VA'),('Rosedale','WV'),('Roseglen','ND'),('Roseland','FL'),('Roseland','LA'),('Roseland','NE'),('Roseland','NJ'),('Roseland','VA'),('Roselawn','IN'),('Roselle','IL'),('Roselle','NJ'),('Roselle Park','NJ'),('Rosemary Beach','FL'),('Rosemead','CA'),('Rosemont','NJ'),('Rosemont','WV'),('Rosemount','MN'),('Rosenberg','TX'),('Rosendale','MO'),('Rosendale','NY'),('Rosendale','WI'),('Rosenhayn','NJ'),('Rosepine','LA'),('Roseville','CA'),('Roseville','IL'),('Roseville','MI'),('Roseville','OH'),('Rosewood','OH'),('Rosharon','TX'),('Rosholt','SD'),('Rosholt','WI'),('Rosiclare','IL'),('Rosie','AR'),('Rosine','KY'),('Roslindale','MA'),('Roslyn','NY'),('Roslyn','SD'),('Roslyn','WA'),('Roslyn Heights','NY'),('Rosman','NC'),('Ross','CA'),('Ross','ND'),('Ross','OH'),('Ross','TX'),('Rossburg','OH'),('Rosser','TX'),('Rossford','OH'),('Rossiter','PA'),('Rosston','AR'),('Rosston','OK'),('Rosston','TX'),('Rossville','GA'),('Rossville','IL'),('Rossville','IN'),('Rossville','KS'),('Rossville','PA'),('Rossville','TN'),('Roswell','GA'),('Roswell','NM'),('Rotan','TX'),('Rothbury','MI'),('Rothsay','MN'),('Rothschild','WI'),('Rothville','MO'),('Rotonda West','FL'),('Rotterdam Junction','NY'),('Rougemont','NC'),('Rough And Ready','CA'),('Rougon','LA'),('Roulette','PA'),('Round Hill','VA'),('Round Lake','IL'),('Round Lake','MN'),('Round Lake','NY'),('Round Mountain','CA'),('Round Mountain','NV'),('Round Mountain','TX'),('Round O','SC'),('Round Pond','ME'),('Round Rock','AZ'),('Round Rock','TX'),('Round Top','NY'),('Round Top','TX'),('Roundhead','OH'),('Roundhill','KY'),('Roundup','MT'),('Rouses Point','NY'),('Rouseville','PA'),('Rousseau','KY'),('Rouzerville','PA'),('Rover','AR'),('Rowan','IA'),('Rowdy','KY'),('Rowe','MA'),('Rowe','NM'),('Rowe','VA'),('Rowena','SD'),('Rowena','TX'),('Rowesville','SC'),('Rowland','NC'),('Rowland','PA'),('Rowland Heights','CA'),('Rowlesburg','WV'),('Rowlett','TX'),('Rowley','IA'),('Rowley','MA'),('Roxana','IL'),('Roxana','KY'),('Roxboro','NC'),('Roxbury','CT'),('Roxbury','KS'),('Roxbury','ME'),('Roxbury','NY'),('Roxbury','PA'),('Roxbury','VT'),('Roxie','MS'),('Roxobel','NC'),('Roxton','TX'),('Roy','MT'),('Roy','NM'),('Roy','UT'),('Roy','WA'),('Royal','AR'),('Royal','IA'),('Royal','IL'),('Royal','NE'),('Royal Center','IN'),('Royal City','WA'),('Royal Oak','MD'),('Royal Oak','MI'),('Royalston','MA'),('Royalton','IL'),('Royalton','KY'),('Royalton','MN'),('Royersford','PA'),('Royse City','TX'),('Royston','GA'),('Rozel','KS'),('Rozet','WY'),('Rubicon','WI'),('Ruby','AK'),('Ruby','LA'),('Ruby','NY'),('Ruby','SC'),('Ruby','VA'),('Ruby Valley','NV'),('Ruckersville','VA'),('Rudd','IA'),('Rudolph','OH'),('Rudolph','WI'),('Rudy','AR'),('Rudyard','MI'),('Rudyard','MT'),('Rueter','MO'),('Rufe','OK'),('Ruffin','NC'),('Ruffin','SC'),('Ruffs Dale','PA'),('Rufus','OR'),('Rugby','ND'),('Rugby','TN'),('Ruidoso','NM'),('Ruidoso Downs','NM'),('Rule','TX'),('Ruleville','MS'),('Rulo','NE'),('Rumely','MI'),('Rumford','ME'),('Rumford','RI'),('Rumford Center','ME'),('Rumney','NH'),('Rumsey','CA'),('Rumsey','KY'),('Rumson','NJ'),('Runge','TX'),('Runnells','IA'),('Runnemede','NJ'),('Running Springs','CA'),('Rupert','GA'),('Rupert','ID'),('Rupert','VT'),('Rupert','WV'),('Rural Hall','NC'),('Rural Retreat','VA'),('Rural Ridge','PA'),('Rural Valley','PA'),('Rush','CO'),('Rush','KY'),('Rush','NY'),('Rush Center','KS'),('Rush City','MN'),('Rush Hill','MO'),('Rush Springs','OK'),('Rush Valley','UT'),('Rushford','MN'),('Rushford','NY'),('Rushland','PA'),('Rushmore','MN'),('Rushsylvania','OH'),('Rushville','IL'),('Rushville','IN'),('Rushville','MO'),('Rushville','NE'),('Rushville','NY'),('Rushville','OH'),('Rusk','TX'),('Ruskin','FL'),('Ruskin','NE'),('Ruso','ND'),('Russell','AR'),('Russell','IA'),('Russell','IL'),('Russell','KS'),('Russell','KY'),('Russell','MA'),('Russell','MN'),('Russell','NY'),('Russell','PA'),('Russell Springs','KY'),('Russells Point','OH'),('Russellton','PA'),('Russellville','AL'),('Russellville','AR'),('Russellville','IN'),('Russellville','KY'),('Russellville','MO'),('Russellville','OH'),('Russellville','SC'),('Russellville','TN'),('Russia','OH'),('Russian Mission','AK'),('Russiaville','IN'),('Rustburg','VA'),('Ruston','LA'),('Ruth','MI'),('Ruth','MS'),('Ruth','NV'),('Ruther Glen','VA'),('Rutherford','CA'),('Rutherford','NJ'),('Rutherford','TN'),('Rutherford College','NC'),('Rutherfordton','NC'),('Ruthton','MN'),('Ruthven','IA'),('Ruthville','VA'),('Rutland','IA'),('Rutland','IL'),('Rutland','MA'),('Rutland','ND'),('Rutland','OH'),('Rutland','SD'),('Rutland','VT'),('Rutledge','AL'),('Rutledge','GA'),('Rutledge','MO'),('Rutledge','TN'),('Ryan','IA'),('Ryan','OK'),('Rydal','GA'),('Ryde','CA'),('Ryder','ND'),('Ryderwood','WA'),('Rye','CO'),('Rye','NH'),('Rye','NY'),('Rye','TX'),('Rye Beach','NH'),('Ryegate','MT'),('Ryland','AL'),('S Coffeyville','OK'),('Sabael','NY'),('Sabana Grande','PR'),('Sabana Hoyos','PR'),('Sabana Seca','PR'),('Sabattus','ME'),('Sabetha','KS'),('Sabillasville','MD'),('Sabin','MN'),('Sabina','OH'),('Sabinal','TX'),('Sabine','WV'),('Sabine Pass','TX'),('Sabinsville','PA'),('Sabula','IA'),('Sac City','IA'),('Sacaton','AZ'),('Sachse','TX'),('Sackets Harbor','NY'),('Saco','ME'),('Saco','MT'),('Sacramento','CA'),('Sacramento','KY'),('Sacramento','NM'),('Sacramento','PA'),('Sacred Heart','MN'),('Sacul','TX'),('Saddle Brook','NJ'),('Saddle River','NJ'),('Saddlestring','WY'),('Sadieville','KY'),('Sadler','TX'),('Sadorus','IL'),('Sadsburyville','PA'),('Saegertown','PA'),('Safety Harbor','FL'),('Saffell','AR'),('Safford','AL'),('Safford','AZ'),('Sag Harbor','NY'),('Sagamore','MA'),('Sagamore','PA'),('Sagamore Beach','MA'),('Sagaponack','NY'),('Sage','AR'),('Saginaw','AL'),('Saginaw','MI'),('Saginaw','MN'),('Saginaw','MO'),('Saginaw','OR'),('Sagle','ID'),('Sagola','MI'),('Saguache','CO'),('Sahuarita','AZ'),('Sailor Springs','IL'),('Saint Agatha','ME'),('Saint Albans','ME'),('Saint Albans','MO'),('Saint Albans','NY'),('Saint Albans','VT'),('Saint Albans','WV'),('Saint Albans Bay','VT'),('Saint Amant','LA'),('Saint Ann','MO'),('Saint Anne','IL'),('Saint Ansgar','IA'),('Saint Anthony','IA'),('Saint Anthony','ID'),('Saint Anthony','IN'),('Saint Anthony','ND'),('Saint Augustine','FL'),('Saint Augustine','IL'),('Saint Benedict','LA'),('Saint Benedict','OR'),('Saint Benedict','PA'),('Saint Bernard','LA'),('Saint Bernice','IN'),('Saint Bonaventure','NY'),('Saint Boniface','PA'),('Saint Bonifacius','MN'),('Saint Catharine','KY'),('Saint Charles','AR'),('Saint Charles','IA'),('Saint Charles','ID'),('Saint Charles','IL'),('Saint Charles','KY'),('Saint Charles','MI'),('Saint Charles','MN'),('Saint Charles','MO'),('Saint Charles','SD'),('Saint Charles','VA'),('Saint Clair','MI'),('Saint Clair','MN'),('Saint Clair','MO'),('Saint Clair','PA'),('Saint Clair Shores','MI'),('Saint Clairsville','OH'),('Saint Cloud','FL'),('Saint Cloud','MN'),('Saint Cloud','WI'),('Saint Croix','IN'),('Saint Croix Falls','WI'),('Saint David','AZ'),('Saint David','IL'),('Saint David','ME'),('Saint Donatus','IA'),('Saint Edward','NE'),('Saint Elizabeth','MO'),('Saint Elmo','AL'),('Saint Elmo','IL'),('Saint Francis','AR'),('Saint Francis','KS'),('Saint Francis','KY'),('Saint Francis','ME'),('Saint Francis','MN'),('Saint Francis','SD'),('Saint Francisville','IL'),('Saint Francisville','LA'),('Saint Gabriel','LA'),('Saint George','GA'),('Saint George','KS'),('Saint George','SC'),('Saint George','UT'),('Saint George Island','AK'),('Saint Georges','DE'),('Saint Germain','WI'),('Saint Hedwig','TX'),('Saint Helen','MI'),('Saint Helena','CA'),('Saint Helena','NE'),('Saint Helena Island','SC'),('Saint Helens','KY'),('Saint Helens','OR'),('Saint Henry','OH'),('Saint Hilaire','MN'),('Saint Ignace','MI'),('Saint Ignatius','MT'),('Saint Inigoes','MD'),('Saint Jacob','IL'),('Saint James','LA'),('Saint James','MD'),('Saint James','MN'),('Saint James','MO'),('Saint James','NY'),('Saint James City','FL'),('Saint Jo','TX'),('Saint Joe','AR'),('Saint Joe','IN'),('Saint John','IN'),('Saint John','KS'),('Saint John','ND'),('Saint John','WA'),('Saint Johns','AZ'),('Saint Johns','FL'),('Saint Johns','MI'),('Saint Johns','OH'),('Saint Johns','PA'),('Saint Johnsbury','VT'),('Saint Johnsbury Center','VT'),('Saint Johnsville','NY'),('Saint Joseph','IL'),('Saint Joseph','LA'),('Saint Joseph','MI'),('Saint Joseph','MN'),('Saint Joseph','MO'),('Saint Joseph','TN'),('Saint Just','PR'),('Saint Landry','LA'),('Saint Lawrence','SD'),('Saint Leo','FL'),('Saint Leonard','MD'),('Saint Libory','IL'),('Saint Libory','NE'),('Saint Louis','MI'),('Saint Louis','MO'),('Saint Louis','OK'),('Saint Louis Park','MN'),('Saint Louisville','OH'),('Saint Lucas','IA'),('Saint Marie','MT'),('Saint Maries','ID'),('Saint Marks','FL'),('Saint Martin','MN'),('Saint Martinville','LA'),('Saint Mary','KY'),('Saint Mary','MO'),('Saint Mary Of The Woods','IN'),('Saint Marys','AK'),('Saint Marys','GA'),('Saint Marys','IA'),('Saint Marys','KS'),('Saint Marys','OH'),('Saint Marys','PA'),('Saint Marys','WV'),('Saint Marys City','MD'),('Saint Matthews','SC'),('Saint Maurice','LA'),('Saint Meinrad','IN'),('Saint Michael','AK'),('Saint Michael','MN'),('Saint Michael','ND'),('Saint Michael','PA'),('Saint Michaels','AZ'),('Saint Michaels','MD'),('Saint Nazianz','WI'),('Saint Olaf','IA'),('Saint Onge','SD'),('Saint Paris','OH'),('Saint Patrick','MO'),('Saint Paul','AR'),('Saint Paul','IA'),('Saint Paul','IN'),('Saint Paul','KS'),('Saint Paul','MN'),('Saint Paul','NE'),('Saint Paul','OR'),('Saint Paul','VA'),('Saint Paul Island','AK'),('Saint Paul Park','MN'),('Saint Pauls','NC'),('Saint Peter','IL'),('Saint Peter','MN'),('Saint Peters','MO'),('Saint Peters','PA'),('Saint Petersburg','FL'),('Saint Petersburg','PA'),('Saint Regis','MT'),('Saint Regis Falls','NY'),('Saint Robert','MO'); + +INSERT INTO [cities] VALUES ('Saint Rose','LA'),('Saint Simons Island','GA'),('Saint Stephen','MN'),('Saint Stephen','SC'),('Saint Stephens','AL'),('Saint Stephens','WY'),('Saint Stephens Church','VA'),('Saint Thomas','MO'),('Saint Thomas','ND'),('Saint Thomas','PA'),('Saint Vincent','MN'),('Saint Vrain','NM'),('Saint Xavier','MT'),('Sainte Genevieve','MO'),('Sainte Marie','IL'),('Salado','AR'),('Salado','TX'),('Salamanca','NY'),('Salamonia','IN'),('Salcha','AK'),('Sale City','GA'),('Sale Creek','TN'),('Salem','AL'),('Salem','AR'),('Salem','CT'),('Salem','FL'),('Salem','IA'),('Salem','IL'),('Salem','IN'),('Salem','KY'),('Salem','MA'),('Salem','MI'),('Salem','MO'),('Salem','NE'),('Salem','NH'),('Salem','NJ'),('Salem','NM'),('Salem','NY'),('Salem','OH'),('Salem','OR'),('Salem','SC'),('Salem','SD'),('Salem','UT'),('Salem','VA'),('Salem','WI'),('Salem','WV'),('Salemburg','NC'),('Salesville','OH'),('Salford','PA'),('Salfordville','PA'),('Salida','CA'),('Salida','CO'),('Salina','KS'),('Salina','OK'),('Salina','PA'),('Salina','UT'),('Salinas','CA'),('Salinas','PR'),('Saline','LA'),('Saline','MI'),('Salineno','TX'),('Salineville','OH'),('Salisbury','CT'),('Salisbury','MA'),('Salisbury','MD'),('Salisbury','MO'),('Salisbury','NC'),('Salisbury','NH'),('Salisbury','PA'),('Salisbury','VT'),('Salisbury Center','NY'),('Salisbury Mills','NY'),('Salix','IA'),('Salix','PA'),('Salkum','WA'),('Salley','SC'),('Sallis','MS'),('Sallisaw','OK'),('Salmon','ID'),('Salol','MN'),('Salome','AZ'),('Salona','PA'),('Salsbury Cove','ME'),('Salt Flat','TX'),('Salt Lake City','UT'),('Salt Lick','KY'),('Salt Point','NY'),('Salt Rock','WV'),('Salter Path','NC'),('Salters','SC'),('Saltese','MT'),('Saltillo','MS'),('Saltillo','PA'),('Saltillo','TN'),('Saltillo','TX'),('Salton City','CA'),('Saltsburg','PA'),('Saltville','VA'),('Saluda','NC'),('Saluda','SC'),('Saluda','VA'),('Salvisa','KY'),('Salvo','NC'),('Salyer','CA'),('Salyersville','KY'),('Sam Norwood','TX'),('Samantha','AL'),('Samaria','MI'),('Samburg','TN'),('Sammamish','WA'),('Samoa','CA'),('Samson','AL'),('San Acacia','NM'),('San Andreas','CA'),('San Angelo','TX'),('San Anselmo','CA'),('San Antonio','FL'),('San Antonio','NM'),('San Antonio','PR'),('San Antonio','TX'),('San Ardo','CA'),('San Augustine','TX'),('San Benito','TX'),('San Bernardino','CA'),('San Bruno','CA'),('San Carlos','AZ'),('San Carlos','CA'),('San Clemente','CA'),('San Cristobal','NM'),('San Diego','CA'),('San Diego','TX'),('San Dimas','CA'),('San Elizario','TX'),('San Felipe','TX'),('San Fernando','CA'),('San Fidel','NM'),('San Francisco','CA'),('San Gabriel','CA'),('San German','PR'),('San Geronimo','CA'),('San Gregorio','CA'),('San Isidro','TX'),('San Jacinto','CA'),('San Joaquin','CA'),('San Jon','NM'),('San Jose','CA'),('San Jose','IL'),('San Jose','NM'),('San Juan','PR'),('San Juan','TX'),('San Juan Bautista','CA'),('San Juan Capistrano','CA'),('San Juan Pueblo','NM'),('San Leandro','CA'),('San Lorenzo','CA'),('San Lorenzo','PR'),('San Lucas','CA'),('San Luis','AZ'),('San Luis','CO'),('San Luis Obispo','CA'),('San Luis Rey','CA'),('San Manuel','AZ'),('San Marcos','CA'),('San Marcos','TX'),('San Marino','CA'),('San Martin','CA'),('San Mateo','CA'),('San Mateo','FL'),('San Miguel','CA'),('San Miguel','NM'),('San Pablo','CA'),('San Pablo','CO'),('San Patricio','NM'),('San Pedro','CA'),('San Perlita','TX'),('San Pierre','IN'),('San Quentin','CA'),('San Rafael','CA'),('San Rafael','NM'),('San Ramon','CA'),('San Saba','TX'),('San Sebastian','PR'),('San Simeon','CA'),('San Simon','AZ'),('San Ygnacio','TX'),('San Ysidro','CA'),('San Ysidro','NM'),('Sanborn','IA'),('Sanborn','MN'),('Sanborn','ND'),('Sanborn','NY'),('Sanbornton','NH'),('Sanbornville','NH'),('Sand Coulee','MT'),('Sand Creek','MI'),('Sand Creek','WI'),('Sand Fork','WV'),('Sand Lake','MI'),('Sand Lake','NY'),('Sand Point','AK'),('Sand Springs','MT'),('Sand Springs','OK'),('Sandborn','IN'),('Sanders','AZ'),('Sanders','KY'),('Sanders','MT'),('Sanderson','FL'),('Sanderson','TX'),('Sandersville','GA'),('Sandersville','MS'),('Sandgap','KY'),('Sandhill','MS'),('Sandia','TX'),('Sandia Park','NM'),('Sandisfield','MA'),('Sandoval','IL'),('Sandown','NH'),('Sandpoint','ID'),('Sandston','VA'),('Sandstone','MN'),('Sandstone','WV'),('Sandusky','MI'),('Sandusky','NY'),('Sandusky','OH'),('Sandwich','IL'),('Sandwich','MA'),('Sandy','OR'),('Sandy','UT'),('Sandy Creek','NY'),('Sandy Hook','CT'),('Sandy Hook','KY'),('Sandy Hook','MS'),('Sandy Hook','VA'),('Sandy Lake','PA'),('Sandy Level','VA'),('Sandy Point','ME'),('Sandy Point','VA'),('Sandy Ridge','NC'),('Sandy Ridge','PA'),('Sandy Spring','MD'),('Sandy Springs','SC'),('Sandyville','OH'),('Sandyville','WV'),('Sanford','CO'),('Sanford','FL'),('Sanford','ME'),('Sanford','MI'),('Sanford','NC'),('Sanford','TX'),('Sanford','VA'),('Sanger','CA'),('Sanger','TX'),('Sangerfield','NY'),('Sangerville','ME'),('Sanibel','FL'),('Sanostee','NM'),('Santa','ID'),('Santa Ana','CA'),('Santa Anna','TX'),('Santa Barbara','CA'),('Santa Clara','CA'),('Santa Clara','NM'),('Santa Clara','UT'),('Santa Clarita','CA'),('Santa Claus','IN'),('Santa Cruz','CA'),('Santa Cruz','NM'),('Santa Elena','TX'),('Santa Fe','MO'),('Santa Fe','NM'),('Santa Fe','TN'),('Santa Fe','TX'),('Santa Fe Springs','CA'),('Santa Isabel','PR'),('Santa Margarita','CA'),('Santa Maria','CA'),('Santa Maria','TX'),('Santa Monica','CA'),('Santa Paula','CA'),('Santa Rita Park','CA'),('Santa Rosa','CA'),('Santa Rosa','NM'),('Santa Rosa','TX'),('Santa Rosa Beach','FL'),('Santa Teresa','NM'),('Santa Ynez','CA'),('Santa Ysabel','CA'),('Santaquin','UT'),('Santee','CA'),('Santee','SC'),('Santiago','MN'),('Santo','TX'),('Santo Domingo Pueblo','NM'),('Sapello','NM'),('Sapelo Island','GA'),('Sapphire','NC'),('Sapulpa','OK'),('Saragosa','TX'),('Sarah','MS'),('Sarah Ann','WV'),('Sarahsville','OH'),('Saraland','AL'),('Saranac','MI'),('Saranac','NY'),('Saranac Lake','NY'),('Sarasota','FL'),('Saratoga','AR'),('Saratoga','CA'),('Saratoga','IN'),('Saratoga','NC'),('Saratoga','TX'),('Saratoga','WY'),('Saratoga Springs','NY'),('Saratoga Springs','UT'),('Sarcoxie','MO'),('Sardinia','NY'),('Sardinia','OH'),('Sardinia','SC'),('Sardis','AL'),('Sardis','GA'),('Sardis','MS'),('Sardis','OH'),('Sardis','TN'),('Sarepta','LA'),('Sargeant','MN'),('Sargent','GA'),('Sargent','NE'),('Sargents','CO'),('Sargentville','ME'),('Sarita','TX'),('Sarles','ND'),('Sarona','WI'),('Saronville','NE'),('Sartell','MN'),('Sarver','PA'),('Sasabe','AZ'),('Sasakwa','OK'),('Sassafras','KY'),('Sassamansville','PA'),('Sasser','GA'),('Satanta','KS'),('Satartia','MS'),('Satellite Beach','FL'),('Satin','TX'),('Satsop','WA'),('Satsuma','AL'),('Satsuma','FL'),('Saucier','MS'),('Saugatuck','MI'),('Saugerties','NY'),('Saugus','MA'),('Sauk Centre','MN'),('Sauk City','WI'),('Sauk Rapids','MN'),('Saukville','WI'),('Saul','KY'),('Saulsbury','TN'),('Saulsville','WV'),('Sault Sainte Marie','MI'),('Saunderstown','RI'),('Saunemin','IL'),('Sauquoit','NY'),('Sausalito','CA'),('Sautee Nacoochee','GA'),('Savage','MD'),('Savage','MN'),('Savage','MT'),('Savanna','IL'),('Savanna','OK'),('Savannah','GA'),('Savannah','MO'),('Savannah','NY'),('Savannah','OH'),('Savannah','TN'),('Saverton','MO'),('Savery','WY'),('Savona','NY'),('Savonburg','KS'),('Savoonga','AK'),('Savoy','IL'),('Savoy','MA'),('Savoy','TX'),('Sawyer','KS'),('Sawyer','MI'),('Sawyer','MN'),('Sawyer','ND'),('Sawyer','OK'),('Sawyerville','AL'),('Sawyerville','IL'),('Saxapahaw','NC'),('Saxe','VA'),('Saxeville','WI'),('Saxis','VA'),('Saxon','WI'),('Saxon','WV'),('Saxonburg','PA'),('Saxton','PA'),('Saxtons River','VT'),('Saybrook','IL'),('Saylorsburg','PA'),('Sayner','WI'),('Sayre','AL'),('Sayre','OK'),('Sayre','PA'),('Sayreville','NJ'),('Sayville','NY'),('Scales Mound','IL'),('Scalf','KY'),('Scaly Mountain','NC'),('Scammon','KS'),('Scammon Bay','AK'),('Scandia','KS'),('Scandia','MN'),('Scandinavia','WI'),('Scappoose','OR'),('Scarborough','ME'),('Scarbro','WV'),('Scarsdale','NY'),('Scarville','IA'),('Scenery Hill','PA'),('Scenic','SD'),('Schaefferstown','PA'),('Schaghticoke','NY'),('Schaller','IA'),('Schaumburg','IL'),('Schell City','MO'),('Scheller','IL'),('Schellsburg','PA'),('Schenectady','NY'),('Schenevus','NY'),('Schenley','PA'),('Schererville','IN'),('Schertz','TX'),('Schiller Park','IL'),('Schlater','MS'),('Schleswig','IA'),('Schley','VA'),('Schnecksville','PA'),('Schneider','IN'),('Schnellville','IN'),('Schodack Landing','NY'),('Schoenchen','KS'),('Schofield','WI'),('Schofield Barracks','HI'),('Schoharie','NY'),('Schoolcraft','MI'),('Schooleys Mountain','NJ'),('Schriever','LA'),('Schroeder','MN'),('Schroon Lake','NY'),('Schulenburg','TX'),('Schulter','OK'),('Schurz','NV'),('Schuyler','NE'),('Schuyler','VA'),('Schuyler Falls','NY'),('Schuyler Lake','NY'),('Schuylerville','NY'),('Schuylkill Haven','PA'),('Schwenksville','PA'),('Schwertner','TX'),('Science Hill','KY'),('Scio','NY'),('Scio','OH'),('Scio','OR'),('Sciota','IL'),('Sciota','PA'),('Scioto Furnace','OH'),('Scipio','IN'),('Scipio','UT'),('Scipio Center','NY'),('Scituate','MA'),('Scobey','MS'),('Scobey','MT'),('Scooba','MS'),('Scotch Plains','NJ'),('Scotia','CA'),('Scotia','NE'),('Scotia','SC'),('Scotland','AR'),('Scotland','CT'),('Scotland','GA'),('Scotland','IN'),('Scotland','MD'),('Scotland','PA'),('Scotland','SD'),('Scotland','TX'),('Scotland Neck','NC'),('Scotrun','PA'),('Scott','AR'),('Scott','LA'),('Scott','MS'),('Scott','OH'),('Scott Air Force Base','IL'),('Scott Bar','CA'),('Scott City','KS'),('Scott City','MO'),('Scott Depot','WV'),('Scottdale','GA'),('Scottdale','PA'); + +INSERT INTO [cities] VALUES ('Scottown','OH'),('Scotts','MI'),('Scotts','NC'),('Scotts Hill','TN'),('Scotts Mills','OR'),('Scotts Valley','CA'),('Scottsbluff','NE'),('Scottsboro','AL'),('Scottsburg','IN'),('Scottsburg','NY'),('Scottsburg','OR'),('Scottsburg','VA'),('Scottsdale','AZ'),('Scottsmoor','FL'),('Scottsville','KY'),('Scottsville','NY'),('Scottsville','TX'),('Scottsville','VA'),('Scottville','IL'),('Scottville','MI'),('Scottville','NC'),('Scranton','AR'),('Scranton','IA'),('Scranton','KS'),('Scranton','NC'),('Scranton','ND'),('Scranton','PA'),('Scranton','SC'),('Screven','GA'),('Scribner','NE'),('Scroggins','TX'),('Scuddy','KY'),('Scurry','TX'),('Sea Cliff','NY'),('Sea Girt','NJ'),('Sea Island','GA'),('Sea Isle City','NJ'),('Seabeck','WA'),('Seaboard','NC'),('Seabrook','NH'),('Seabrook','SC'),('Seabrook','TX'),('Seadrift','TX'),('Seaford','DE'),('Seaford','NY'),('Seaford','VA'),('Seaforth','MN'),('Seagoville','TX'),('Seagraves','TX'),('Seagrove','NC'),('Seahurst','WA'),('Seal Beach','CA'),('Seal Cove','ME'),('Seal Harbor','ME'),('Seal Rock','OR'),('Seale','AL'),('Sealevel','NC'),('Sealston','VA'),('Sealy','TX'),('Seaman','OH'),('Seanor','PA'),('Searchlight','NV'),('Searcy','AR'),('Searles','MN'),('Sears','MI'),('Searsboro','IA'),('Searsmont','ME'),('Searsport','ME'),('Seaside','CA'),('Seaside','OR'),('Seaside Heights','NJ'),('Seaside Park','NJ'),('Seaton','IL'),('Seatonville','IL'),('Seattle','WA'),('Seaview','VA'),('Seaview','WA'),('Sebago','ME'),('Sebago Lake','ME'),('Sebasco Estates','ME'),('Sebastian','FL'),('Sebastian','TX'),('Sebastopol','CA'),('Sebastopol','MS'),('Sebec','ME'),('Sebeka','MN'),('Sebewaing','MI'),('Sebree','KY'),('Sebring','FL'),('Sebring','OH'),('Secaucus','NJ'),('Seco','KY'),('Second Mesa','AZ'),('Secondcreek','WV'),('Secor','IL'),('Secretary','MD'),('Section','AL'),('Sedalia','CO'),('Sedalia','IN'),('Sedalia','KY'),('Sedalia','MO'),('Sedalia','NC'),('Sedalia','OH'),('Sedan','KS'),('Sedan','NM'),('Sedgewickville','MO'),('Sedgwick','AR'),('Sedgwick','CO'),('Sedgwick','KS'),('Sedgwick','ME'),('Sedley','VA'),('Sedona','AZ'),('Sedro Woolley','WA'),('Seekonk','MA'),('Seeley','CA'),('Seeley Lake','MT'),('Seelyville','IN'),('Seffner','FL'),('Seguin','TX'),('Seiad Valley','CA'),('Seibert','CO'),('Seiling','OK'),('Sekiu','WA'),('Selah','WA'),('Selawik','AK'),('Selby','SD'),('Selbyville','DE'),('Selbyville','WV'),('Selden','KS'),('Selden','NY'),('Seldovia','AK'),('Selfridge','ND'),('Seligman','AZ'),('Seligman','MO'),('Selinsgrove','PA'),('Selkirk','NY'),('Sellers','SC'),('Sellersburg','IN'),('Sellersville','PA'),('Sells','AZ'),('Selma','AL'),('Selma','CA'),('Selma','IA'),('Selma','IN'),('Selma','NC'),('Selma','OR'),('Selma','VA'),('Selman City','TX'),('Selmer','TN'),('Seltzer','PA'),('Seminary','MS'),('Seminole','AL'),('Seminole','FL'),('Seminole','OK'),('Seminole','PA'),('Seminole','TX'),('Semmes','AL'),('Semora','NC'),('Senath','MO'),('Senatobia','MS'),('Seneca','IL'),('Seneca','KS'),('Seneca','MO'),('Seneca','NE'),('Seneca','OR'),('Seneca','PA'),('Seneca','SC'),('Seneca','SD'),('Seneca','WI'),('Seneca Castle','NY'),('Seneca Falls','NY'),('Seneca Rocks','WV'),('Senecaville','OH'),('Seney','MI'),('Senoia','GA'),('Sentinel','OK'),('Sentinel Butte','ND'),('Sequatchie','TN'),('Sequim','WA'),('Sequoia National Park','CA'),('Serafina','NM'),('Serena','IL'),('Sergeant Bluff','IA'),('Sergeantsville','NJ'),('Servia','IN'),('Sesser','IL'),('Seth','WV'),('Seven Mile','OH'),('Seven Springs','NC'),('Seven Valleys','PA'),('Severance','CO'),('Severance','NY'),('Severn','MD'),('Severn','NC'),('Severn','VA'),('Severna Park','MD'),('Severy','KS'),('Sevier','UT'),('Sevierville','TN'),('Seville','FL'),('Seville','GA'),('Seville','OH'),('Sewanee','TN'),('Seward','AK'),('Seward','IL'),('Seward','NE'),('Seward','PA'),('Sewaren','NJ'),('Sewell','NJ'),('Sewickley','PA'),('Sextons Creek','KY'),('Sextonville','WI'),('Seymour','CT'),('Seymour','IA'),('Seymour','IL'),('Seymour','IN'),('Seymour','MO'),('Seymour','TN'),('Seymour','TX'),('Seymour','WI'),('Shabbona','IL'),('Shacklefords','VA'),('Shade','OH'),('Shade Gap','PA'),('Shady Cove','OR'),('Shady Dale','GA'),('Shady Grove','FL'),('Shady Grove','PA'),('Shady Point','OK'),('Shady Side','MD'),('Shady Spring','WV'),('Shady Valley','TN'),('Shadyside','OH'),('Shafer','MN'),('Shafter','CA'),('Shaftsburg','MI'),('Shaftsbury','VT'),('Shageluk','AK'),('Shakopee','MN'),('Shaktoolik','AK'),('Shalimar','FL'),('Shallotte','NC'),('Shallowater','TX'),('Shambaugh','IA'),('Shamokin','PA'),('Shamokin Dam','PA'),('Shamrock','OK'),('Shamrock','TX'),('Shandaken','NY'),('Shandon','CA'),('Shandon','OH'),('Shaniko','OR'),('Shanks','WV'),('Shanksville','PA'),('Shannock','RI'),('Shannon','AL'),('Shannon','GA'),('Shannon','IL'),('Shannon','MS'),('Shannon','NC'),('Shannon City','IA'),('Shapleigh','ME'),('Sharon','CT'),('Sharon','GA'),('Sharon','KS'),('Sharon','MA'),('Sharon','MS'),('Sharon','ND'),('Sharon','OK'),('Sharon','PA'),('Sharon','SC'),('Sharon','TN'),('Sharon','VT'),('Sharon','WI'),('Sharon Center','OH'),('Sharon Grove','KY'),('Sharon Hill','PA'),('Sharon Springs','KS'),('Sharon Springs','NY'),('Sharpes','FL'),('Sharples','WV'),('Sharps','VA'),('Sharps Chapel','TN'),('Sharpsburg','GA'),('Sharpsburg','IA'),('Sharpsburg','KY'),('Sharpsburg','MD'),('Sharpsburg','NC'),('Sharpsburg','OH'),('Sharpsville','IN'),('Sharpsville','PA'),('Sharptown','MD'),('Shartlesville','PA'),('Shasta','CA'),('Shasta Lake','CA'),('Shattuck','OK'),('Shauck','OH'),('Shaver Lake','CA'),('Shavertown','PA'),('Shaw','MS'),('Shaw A F B','SC'),('Shaw Island','WA'),('Shawanee','TN'),('Shawanese','PA'),('Shawano','WI'),('Shawboro','NC'),('Shawmut','ME'),('Shawmut','MT'),('Shawnee','CO'),('Shawnee','KS'),('Shawnee','OH'),('Shawnee','OK'),('Shawnee','WY'),('Shawnee Mission','KS'),('Shawnee On Delaware','PA'),('Shawneetown','IL'),('Shawsville','VA'),('Shawville','PA'),('Sheakleyville','PA'),('Sheboygan','WI'),('Sheboygan Falls','WI'),('Shedd','OR'),('Sheep Ranch','CA'),('Sheep Springs','NM'),('Sheffield','AL'),('Sheffield','IA'),('Sheffield','IL'),('Sheffield','MA'),('Sheffield','PA'),('Sheffield','TX'),('Sheffield','VT'),('Sheffield Lake','OH'),('Shelbiana','KY'),('Shelbina','MO'),('Shelburn','IN'),('Shelburne','VT'),('Shelburne Falls','MA'),('Shelby','AL'),('Shelby','IA'),('Shelby','IN'),('Shelby','MI'),('Shelby','MS'),('Shelby','MT'),('Shelby','NC'),('Shelby','NE'),('Shelby','OH'),('Shelby Gap','KY'),('Shelbyville','IL'),('Shelbyville','IN'),('Shelbyville','KY'),('Shelbyville','MI'),('Shelbyville','MO'),('Shelbyville','TN'),('Shelbyville','TX'),('Sheldahl','IA'),('Sheldon','IA'),('Sheldon','IL'),('Sheldon','MO'),('Sheldon','ND'),('Sheldon','SC'),('Sheldon','VT'),('Sheldon','WI'),('Sheldon Springs','VT'),('Sheldonville','MA'),('Shell','WY'),('Shell Knob','MO'),('Shell Lake','WI'),('Shell Rock','IA'),('Shelley','ID'),('Shellman','GA'),('Shellsburg','IA'),('Shelly','MN'),('Shelocta','PA'),('Shelter Island','NY'),('Shelter Island Heights','NY'),('Shelton','CT'),('Shelton','NE'),('Shelton','WA'),('Shenandoah','IA'),('Shenandoah','PA'),('Shenandoah','VA'),('Shenandoah Junction','WV'),('Shenorock','NY'),('Shepardsville','IN'),('Shepherd','MI'),('Shepherd','MT'),('Shepherd','TX'),('Shepherdstown','WV'),('Shepherdsville','KY'),('Sheppard Afb','TX'),('Sheppton','PA'),('Sherard','MS'),('Sherborn','MA'),('Sherburn','MN'),('Sherburne','NY'),('Sheridan','AR'),('Sheridan','CA'),('Sheridan','IL'),('Sheridan','IN'),('Sheridan','ME'),('Sheridan','MI'),('Sheridan','MO'),('Sheridan','MT'),('Sheridan','NY'),('Sheridan','OR'),('Sheridan','TX'),('Sheridan','WY'),('Sheridan Lake','CO'),('Sherman','CT'),('Sherman','IL'),('Sherman','ME'),('Sherman','MS'),('Sherman','NY'),('Sherman','TX'),('Sherman Oaks','CA'),('Shermans Dale','PA'),('Sherrard','IL'),('Sherrill','AR'),('Sherrill','IA'),('Sherrill','NY'),('Sherrills Ford','NC'),('Sherrodsville','OH'),('Sherwood','AR'),('Sherwood','MD'),('Sherwood','MI'),('Sherwood','ND'),('Sherwood','OH'),('Sherwood','OR'),('Sherwood','TN'),('Sherwood','WI'),('Shevlin','MN'),('Sheyenne','ND'),('Shickley','NE'),('Shickshinny','PA'),('Shidler','OK'),('Shields','ND'),('Shiloh','GA'),('Shiloh','NC'),('Shiloh','NJ'),('Shiloh','OH'),('Shiloh','TN'),('Shiner','TX'),('Shingle Springs','CA'),('Shinglehouse','PA'),('Shingleton','MI'),('Shingletown','CA'),('Shinhopple','NY'),('Shinnston','WV'),('Shiocton','WI'),('Shipman','IL'),('Shipman','VA'),('Shippensburg','PA'),('Shippenville','PA'),('Shippingport','PA'),('Shiprock','NM'),('Shipshewana','IN'),('Shirland','IL'),('Shirley','AR'),('Shirley','IL'),('Shirley','IN'),('Shirley','MA'),('Shirley','NY'),('Shirley','WV'),('Shirley Basin','WY'),('Shirley Mills','ME'),('Shirleysburg','PA'),('Shiro','TX'),('Shishmaref','AK'),('Shoals','IN'),('Shoals','WV'),('Shobonier','IL'),('Shock','WV'),('Shoemakersville','PA'),('Shohola','PA'),('Shokan','NY'),('Shongaloo','LA'),('Shonto','AZ'),('Shook','MO'),('Shoreham','NY'),('Shoreham','VT'),('Shorewood','IL'),('Short Creek','WV'),('Short Hills','NJ'),('Shorter','AL'),('Shorterville','AL'),('Shortsville','NY'),('Shortt Gap','VA'),('Shoshone','CA'),('Shoshone','ID'),('Shoshoni','WY'),('Shoup','ID'),('Show Low','AZ'),('Showell','MD'),('Shreve','OH'),('Shreveport','LA'),('Shrewsbury','MA'),('Shrewsbury','NJ'),('Shrewsbury','PA'),('Shrub Oak','NY'),('Shubert','NE'); + +INSERT INTO [cities] VALUES ('Shubuta','MS'),('Shullsburg','WI'),('Shumway','IL'),('Shungnak','AK'),('Shunk','PA'),('Shuqualak','MS'),('Shushan','NY'),('Shutesbury','MA'),('Siasconset','MA'),('Sibley','IA'),('Sibley','IL'),('Sibley','LA'),('Sibley','MO'),('Sibley','MS'),('Sicily Island','LA'),('Sicklerville','NJ'),('Side Lake','MN'),('Sidell','IL'),('Sidman','PA'),('Sidnaw','MI'),('Sidney','AR'),('Sidney','IA'),('Sidney','IL'),('Sidney','KY'),('Sidney','MI'),('Sidney','MT'),('Sidney','NE'),('Sidney','NY'),('Sidney','OH'),('Sidney','TX'),('Sidney Center','NY'),('Sidon','MS'),('Sieper','LA'),('Sierra Blanca','TX'),('Sierra City','CA'),('Sierra Madre','CA'),('Sierra Vista','AZ'),('Sierraville','CA'),('Sigel','IL'),('Sigel','PA'),('Signal Hill','CA'),('Signal Mountain','TN'),('Sigourney','IA'),('Sigurd','UT'),('Sikes','LA'),('Sikeston','MO'),('Silas','AL'),('Siler','KY'),('Siler City','NC'),('Silerton','TN'),('Siletz','OR'),('Silex','MO'),('Siloam','GA'),('Siloam','NC'),('Siloam Springs','AR'),('Silsbee','TX'),('Silt','CO'),('Siluria','AL'),('Silva','MO'),('Silvana','WA'),('Silver','TX'),('Silver Bay','MN'),('Silver Bay','NY'),('Silver Beach','MA'),('Silver City','IA'),('Silver City','MS'),('Silver City','NM'),('Silver City','NV'),('Silver Creek','GA'),('Silver Creek','MN'),('Silver Creek','MS'),('Silver Creek','NE'),('Silver Creek','NY'),('Silver Creek','WA'),('Silver Gate','MT'),('Silver Grove','KY'),('Silver Lake','IN'),('Silver Lake','KS'),('Silver Lake','MN'),('Silver Lake','NH'),('Silver Lake','NY'),('Silver Lake','OR'),('Silver Lake','WI'),('Silver Plume','CO'),('Silver Point','TN'),('Silver Spring','MD'),('Silver Spring','PA'),('Silver Springs','FL'),('Silver Springs','NV'),('Silver Springs','NY'),('Silver Star','MT'),('Silverado','CA'),('Silverdale','PA'),('Silverdale','WA'),('Silverhill','AL'),('Silverlake','WA'),('Silverpeak','NV'),('Silverstreet','SC'),('Silverthorne','CO'),('Silverton','CO'),('Silverton','ID'),('Silverton','OR'),('Silverton','TX'),('Silverwood','MI'),('Silvis','IL'),('Simi Valley','CA'),('Simla','CO'),('Simmesport','LA'),('Simms','MT'),('Simms','TX'),('Simon','WV'),('Simonton','TX'),('Simpson','IL'),('Simpson','KS'),('Simpson','LA'),('Simpson','NC'),('Simpson','WV'),('Simpsonville','KY'),('Simpsonville','MD'),('Simpsonville','SC'),('Sims','AR'),('Sims','IL'),('Sims','NC'),('Simsboro','LA'),('Simsbury','CT'),('Sinai','SD'),('Sinclair','ME'),('Sinclair','WY'),('Sinclairville','NY'),('Singer','LA'),('Singers Glen','VA'),('Sinking Spring','OH'),('Sinks Grove','WV'),('Sinnamahoning','PA'),('Sinsinawa','WI'),('Sinton','TX'),('Sioux Center','IA'),('Sioux City','IA'),('Sioux Falls','SD'),('Sioux Rapids','IA'),('Sipesville','PA'),('Sipsey','AL'),('Siren','WI'),('Sisseton','SD'),('Sister Bay','WI'),('Sisters','OR'),('Sistersville','WV'),('Sitka','AK'),('Sitka','KY'),('Six Lakes','MI'),('Six Mile','SC'),('Six Mile Run','PA'),('Sixes','OR'),('Sizerock','KY'),('Skagway','AK'),('Skamokawa','WA'),('Skandia','MI'),('Skaneateles','NY'),('Skaneateles Falls','NY'),('Skanee','MI'),('Skellytown','TX'),('Skelton','WV'),('Skiatook','OK'),('Skidmore','MO'),('Skidmore','TX'),('Skillman','NJ'),('Skippack','PA'),('Skippers','VA'),('Skipperville','AL'),('Skipwith','VA'),('Skokie','IL'),('Skowhegan','ME'),('Skull Valley','AZ'),('Skwentna','AK'),('Skyforest','CA'),('Skykomish','WA'),('Skyland','NC'),('Skytop','PA'),('Slab Fork','WV'),('Slade','KY'),('Slagle','LA'),('Slanesville','WV'),('Slate Hill','NY'),('Slate Run','PA'),('Slate Spring','MS'),('Slatedale','PA'),('Slater','CO'),('Slater','IA'),('Slater','MO'),('Slater','SC'),('Slatersville','RI'),('Slaterville Springs','NY'),('Slatington','PA'),('Slaton','TX'),('Slatyfork','WV'),('Slaughter','LA'),('Slaughters','KY'),('Slayden','TN'),('Slayton','MN'),('Sledge','MS'),('Sleepy Eye','MN'),('Sleetmute','AK'),('Slemp','KY'),('Slick','OK'),('Slickville','PA'),('Slidell','LA'),('Slidell','TX'),('Sligo','PA'),('Slinger','WI'),('Slingerlands','NY'),('Slippery Rock','PA'),('Sloan','IA'),('Sloan','NV'),('Sloansville','NY'),('Sloatsburg','NY'),('Slocomb','AL'),('Slocum','RI'),('Sloughhouse','CA'),('Slovan','PA'),('Smackover','AR'),('Smallwood','NY'),('Smarr','GA'),('Smartt','TN'),('Smartville','CA'),('Smelterville','ID'),('Smethport','PA'),('Smicksburg','PA'),('Smilax','KY'),('Smiley','TX'),('Smith','NV'),('Smith Center','KS'),('Smith Lake','NM'),('Smith Mills','KY'),('Smith River','CA'),('Smithboro','IL'),('Smithboro','NY'),('Smithburg','WV'),('Smithdale','MS'),('Smithers','WV'),('Smithfield','IL'),('Smithfield','KY'),('Smithfield','ME'),('Smithfield','NC'),('Smithfield','NE'),('Smithfield','OH'),('Smithfield','PA'),('Smithfield','RI'),('Smithfield','UT'),('Smithfield','VA'),('Smithfield','WV'),('Smithland','IA'),('Smithland','KY'),('Smithmill','PA'),('Smiths Creek','MI'),('Smiths Grove','KY'),('Smiths Station','AL'),('Smithsburg','MD'),('Smithshire','IL'),('Smithton','IL'),('Smithton','MO'),('Smithton','PA'),('Smithtown','NY'),('Smithville','AR'),('Smithville','GA'),('Smithville','IN'),('Smithville','MO'),('Smithville','MS'),('Smithville','OH'),('Smithville','OK'),('Smithville','TN'),('Smithville','TX'),('Smithville','WV'),('Smithville Flats','NY'),('Smithwick','SD'),('Smoaks','SC'),('Smock','PA'),('Smokerun','PA'),('Smoketown','PA'),('Smoot','WV'),('Smoot','WY'),('Smyer','TX'),('Smyrna','DE'),('Smyrna','GA'),('Smyrna','MI'),('Smyrna','NC'),('Smyrna','NY'),('Smyrna','SC'),('Smyrna','TN'),('Smyrna Mills','ME'),('Sneads','FL'),('Sneads Ferry','NC'),('Sneedville','TN'),('Snelling','CA'),('Snellville','GA'),('Snohomish','WA'),('Snook','TX'),('Snoqualmie','WA'),('Snoqualmie Pass','WA'),('Snover','MI'),('Snow','OK'),('Snow Camp','NC'),('Snow Hill','MD'),('Snow Hill','NC'),('Snow Lake','AR'),('Snow Shoe','PA'),('Snowflake','AZ'),('Snowmass','CO'),('Snowmass Village','CO'),('Snowshoe','WV'),('Snowville','UT'),('Snyder','CO'),('Snyder','NE'),('Snyder','OK'),('Snyder','TX'),('Snydersburg','PA'),('Snydertown','PA'),('Soap Lake','WA'),('Sobieski','WI'),('Social Circle','GA'),('Society Hill','SC'),('Socorro','NM'),('Sod','WV'),('Soda Springs','CA'),('Soda Springs','ID'),('Soddy Daisy','TN'),('Sodus','MI'),('Sodus','NY'),('Sodus Point','NY'),('Solana Beach','CA'),('Solano','NM'),('Soldier','IA'),('Soldier','KS'),('Soldier','KY'),('Soldiers Grove','WI'),('Soldotna','AK'),('Solebury','PA'),('Soledad','CA'),('Solen','ND'),('Solgohachia','AR'),('Solo','MO'),('Solomon','AZ'),('Solomon','KS'),('Solomons','MD'),('Solon','IA'),('Solon','ME'),('Solon','OH'),('Solon Springs','WI'),('Solsberry','IN'),('Solsville','NY'),('Solvang','CA'),('Solway','MN'),('Somerdale','NJ'),('Somerdale','OH'),('Somers','CT'),('Somers','IA'),('Somers','MT'),('Somers','NY'),('Somers','WI'),('Somers Point','NJ'),('Somerset','CA'),('Somerset','CO'),('Somerset','IN'),('Somerset','KY'),('Somerset','MA'),('Somerset','MI'),('Somerset','NJ'),('Somerset','OH'),('Somerset','PA'),('Somerset','TX'),('Somerset','VA'),('Somerset','WI'),('Somerset Center','MI'),('Somersville','CT'),('Somersworth','NH'),('Somerton','AZ'),('Somerville','AL'),('Somerville','IN'),('Somerville','MA'),('Somerville','NJ'),('Somerville','OH'),('Somerville','TN'),('Somerville','TX'),('Somerville','VA'),('Somes Bar','CA'),('Somis','CA'),('Somonauk','IL'),('Sondheimer','LA'),('Sonoita','AZ'),('Sonoma','CA'),('Sonora','CA'),('Sonora','KY'),('Sonora','TX'),('Sontag','MS'),('Sonyea','NY'),('Sopchoppy','FL'),('Soper','OK'),('Soperton','GA'),('Sophia','NC'),('Sophia','WV'),('Soquel','CA'),('Sorento','IL'),('Sorrento','FL'),('Sorrento','LA'),('Sorrento','ME'),('Soso','MS'),('Soudan','MN'),('Souderton','PA'),('Soulsbyville','CA'),('Sound Beach','NY'),('Sour Lake','TX'),('Souris','ND'),('South Acworth','NH'),('South Amana','IA'),('South Amboy','NJ'),('South Barre','MA'),('South Barre','VT'),('South Bay','FL'),('South Beach','OR'),('South Beloit','IL'),('South Bend','IN'),('South Bend','NE'),('South Bend','TX'),('South Bend','WA'),('South Berwick','ME'),('South Bethlehem','NY'),('South Bloomingville','OH'),('South Boardman','MI'),('South Boston','VA'),('South Bound Brook','NJ'),('South Branch','MI'),('South Bristol','ME'),('South Britain','CT'),('South Burlington','VT'),('South Butler','NY'),('South Byron','NY'),('South Cairo','NY'),('South Canaan','PA'),('South Carrollton','KY'),('South Carver','MA'),('South Casco','ME'),('South Charleston','OH'),('South Chatham','MA'),('South China','ME'),('South Cle Elum','WA'),('South Colby','WA'),('South Colton','NY'),('South Dartmouth','MA'),('South Dayton','NY'),('South Deerfield','MA'),('South Dennis','MA'),('South Dennis','NJ'),('South Dos Palos','CA'),('South Easton','MA'),('South Egremont','MA'),('South El Monte','CA'),('South Elgin','IL'),('South English','IA'),('South Fallsburg','NY'),('South Fork','CO'),('South Fork','MO'),('South Fork','PA'),('South Freeport','ME'),('South Fulton','TN'),('South Gardiner','ME'),('South Gate','CA'),('South Gibson','PA'),('South Glastonbury','CT'),('South Glens Falls','NY'),('South Grafton','MA'),('South Greenfield','MO'),('South Hackensack','NJ'),('South Hadley','MA'),('South Hamilton','MA'),('South Hampton','NH'),('South Harwich','MA'),('South Haven','KS'),('South Haven','MI'),('South Haven','MN'),('South Heart','ND'),('South Heights','PA'),('South Hero','VT'),('South Hill','VA'),('South Holland','IL'),('South Houston','TX'),('South Hutchinson','KS'),('South International Falls','MN'),('South Jamesport','NY'),('South Jordan','UT'),('South Kent','CT'),('South Kortright','NY'),('South Lake Tahoe','CA'),('South Lancaster','MA'),('South Lebanon','OH'),('South Lee','MA'),('South Lima','NY'),('South Londonderry','VT'),('South Lyme','CT'),('South Lyon','MI'),('South Milford','IN'),('South Mills','NC'); + +INSERT INTO [cities] VALUES ('South Milwaukee','WI'),('South Montrose','PA'),('South Mountain','PA'),('South Naknek','AK'),('South New Berlin','NY'),('South Newbury','NH'),('South Newfane','VT'),('South Orange','NJ'),('South Orleans','MA'),('South Otselic','NY'),('South Ozone Park','NY'),('South Padre Island','TX'),('South Paris','ME'),('South Park','PA'),('South Pasadena','CA'),('South Pekin','IL'),('South Pittsburg','TN'),('South Plainfield','NJ'),('South Plains','TX'),('South Plymouth','NY'),('South Point','OH'),('South Pomfret','VT'),('South Portland','ME'),('South Portsmouth','KY'),('South Prairie','WA'),('South Range','MI'),('South Range','WI'),('South Richmond Hill','NY'),('South River','NJ'),('South Rockwood','MI'),('South Roxana','IL'),('South Royalton','VT'),('South Ryegate','VT'),('South Saint Paul','MN'),('South Salem','NY'),('South Salem','OH'),('South San Francisco','CA'),('South Seaville','NJ'),('South Shore','KY'),('South Shore','SD'),('South Sioux City','NE'),('South Solon','OH'),('South Sterling','PA'),('South Strafford','VT'),('South Sutton','NH'),('South Tamworth','NH'),('South Thomaston','ME'),('South Union','KY'),('South Vienna','OH'),('South Wales','NY'),('South Walpole','MA'),('South Wayne','WI'),('South Webster','OH'),('South Wellfleet','MA'),('South West City','MO'),('South Weymouth','MA'),('South Whitley','IN'),('South Williamson','KY'),('South Willington','CT'),('South Wilmington','IL'),('South Windham','CT'),('South Windham','ME'),('South Windsor','CT'),('South Woodstock','CT'),('South Woodstock','VT'),('South Yarmouth','MA'),('Southampton','MA'),('Southampton','NY'),('Southampton','PA'),('Southard','OK'),('Southaven','MS'),('Southborough','MA'),('Southbridge','MA'),('Southbury','CT'),('Southeastern','PA'),('Southern Md Facility','MD'),('Southern Pines','NC'),('Southfield','MA'),('Southfield','MI'),('Southfields','NY'),('Southgate','MI'),('Southington','CT'),('Southington','OH'),('Southlake','TX'),('Southmayd','TX'),('Southmont','NC'),('Southold','NY'),('Southport','CT'),('Southport','ME'),('Southport','NC'),('Southside','TN'),('Southside','WV'),('Southview','PA'),('Southwest','PA'),('Southwest Harbor','ME'),('Southwick','MA'),('Southworth','WA'),('Spade','TX'),('Spalding','MI'),('Spalding','NE'),('Spanaway','WA'),('Spangle','WA'),('Spangler','PA'),('Spanish Fork','UT'),('Spanish Fort','AL'),('Spanishburg','WV'),('Sparkill','NY'),('Sparkman','AR'),('Sparks','GA'),('Sparks','NE'),('Sparks','NV'),('Sparks','OK'),('Sparks Glencoe','MD'),('Sparland','IL'),('Sparr','FL'),('Sparrow Bush','NY'),('Sparrows Point','MD'),('Sparta','GA'),('Sparta','IL'),('Sparta','KY'),('Sparta','MI'),('Sparta','MO'),('Sparta','NC'),('Sparta','NJ'),('Sparta','OH'),('Sparta','TN'),('Sparta','VA'),('Sparta','WI'),('Spartanburg','SC'),('Spartansburg','PA'),('Spavinaw','OK'),('Spearfish','SD'),('Spearman','TX'),('Spearsville','LA'),('Spearville','KS'),('Speculator','NY'),('Speed','NC'),('Speedway','IN'),('Speedwell','TN'),('Speedwell','VA'),('Speer','IL'),('Spelter','WV'),('Spencer','IA'),('Spencer','ID'),('Spencer','IN'),('Spencer','MA'),('Spencer','NC'),('Spencer','NE'),('Spencer','NY'),('Spencer','OH'),('Spencer','OK'),('Spencer','SD'),('Spencer','TN'),('Spencer','VA'),('Spencer','WI'),('Spencer','WV'),('Spencerport','NY'),('Spencertown','NY'),('Spencerville','IN'),('Spencerville','MD'),('Spencerville','OH'),('Spencerville','OK'),('Speonk','NY'),('Sperry','IA'),('Sperry','OK'),('Sperryville','VA'),('Spiceland','IN'),('Spicer','MN'),('Spicewood','TX'),('Spickard','MO'),('Spillville','IA'),('Spindale','NC'),('Spinnerstown','PA'),('Spirit Lake','IA'),('Spirit Lake','ID'),('Spiritwood','ND'),('Spiro','OK'),('Spivey','KS'),('Splendora','TX'),('Spofford','NH'),('Spokane','MO'),('Spokane','WA'),('Spooner','WI'),('Spotswood','NJ'),('Spotsylvania','VA'),('Spottsville','KY'),('Spout Spring','VA'),('Spraggs','PA'),('Sprague','NE'),('Sprague','WA'),('Sprague','WV'),('Sprague River','OR'),('Spragueville','IA'),('Sprakers','NY'),('Sprankle Mills','PA'),('Spray','OR'),('Spreckels','CA'),('Spring','TX'),('Spring Arbor','MI'),('Spring Branch','TX'),('Spring Brook','NY'),('Spring Church','PA'),('Spring City','PA'),('Spring City','TN'),('Spring City','UT'),('Spring Creek','NV'),('Spring Creek','PA'),('Spring Creek','TN'),('Spring Dale','WV'),('Spring Gap','MD'),('Spring Garden','AL'),('Spring Glen','NY'),('Spring Glen','PA'),('Spring Green','WI'),('Spring Grove','IL'),('Spring Grove','MN'),('Spring Grove','PA'),('Spring Grove','VA'),('Spring Hill','FL'),('Spring Hill','KS'),('Spring Hill','TN'),('Spring Hope','NC'),('Spring House','PA'),('Spring Lake','MI'),('Spring Lake','MN'),('Spring Lake','NC'),('Spring Lake','NJ'),('Spring Mills','PA'),('Spring Mount','PA'),('Spring Park','MN'),('Spring Run','PA'),('Spring Valley','CA'),('Spring Valley','IL'),('Spring Valley','MN'),('Spring Valley','NY'),('Spring Valley','OH'),('Spring Valley','WI'),('Springboro','OH'),('Springboro','PA'),('Springbrook','IA'),('Springbrook','WI'),('Springdale','AR'),('Springdale','MT'),('Springdale','PA'),('Springdale','UT'),('Springdale','WA'),('Springer','NM'),('Springer','OK'),('Springerton','IL'),('Springerville','AZ'),('Springfield','AR'),('Springfield','CO'),('Springfield','GA'),('Springfield','ID'),('Springfield','IL'),('Springfield','KY'),('Springfield','LA'),('Springfield','MA'),('Springfield','ME'),('Springfield','MN'),('Springfield','MO'),('Springfield','NE'),('Springfield','NH'),('Springfield','NJ'),('Springfield','OH'),('Springfield','OR'),('Springfield','PA'),('Springfield','SC'),('Springfield','SD'),('Springfield','TN'),('Springfield','VA'),('Springfield','VT'),('Springfield','WI'),('Springfield','WV'),('Springfield Center','NY'),('Springfield Gardens','NY'),('Springhill','LA'),('Springlake','TX'),('Springport','IN'),('Springport','MI'),('Springs','PA'),('Springtown','PA'),('Springtown','TX'),('Springvale','ME'),('Springview','NE'),('Springville','AL'),('Springville','CA'),('Springville','IA'),('Springville','IN'),('Springville','NY'),('Springville','PA'),('Springville','TN'),('Springville','UT'),('Springwater','NY'),('Sproul','PA'),('Spruce','MI'),('Spruce Creek','PA'),('Spruce Head','ME'),('Spruce Pine','AL'),('Spruce Pine','NC'),('Spur','TX'),('Spurgeon','IN'),('Spurger','TX'),('Spurlockville','WV'),('Squaw Lake','MN'),('Squaw Valley','CA'),('Squire','WV'),('Squires','MO'),('Squirrel Island','ME'),('St Clairsville','PA'),('St Columbans','NE'),('Staatsburg','NY'),('Stacy','MN'),('Stacy','NC'),('Stacyville','IA'),('Stacyville','ME'),('Stafford','CT'),('Stafford','KS'),('Stafford','NY'),('Stafford','OH'),('Stafford','TX'),('Stafford','VA'),('Stafford Springs','CT'),('Staffordsville','KY'),('Staffordsville','VA'),('Staffordville','CT'),('Stahlstown','PA'),('Staley','NC'),('Stambaugh','KY'),('Stambaugh','MI'),('Stamford','CT'),('Stamford','NE'),('Stamford','NY'),('Stamford','TX'),('Stamford','VT'),('Stamping Ground','KY'),('Stamps','AR'),('Stanaford','WV'),('Stanardsville','VA'),('Stanberry','MO'),('Stanchfield','MN'),('Standard','CA'),('Standard','IL'),('Standish','CA'),('Standish','ME'),('Standish','MI'),('Stanfield','AZ'),('Stanfield','NC'),('Stanfield','OR'),('Stanford','CA'),('Stanford','IL'),('Stanford','IN'),('Stanford','KY'),('Stanford','MT'),('Stanfordville','NY'),('Stanhope','IA'),('Stanhope','NJ'),('Stanley','IA'),('Stanley','ID'),('Stanley','KY'),('Stanley','NC'),('Stanley','ND'),('Stanley','NM'),('Stanley','NY'),('Stanley','VA'),('Stanley','WI'),('Stanleytown','VA'),('Stanton','AL'),('Stanton','CA'),('Stanton','IA'),('Stanton','KY'),('Stanton','MI'),('Stanton','MO'),('Stanton','ND'),('Stanton','NE'),('Stanton','NJ'),('Stanton','TN'),('Stanton','TX'),('Stantonsburg','NC'),('Stantonville','TN'),('Stanville','KY'),('Stanwood','IA'),('Stanwood','MI'),('Stanwood','WA'),('Staplehurst','NE'),('Staples','MN'),('Staples','TX'),('Stapleton','AL'),('Stapleton','GA'),('Stapleton','NE'),('Star','ID'),('Star','MS'),('Star','NC'),('Star','TX'),('Star City','AR'),('Star City','IN'),('Star Junction','PA'),('Star Lake','NY'),('Star Lake','WI'),('Star Prairie','WI'),('Star Tannery','VA'),('Starbuck','MN'),('Starbuck','WA'),('Starford','PA'),('Stark','KS'),('Stark City','MO'),('Starke','FL'),('Starks','LA'),('Starksboro','VT'),('Starkville','MS'),('Starkweather','ND'),('Starlight','PA'),('Starr','SC'),('Starrucca','PA'),('Start','LA'),('Startex','SC'),('Startup','WA'),('State Center','IA'),('State College','PA'),('State Farm','VA'),('State Line','IN'),('State Line','MS'),('State Line','PA'),('State Park','SC'),('State Road','NC'),('State University','AR'),('Stateline','NV'),('Staten Island','NY'),('Statenville','GA'),('Statesboro','GA'),('Statesville','NC'),('Statham','GA'),('Staunton','IL'),('Staunton','IN'),('Staunton','VA'),('Stayton','OR'),('Steamboat Rock','IA'),('Steamboat Springs','CO'),('Steamburg','NY'),('Stearns','KY'),('Stebbins','AK'),('Stedman','NC'),('Steedman','MO'),('Steele','AL'),('Steele','KY'),('Steele','MO'),('Steele','ND'),('Steele City','NE'),('Steeles Tavern','VA'),('Steeleville','IL'),('Steelville','MO'),('Steen','MN'),('Steens','MS'),('Steep Falls','ME'),('Steger','IL'),('Stehekin','WA'),('Steilacoom','WA'),('Steinauer','NE'),('Steinhatchee','FL'),('Stella','MO'),('Stella','NC'),('Stella','NE'),('Stella Niagara','NY'),('Stem','NC'),('Stendal','IN'),('Stennis Space Center','MS'),('Stephan','SD'),('Stephen','MN'),('Stephens','AR'),('Stephens','GA'),('Stephens City','VA'),('Stephenson','MI'),('Stephenson','VA'),('Stephenson','WV'),('Stephensport','KY'),('Stephentown','NY'),('Stephenville','TX'),('Steptoe','WA'),('Sterling','AK'),('Sterling','CO'),('Sterling','CT'),('Sterling','IL'),('Sterling','KS'),('Sterling','MA'),('Sterling','MI'),('Sterling','ND'),('Sterling','NE'),('Sterling','NY'),('Sterling','OH'),('Sterling','OK'),('Sterling','PA'),('Sterling','UT'),('Sterling','VA'),('Sterling City','TX'),('Sterling Forest','NY'),('Sterling Heights','MI'),('Sterlington','LA'),('Sterrett','AL'),('Stet','MO'),('Stetson','ME'); + +INSERT INTO [cities] VALUES ('Stetsonville','WI'),('Steuben','ME'),('Steuben','WI'),('Steubenville','OH'),('Stevens','PA'),('Stevens Point','WI'),('Stevens Village','AK'),('Stevensburg','VA'),('Stevenson','AL'),('Stevenson','CT'),('Stevenson','MD'),('Stevenson','WA'),('Stevenson Ranch','CA'),('Stevensville','MD'),('Stevensville','MI'),('Stevensville','MT'),('Stevensville','PA'),('Stevensville','VA'),('Stevinson','CA'),('Steward','IL'),('Stewardson','IL'),('Stewart','MN'),('Stewart','MS'),('Stewart','OH'),('Stewart','TN'),('Stewarts Point','CA'),('Stewartstown','PA'),('Stewartsville','MO'),('Stewartsville','NJ'),('Stewartville','MN'),('Stickney','SD'),('Stidham','OK'),('Stigler','OK'),('Stilesville','IN'),('Still Pond','MD'),('Still River','MA'),('Stillman Valley','IL'),('Stillmore','GA'),('Stillwater','ME'),('Stillwater','MN'),('Stillwater','NJ'),('Stillwater','NY'),('Stillwater','OH'),('Stillwater','OK'),('Stillwater','PA'),('Stilwell','KS'),('Stilwell','OK'),('Stinesville','IN'),('Stinnett','KY'),('Stinnett','TX'),('Stinson Beach','CA'),('Stinson Lake','NH'),('Stirling','NJ'),('Stirling City','CA'),('Stirum','ND'),('Stites','ID'),('Stittville','NY'),('Stitzer','WI'),('Stockbridge','GA'),('Stockbridge','MA'),('Stockbridge','MI'),('Stockbridge','VT'),('Stockbridge','WI'),('Stockdale','OH'),('Stockdale','PA'),('Stockdale','TX'),('Stockertown','PA'),('Stockett','MT'),('Stockholm','ME'),('Stockholm','NJ'),('Stockholm','SD'),('Stockholm','WI'),('Stockland','IL'),('Stockport','IA'),('Stockport','OH'),('Stockton','AL'),('Stockton','CA'),('Stockton','GA'),('Stockton','IA'),('Stockton','IL'),('Stockton','KS'),('Stockton','MD'),('Stockton','MN'),('Stockton','MO'),('Stockton','NJ'),('Stockton','NY'),('Stockton','UT'),('Stockton Springs','ME'),('Stockville','NE'),('Stockwell','IN'),('Stoddard','NH'),('Stoddard','WI'),('Stokes','NC'),('Stokesdale','NC'),('Stollings','WV'),('Stone','KY'),('Stone Creek','OH'),('Stone Harbor','NJ'),('Stone Lake','WI'),('Stone Mountain','GA'),('Stone Park','IL'),('Stone Ridge','NY'),('Stoneboro','PA'),('Stonefort','IL'),('Stoneham','CO'),('Stoneham','MA'),('Stoneham','ME'),('Stoneville','MS'),('Stoneville','NC'),('Stonewall','LA'),('Stonewall','MS'),('Stonewall','NC'),('Stonewall','OK'),('Stonewall','TX'),('Stoney Fork','KY'),('Stonington','CT'),('Stonington','IL'),('Stonington','ME'),('Stony Brook','NY'),('Stony Creek','NY'),('Stony Creek','VA'),('Stony Point','NC'),('Stony Point','NY'),('Stony Ridge','OH'),('Stonyford','CA'),('Stopover','KY'),('Storden','MN'),('Storm Lake','IA'),('Stormville','NY'),('Storrie','CA'),('Storrs Mansfield','CT'),('Story','AR'),('Story','WY'),('Story City','IA'),('Stotts City','MO'),('Stottville','NY'),('Stoughton','MA'),('Stoughton','WI'),('Stout','IA'),('Stout','OH'),('Stoutland','MO'),('Stoutsville','MO'),('Stoutsville','OH'),('Stovall','NC'),('Stover','MO'),('Stow','MA'),('Stow','NY'),('Stow','OH'),('Stowe','VT'),('Stowell','TX'),('Stoy','IL'),('Stoystown','PA'),('Strabane','PA'),('Strafford','MO'),('Strafford','NH'),('Strafford','VT'),('Strandburg','SD'),('Strandquist','MN'),('Strang','NE'),('Strang','OK'),('Strasburg','CO'),('Strasburg','IL'),('Strasburg','MO'),('Strasburg','ND'),('Strasburg','OH'),('Strasburg','PA'),('Strasburg','VA'),('Stratford','CA'),('Stratford','CT'),('Stratford','IA'),('Stratford','NJ'),('Stratford','NY'),('Stratford','OK'),('Stratford','SD'),('Stratford','TX'),('Stratford','VA'),('Stratford','WA'),('Stratford','WI'),('Stratham','NH'),('Strathcona','MN'),('Strathmere','NJ'),('Strathmore','CA'),('Strattanville','PA'),('Stratton','CO'),('Stratton','ME'),('Stratton','NE'),('Stratton','OH'),('Straughn','IN'),('Strausstown','PA'),('Strawberry','AR'),('Strawberry','CA'),('Strawberry Plains','TN'),('Strawberry Point','IA'),('Strawberry Valley','CA'),('Strawn','IL'),('Strawn','TX'),('Streamwood','IL'),('Streator','IL'),('Street','MD'),('Streeter','ND'),('Streetman','TX'),('Streetsboro','OH'),('Stringer','MS'),('Stringtown','OK'),('Stroh','IN'),('Stromsburg','NE'),('Strong','AR'),('Strong','ME'),('Strong City','KS'),('Stronghurst','IL'),('Strongs','MI'),('Strongstown','PA'),('Strongsville','OH'),('Stroud','OK'),('Stroudsburg','PA'),('Strum','WI'),('Strunk','KY'),('Struthers','OH'),('Stryker','MT'),('Stryker','OH'),('Strykersville','NY'),('Stuart','FL'),('Stuart','IA'),('Stuart','NE'),('Stuart','OK'),('Stuart','VA'),('Stuarts Draft','VA'),('Studio City','CA'),('Studley','VA'),('Stump Creek','PA'),('Stumpy Point','NC'),('Sturbridge','MA'),('Sturdivant','MO'),('Sturgeon','MO'),('Sturgeon','PA'),('Sturgeon Bay','WI'),('Sturgeon Lake','MN'),('Sturgis','KY'),('Sturgis','MI'),('Sturgis','MS'),('Sturgis','SD'),('Sturkie','AR'),('Sturtevant','WI'),('Stuttgart','AR'),('Stuyvesant','NY'),('Stuyvesant Falls','NY'),('Suamico','WI'),('Subiaco','AR'),('Sublette','IL'),('Sublette','KS'),('Sublime','TX'),('Sublimity','OR'),('Suburb Maryland Fac','MD'),('Succasunna','NJ'),('Success','AR'),('Success','MO'),('Suches','GA'),('Sudan','TX'),('Sudbury','MA'),('Sudlersville','MD'),('Suffern','NY'),('Suffield','CT'),('Suffolk','VA'),('Sugar City','CO'),('Sugar City','ID'),('Sugar Grove','IL'),('Sugar Grove','NC'),('Sugar Grove','OH'),('Sugar Grove','PA'),('Sugar Grove','VA'),('Sugar Grove','WV'),('Sugar Hill','NH'),('Sugar Land','TX'),('Sugar Loaf','NY'),('Sugar Run','PA'),('Sugar Tree','TN'),('Sugar Valley','GA'),('Sugarcreek','OH'),('Sugarloaf','CA'),('Sugarloaf','PA'),('Sugartown','LA'),('Suisun City','CA'),('Suitland','MD'),('Sula','MT'),('Sulligent','AL'),('Sullivan','IL'),('Sullivan','IN'),('Sullivan','KY'),('Sullivan','ME'),('Sullivan','MO'),('Sullivan','NH'),('Sullivan','OH'),('Sullivan','WI'),('Sullivan City','TX'),('Sullivans Island','SC'),('Sully','IA'),('Sulphur','IN'),('Sulphur','KY'),('Sulphur','LA'),('Sulphur','OK'),('Sulphur Bluff','TX'),('Sulphur Rock','AR'),('Sulphur Springs','AR'),('Sulphur Springs','IN'),('Sulphur Springs','OH'),('Sulphur Springs','TX'),('Sultan','WA'),('Sultana','CA'),('Sumas','WA'),('Sumatra','FL'),('Sumatra','MT'),('Sumava Resorts','IN'),('Sumerco','WV'),('Sumerduck','VA'),('Sumiton','AL'),('Summer Lake','OR'),('Summer Shade','KY'),('Summerdale','AL'),('Summerdale','PA'),('Summerfield','FL'),('Summerfield','IL'),('Summerfield','KS'),('Summerfield','LA'),('Summerfield','NC'),('Summerfield','OH'),('Summerfield','TX'),('Summerhill','PA'),('Summerland','CA'),('Summerland Key','FL'),('Summers','AR'),('Summersville','KY'),('Summersville','MO'),('Summersville','WV'),('Summerton','SC'),('Summertown','TN'),('Summerville','GA'),('Summerville','OR'),('Summerville','PA'),('Summerville','SC'),('Summit','AR'),('Summit','MS'),('Summit','NJ'),('Summit','NY'),('Summit','SD'),('Summit','UT'),('Summit Argo','IL'),('Summit Hill','PA'),('Summit Lake','WI'),('Summit Point','WV'),('Summit Station','OH'),('Summit Station','PA'),('Summitville','IN'),('Summitville','NY'),('Summitville','OH'),('Summitville','TN'),('Sumner','GA'),('Sumner','IA'),('Sumner','IL'),('Sumner','ME'),('Sumner','MI'),('Sumner','MO'),('Sumner','MS'),('Sumner','NE'),('Sumner','TX'),('Sumner','WA'),('Sumneytown','PA'),('Sumpter','OR'),('Sumrall','MS'),('Sumter','SC'),('Sumterville','FL'),('Sun','LA'),('Sun City','AZ'),('Sun City','CA'),('Sun City','FL'),('Sun City','KS'),('Sun City Center','FL'),('Sun City West','AZ'),('Sun Prairie','WI'),('Sun River','MT'),('Sun Valley','AZ'),('Sun Valley','CA'),('Sun Valley','ID'),('Sun Valley','NV'),('Sunapee','NH'),('Sunbright','TN'),('Sunburg','MN'),('Sunburst','MT'),('Sunbury','NC'),('Sunbury','OH'),('Sunbury','PA'),('Suncook','NH'),('Sundance','WY'),('Sunderland','MA'),('Sunderland','MD'),('Sundown','TX'),('Sunfield','MI'),('Sunflower','AL'),('Sunflower','MS'),('Sunland','CA'),('Sunland Park','NM'),('Sunman','IN'),('Sunny Side','GA'),('Sunnyside','NY'),('Sunnyside','UT'),('Sunnyside','WA'),('Sunnyvale','CA'),('Sunnyvale','TX'),('Sunol','CA'),('Sunray','TX'),('Sunrise Beach','MO'),('Sunset','LA'),('Sunset','ME'),('Sunset','SC'),('Sunset','TX'),('Sunset Beach','CA'),('Sunset Beach','NC'),('Sunshine','LA'),('Sunspot','NM'),('Supai','AZ'),('Superior','AZ'),('Superior','IA'),('Superior','MT'),('Superior','NE'),('Superior','WI'),('Superior','WY'),('Suplee','PA'),('Supply','NC'),('Suquamish','WA'),('Surfside','CA'),('Surgoinsville','TN'),('Suring','WI'),('Surprise','AZ'),('Surprise','NE'),('Surprise','NY'),('Surrency','GA'),('Surrey','ND'),('Surry','ME'),('Surry','VA'),('Surveyor','WV'),('Susan','VA'),('Susanville','CA'),('Susquehanna','PA'),('Sussex','NJ'),('Sussex','VA'),('Sussex','WI'),('Sutersville','PA'),('Sutherland','IA'),('Sutherland','NE'),('Sutherland','VA'),('Sutherland Springs','TX'),('Sutherlin','OR'),('Sutherlin','VA'),('Sutter','CA'),('Sutter','IL'),('Sutter Creek','CA'),('Sutton','AK'),('Sutton','MA'),('Sutton','ND'),('Sutton','NE'),('Sutton','VT'),('Sutton','WV'),('Suttons Bay','MI'),('Suwanee','GA'),('Suwannee','FL'),('Swain','NY'),('Swainsboro','GA'),('Swaledale','IA'),('Swampscott','MA'),('Swan','IA'),('Swan Lake','MS'),('Swan Lake','NY'),('Swan River','MN'),('Swan Valley','ID'),('Swanlake','ID'),('Swannanoa','NC'),('Swanquarter','NC'),('Swans Island','ME'),('Swansboro','NC'),('Swansea','MA'),('Swansea','SC'),('Swanton','MD'),('Swanton','NE'),('Swanton','OH'),('Swanton','VT'),('Swanville','MN'),('Swanzey','NH'),('Swarthmore','PA'),('Swartswood','NJ'),('Swartz','LA'),('Swartz Creek','MI'),('Swatara','MN'),('Swayzee','IN'),('Swea City','IA'),('Swedesboro','NJ'),('Swedesburg','IA'),('Sweeden','KY'),('Sweeny','TX'),('Sweet','ID'),('Sweet Briar','VA'),('Sweet Grass','MT'); + +INSERT INTO [cities] VALUES ('Sweet Home','AR'),('Sweet Home','OR'),('Sweet Home','TX'),('Sweet Springs','MO'),('Sweet Valley','PA'),('Sweet Water','AL'),('Sweetser','IN'),('Sweetwater','OK'),('Sweetwater','TN'),('Sweetwater','TX'),('Swengel','PA'),('Swepsonville','NC'),('Swift','MN'),('Swifton','AR'),('Swiftown','MS'),('Swiftwater','PA'),('Swink','CO'),('Swink','OK'),('Swisher','IA'),('Swiss','WV'),('Swisshome','OR'),('Switchback','WV'),('Switz City','IN'),('Switzer','WV'),('Swoope','VA'),('Swords Creek','VA'),('Sybertsville','PA'),('Sycamore','AL'),('Sycamore','GA'),('Sycamore','IL'),('Sycamore','KS'),('Sycamore','OH'),('Sycamore','PA'),('Sycamore','SC'),('Sycamore Valley','OH'),('Sydney','FL'),('Sykeston','ND'),('Sykesville','MD'),('Sykesville','PA'),('Sylacauga','AL'),('Sylmar','CA'),('Sylva','NC'),('Sylvan Beach','MI'),('Sylvan Beach','NY'),('Sylvan Grove','KS'),('Sylvania','AL'),('Sylvania','GA'),('Sylvania','OH'),('Sylvania','PA'),('Sylvester','GA'),('Sylvester','TX'),('Sylvester','WV'),('Sylvia','KS'),('Symsonia','KY'),('Syosset','NY'),('Syracuse','IN'),('Syracuse','KS'),('Syracuse','MO'),('Syracuse','NE'),('Syracuse','NY'),('Syracuse','OH'),('Syracuse','UT'),('Syria','VA'),('Taberg','NY'),('Tabernacle','NJ'),('Tabernash','CO'),('Tabiona','UT'),('Table Grove','IL'),('Table Rock','NE'),('Tabor','IA'),('Tabor','SD'),('Tabor City','NC'),('Tacna','AZ'),('Tacoma','WA'),('Taconic','CT'),('Taconite','MN'),('Tad','WV'),('Taft','CA'),('Taft','OK'),('Taft','TN'),('Taft','TX'),('Tafton','PA'),('Taftsville','VT'),('Taftville','CT'),('Tahlequah','OK'),('Tahoe City','CA'),('Tahoe Vista','CA'),('Tahoka','TX'),('Taholah','WA'),('Tahoma','CA'),('Tahuya','WA'),('Taiban','NM'),('Takoma Park','MD'),('Takotna','AK'),('Talala','OK'),('Talbot','IN'),('Talbott','TN'),('Talbotton','GA'),('Talco','TX'),('Talcott','WV'),('Talent','OR'),('Talihina','OK'),('Talisheek','LA'),('Talkeetna','AK'),('Talking Rock','GA'),('Tall Timbers','MD'),('Talladega','AL'),('Tallahassee','FL'),('Tallapoosa','GA'),('Tallapoosa','MO'),('Tallassee','AL'),('Tallassee','TN'),('Tallevast','FL'),('Tallmadge','OH'),('Tallman','NY'),('Tallmansville','WV'),('Tallula','IL'),('Tallulah','LA'),('Tallulah Falls','GA'),('Talmage','CA'),('Talmage','KS'),('Talmage','NE'),('Talmage','PA'),('Talmage','UT'),('Talmo','GA'),('Talmoon','MN'),('Taloga','OK'),('Talpa','TX'),('Tama','IA'),('Tamaqua','PA'),('Tamarack','MN'),('Tamaroa','IL'),('Tamassee','SC'),('Tamiment','PA'),('Tamms','IL'),('Tampa','FL'),('Tampa','KS'),('Tampico','IL'),('Tamworth','NH'),('Tanacross','AK'),('Tanana','AK'),('Taneytown','MD'),('Taneyville','MO'),('Tangent','OR'),('Tangerine','FL'),('Tangier','VA'),('Tangipahoa','LA'),('Tanner','AL'),('Tannersville','NY'),('Tannersville','PA'),('Tannersville','VA'),('Taopi','MN'),('Taos','NM'),('Taos Ski Valley','NM'),('Tappahannock','VA'),('Tappan','NY'),('Tappen','ND'),('Tar Heel','NC'),('Tarawa Terrace','NC'),('Tarboro','NC'),('Tarentum','PA'),('Tariffville','CT'),('Tarkio','MO'),('Tarlton','OH'),('Tarpley','TX'),('Tarpon Springs','FL'),('Tarrs','PA'),('Tarrytown','GA'),('Tarrytown','NY'),('Tarzan','TX'),('Tarzana','CA'),('Tasley','VA'),('Taswell','IN'),('Tatamy','PA'),('Tate','GA'),('Tateville','KY'),('Tatitlek','AK'),('Tatum','NM'),('Tatum','SC'),('Tatum','TX'),('Tatums','OK'),('Taunton','MA'),('Taunton','MN'),('Tavares','FL'),('Tavernier','FL'),('Tawas City','MI'),('Taylor','AR'),('Taylor','AZ'),('Taylor','LA'),('Taylor','MI'),('Taylor','MO'),('Taylor','MS'),('Taylor','ND'),('Taylor','NE'),('Taylor','PA'),('Taylor','TX'),('Taylor','WI'),('Taylor Ridge','IL'),('Taylor Springs','IL'),('Taylors','SC'),('Taylors Falls','MN'),('Taylors Island','MD'),('Taylorstown','PA'),('Taylorsville','CA'),('Taylorsville','GA'),('Taylorsville','IN'),('Taylorsville','KY'),('Taylorsville','MS'),('Taylorsville','NC'),('Taylorville','IL'),('Tazewell','TN'),('Tazewell','VA'),('Tchula','MS'),('Tea','SD'),('Teaberry','KY'),('Teachey','NC'),('Teague','TX'),('Teaneck','NJ'),('Teasdale','UT'),('Teays','WV'),('Tebbetts','MO'),('Tecate','CA'),('Techny','IL'),('Tecopa','CA'),('Tecumseh','KS'),('Tecumseh','MI'),('Tecumseh','MO'),('Tecumseh','NE'),('Tecumseh','OK'),('Teec Nos Pos','AZ'),('Teeds Grove','IA'),('Tefft','IN'),('Tehachapi','CA'),('Tehama','CA'),('Tehuacana','TX'),('Teigen','MT'),('Tekamah','NE'),('Tekoa','WA'),('Tekonsha','MI'),('Telegraph','TX'),('Telephone','TX'),('Telferner','TX'),('Telford','PA'),('Telford','TN'),('Tell','TX'),('Tell City','IN'),('Teller','AK'),('Tellico Plains','TN'),('Telluride','CO'),('Telogia','FL'),('Temecula','CA'),('Tempe','AZ'),('Temperance','MI'),('Temperanceville','VA'),('Temple','GA'),('Temple','ME'),('Temple','NH'),('Temple','OK'),('Temple','PA'),('Temple','TX'),('Temple Bar Marina','AZ'),('Temple City','CA'),('Temple Hills','MD'),('Templeton','CA'),('Templeton','IA'),('Templeton','IN'),('Templeton','MA'),('Templeton','PA'),('Templeville','MD'),('Ten Mile','TN'),('Ten Sleep','WY'),('Tenafly','NJ'),('Tenaha','TX'),('Tenakee Springs','AK'),('Tenants Harbor','ME'),('Tendoy','ID'),('Tenino','WA'),('Tenmile','OR'),('Tennent','NJ'),('Tennessee','IL'),('Tennessee Colony','TX'),('Tennessee Ridge','TN'),('Tennga','GA'),('Tennille','GA'),('Tennyson','IN'),('Tennyson','TX'),('Tensed','ID'),('Tenstrike','MN'),('Tererro','NM'),('Terlingua','TX'),('Terlton','OK'),('Termo','CA'),('Terra Alta','WV'),('Terra Bella','CA'),('Terra Ceia','FL'),('Terrace Park','OH'),('Terral','OK'),('Terre Haute','IN'),('Terre Hill','PA'),('Terrebonne','OR'),('Terrell','NC'),('Terrell','TX'),('Terreton','ID'),('Terril','IA'),('Terry','MS'),('Terry','MT'),('Terryville','CT'),('Tescott','KS'),('Tesuque','NM'),('Teterboro','NJ'),('Teton','ID'),('Teton Village','WY'),('Tetonia','ID'),('Teutopolis','IL'),('Tewksbury','MA'),('Texarkana','AR'),('Texarkana','TX'),('Texas City','TX'),('Texhoma','OK'),('Texico','IL'),('Texico','NM'),('Texline','TX'),('Texola','OK'),('Thackerville','OK'),('Thatcher','AZ'),('Thatcher','ID'),('Thawville','IL'),('Thaxton','MS'),('Thaxton','VA'),('Thayer','IA'),('Thayer','IL'),('Thayer','IN'),('Thayer','KS'),('Thayer','MO'),('Thayne','WY'),('The Colony','TX'),('The Dalles','OR'),('The Lakes','NV'),('The Plains','OH'),('The Plains','VA'),('The Rock','GA'),('The Sea Ranch','CA'),('The Villages','FL'),('Thebes','IL'),('Thedford','NE'),('Thelma','KY'),('Thendara','NY'),('Theodore','AL'),('Theodosia','MO'),('Theresa','NY'),('Theresa','WI'),('Theriot','LA'),('Thermal','CA'),('Thermopolis','WY'),('Thetford','VT'),('Thetford Center','VT'),('Thibodaux','LA'),('Thicket','TX'),('Thida','AR'),('Thief River Falls','MN'),('Thiells','NY'),('Thiensville','WI'),('Thomas','OK'),('Thomas','WV'),('Thomasboro','IL'),('Thomaston','AL'),('Thomaston','CT'),('Thomaston','GA'),('Thomaston','ME'),('Thomaston','TX'),('Thomastown','MS'),('Thomasville','AL'),('Thomasville','GA'),('Thomasville','NC'),('Thomasville','PA'),('Thompson','CT'),('Thompson','IA'),('Thompson','MO'),('Thompson','ND'),('Thompson','OH'),('Thompson','PA'),('Thompson','UT'),('Thompson Falls','MT'),('Thompson Ridge','NY'),('Thompsons','TX'),('Thompsons Station','TN'),('Thompsontown','PA'),('Thompsonville','IL'),('Thompsonville','MI'),('Thompsonville','NY'),('Thomson','GA'),('Thomson','IL'),('Thonotosassa','FL'),('Thor','IA'),('Thoreau','NM'),('Thorn Hill','TN'),('Thornburg','IA'),('Thornburg','VA'),('Thorndale','PA'),('Thorndale','TX'),('Thorndike','MA'),('Thorndike','ME'),('Thorne Bay','AK'),('Thornfield','MO'),('Thornton','AR'),('Thornton','CA'),('Thornton','IA'),('Thornton','IL'),('Thornton','KY'),('Thornton','NH'),('Thornton','PA'),('Thornton','TX'),('Thornton','WA'),('Thornton','WV'),('Thorntown','IN'),('Thornville','OH'),('Thornwood','NY'),('Thorofare','NJ'),('Thorp','WA'),('Thorp','WI'),('Thorpe','WV'),('Thorsby','AL'),('Thousand Island Park','NY'),('Thousand Oaks','CA'),('Thousand Palms','CA'),('Thousandsticks','KY'),('Thrall','TX'),('Three Bridges','NJ'),('Three Forks','MT'),('Three Lakes','WI'),('Three Mile Bay','NY'),('Three Oaks','MI'),('Three Rivers','CA'),('Three Rivers','MA'),('Three Rivers','MI'),('Three Rivers','TX'),('Three Springs','PA'),('Throckmorton','TX'),('Thurman','IA'),('Thurman','OH'),('Thurmond','NC'),('Thurmond','WV'),('Thurmont','MD'),('Thurston','NE'),('Thurston','OH'),('Thurston','OR'),('Tibbie','AL'),('Tichnor','AR'),('Tickfaw','LA'),('Ticonderoga','NY'),('Tidewater','OR'),('Tidioute','PA'),('Tie Plant','MS'),('Tie Siding','WY'),('Tierra Amarilla','NM'),('Tieton','WA'),('Tiff','MO'),('Tiff City','MO'),('Tiffin','IA'),('Tiffin','OH'),('Tifton','GA'),('Tiger','GA'),('Tigerton','WI'),('Tigerville','SC'),('Tignall','GA'),('Tigrett','TN'),('Tijeras','NM'),('Tilden','IL'),('Tilden','NE'),('Tilden','TX'),('Tilghman','MD'),('Tiline','KY'),('Tillamook','OR'),('Tillar','AR'),('Tillatoba','MS'),('Tilleda','WI'),('Tiller','OR'),('Tillery','NC'),('Tillman','SC'),('Tillson','NY'),('Tilly','AR'),('Tilton','IL'),('Tilton','NH'),('Tiltonsville','OH'),('Timber','OR'),('Timber Lake','SD'),('Timberlake','NC'),('Timberon','NM'),('Timberville','VA'),('Timblin','PA'),('Timbo','AR'),('Timewell','IL'),('Timmonsville','SC'),('Timnath','CO'),('Timpson','TX'),('Tina','MO'),('Tingley','IA'),('Tinker Afb','OK'),('Tinley Park','IL'),('Tinnie','NM'); + +INSERT INTO [cities] VALUES ('Tinsley','MS'),('Tintah','MN'),('Tioga','LA'),('Tioga','ND'),('Tioga','PA'),('Tioga','TX'),('Tioga','WV'),('Tioga Center','NY'),('Tiona','PA'),('Tionesta','PA'),('Tiplersville','MS'),('Tipp City','OH'),('Tippecanoe','IN'),('Tippecanoe','OH'),('Tippo','MS'),('Tipton','CA'),('Tipton','IA'),('Tipton','IN'),('Tipton','KS'),('Tipton','MI'),('Tipton','MO'),('Tipton','OK'),('Tipton','PA'),('Tipton','TN'),('Tiptonville','TN'),('Tire Hill','PA'),('Tiro','OH'),('Tisch Mills','WI'),('Tishomingo','MS'),('Tishomingo','OK'),('Tiskilwa','IL'),('Titonka','IA'),('Titus','AL'),('Titusville','FL'),('Titusville','NJ'),('Titusville','PA'),('Tiverton','RI'),('Tivoli','NY'),('Tivoli','TX'),('Toa Alta','PR'),('Toa Baja','PR'),('Toano','VA'),('Toast','NC'),('Tobaccoville','NC'),('Tobias','NE'),('Tobyhanna','PA'),('Toccoa','GA'),('Toccoa Falls','GA'),('Toccopola','MS'),('Todd','NC'),('Todd','PA'),('Toddville','IA'),('Toddville','MD'),('Toeterville','IA'),('Tofte','MN'),('Togiak','AK'),('Tohatchi','NM'),('Toivola','MI'),('Tok','AK'),('Tokeland','WA'),('Tokio','ND'),('Tokio','TX'),('Toksook Bay','AK'),('Tolar','TX'),('Toledo','IA'),('Toledo','IL'),('Toledo','OH'),('Toledo','OR'),('Toledo','WA'),('Tolland','CT'),('Tollesboro','KY'),('Tolleson','AZ'),('Tolley','ND'),('Tollhouse','CA'),('Tolna','ND'),('Tolono','IL'),('Tolovana Park','OR'),('Tolstoy','SD'),('Tolu','KY'),('Toluca','IL'),('Toluca Lake','CA'),('Tom Bean','TX'),('Tomah','WI'),('Tomahawk','KY'),('Tomahawk','WI'),('Tomales','CA'),('Tomball','TX'),('Tombstone','AZ'),('Tome','NM'),('Tomkins Cove','NY'),('Tompkinsville','KY'),('Toms Brook','VA'),('Toms River','NJ'),('Tonalea','AZ'),('Tonasket','WA'),('Tonawanda','NY'),('Toney','AL'),('Tonganoxie','KS'),('Tonica','IL'),('Tonkawa','OK'),('Tonopah','AZ'),('Tonopah','NV'),('Tontitown','AR'),('Tonto Basin','AZ'),('Tontogany','OH'),('Tony','WI'),('Tooele','UT'),('Toomsboro','GA'),('Toomsuba','MS'),('Toone','TN'),('Topanga','CA'),('Topawa','AZ'),('Topaz','CA'),('Topeka','IL'),('Topeka','IN'),('Topeka','KS'),('Topinabee','MI'),('Topmost','KY'),('Topock','AZ'),('Toponas','CO'),('Toppenish','WA'),('Topping','VA'),('Topsfield','MA'),('Topsfield','ME'),('Topsham','ME'),('Topsham','VT'),('Topton','NC'),('Topton','PA'),('Toquerville','UT'),('Tornado','WV'),('Tornillo','TX'),('Toronto','KS'),('Toronto','OH'),('Toronto','SD'),('Torrance','CA'),('Torrance','PA'),('Torreon','NM'),('Torrey','UT'),('Torrington','CT'),('Torrington','WY'),('Tortilla Flat','AZ'),('Toston','MT'),('Totowa','NJ'),('Totz','KY'),('Touchet','WA'),('Tougaloo','MS'),('Toughkenamon','PA'),('Toulon','IL'),('Toutle','WA'),('Tovey','IL'),('Tow','TX'),('Towaco','NJ'),('Towanda','IL'),('Towanda','KS'),('Towanda','PA'),('Towaoc','CO'),('Tower','MI'),('Tower','MN'),('Tower City','ND'),('Tower City','PA'),('Tower Hill','IL'),('Town Creek','AL'),('Towner','ND'),('Townley','AL'),('Townsend','DE'),('Townsend','GA'),('Townsend','MA'),('Townsend','MT'),('Townsend','TN'),('Townsend','VA'),('Townsend','WI'),('Townshend','VT'),('Township Of Washington','NJ'),('Townsville','NC'),('Townville','PA'),('Townville','SC'),('Towson','MD'),('Toxey','AL'),('Toyah','TX'),('Toyahvale','TX'),('Trabuco Canyon','CA'),('Tracy','CA'),('Tracy','IA'),('Tracy','MN'),('Tracy City','TN'),('Tracys Landing','MD'),('Tracyton','WA'),('Trade','TN'),('Traer','IA'),('Trafalgar','IN'),('Trafford','AL'),('Trafford','PA'),('Trail','MN'),('Trail','OR'),('Trail City','SD'),('Tram','KY'),('Trampas','NM'),('Tranquility','NJ'),('Tranquillity','CA'),('Transfer','PA'),('Transylvania','LA'),('Traphill','NC'),('Trappe','MD'),('Trapper Creek','AK'),('Traskwood','AR'),('Travelers Rest','SC'),('Traver','CA'),('Traverse City','MI'),('Travis Afb','CA'),('Treadwell','NY'),('Trebloc','MS'),('Treece','KS'),('Trego','MT'),('Trego','WI'),('Treichlers','PA'),('Treloar','MO'),('Trementina','NM'),('Tremont','IL'),('Tremont','MS'),('Tremont','PA'),('Tremont City','OH'),('Tremonton','UT'),('Trempealeau','WI'),('Trenary','MI'),('Trent','SD'),('Trent','TX'),('Trenton','AL'),('Trenton','FL'),('Trenton','GA'),('Trenton','IL'),('Trenton','KY'),('Trenton','MI'),('Trenton','MO'),('Trenton','NC'),('Trenton','ND'),('Trenton','NE'),('Trenton','NJ'),('Trenton','OH'),('Trenton','SC'),('Trenton','TN'),('Trenton','TX'),('Trenton','UT'),('Tres Piedras','NM'),('Tres Pinos','CA'),('Tresckow','PA'),('Trevett','ME'),('Trevilians','VA'),('Trevor','WI'),('Trevorton','PA'),('Trexlertown','PA'),('Treynor','IA'),('Trezevant','TN'),('Triadelphia','WV'),('Triangle','VA'),('Tribes Hill','NY'),('Tribune','KS'),('Tridell','UT'),('Trilby','FL'),('Trilla','IL'),('Trimble','MO'),('Trimble','OH'),('Trimble','TN'),('Trimont','MN'),('Trinchera','CO'),('Trinidad','CA'),('Trinidad','CO'),('Trinidad','TX'),('Trinity','AL'),('Trinity','NC'),('Trinity','TX'),('Trinity Center','CA'),('Trinway','OH'),('Trion','GA'),('Tripler Army Medical Center','HI'),('Triplett','MO'),('Tripoli','IA'),('Tripoli','WI'),('Tripp','SD'),('Triumph','IL'),('Trivoli','IL'),('Trona','CA'),('Tropic','UT'),('Trosky','MN'),('Trosper','KY'),('Troup','TX'),('Troupsburg','NY'),('Trout','LA'),('Trout Creek','MI'),('Trout Creek','MT'),('Trout Creek','NY'),('Trout Lake','MI'),('Trout Lake','WA'),('Trout Run','PA'),('Troutdale','OR'),('Troutdale','VA'),('Troutman','NC'),('Troutville','PA'),('Troutville','VA'),('Troxelville','PA'),('Troy','AL'),('Troy','ID'),('Troy','IL'),('Troy','IN'),('Troy','KS'),('Troy','ME'),('Troy','MI'),('Troy','MO'),('Troy','MT'),('Troy','NC'),('Troy','NH'),('Troy','NY'),('Troy','OH'),('Troy','PA'),('Troy','SC'),('Troy','TN'),('Troy','TX'),('Troy','VA'),('Troy','VT'),('Troy','WV'),('Troy Grove','IL'),('Troy Mills','IA'),('Truchas','NM'),('Truckee','CA'),('Truesdale','IA'),('Trufant','MI'),('Trujillo Alto','PR'),('Truman','MN'),('Trumann','AR'),('Trumansburg','NY'),('Trumbauersville','PA'),('Trumbull','CT'),('Trumbull','NE'),('Truro','IA'),('Truro','MA'),('Trussville','AL'),('Truth Or Consequences','NM'),('Truxton','MO'),('Truxton','NY'),('Tryon','NC'),('Tryon','NE'),('Tryon','OK'),('Tsaile','AZ'),('Tualatin','OR'),('Tuba City','AZ'),('Tubac','AZ'),('Tuckahoe','NJ'),('Tuckahoe','NY'),('Tuckasegee','NC'),('Tucker','AR'),('Tucker','GA'),('Tuckerman','AR'),('Tucson','AZ'),('Tucumcari','NM'),('Tujunga','CA'),('Tula','MS'),('Tulare','CA'),('Tulare','SD'),('Tularosa','NM'),('Tulelake','CA'),('Tuleta','TX'),('Tulia','TX'),('Tullahoma','TN'),('Tullos','LA'),('Tully','NY'),('Tulsa','OK'),('Tuluksak','AK'),('Tumacacori','AZ'),('Tumbling Shoals','AR'),('Tumtum','WA'),('Tumwater','WA'),('Tunas','MO'),('Tunbridge','VT'),('Tunica','LA'),('Tunica','MS'),('Tunkhannock','PA'),('Tunnel','NY'),('Tunnel City','WI'),('Tunnel Hill','GA'),('Tunnelton','IN'),('Tunnelton','WV'),('Tuntutuliak','AK'),('Tununak','AK'),('Tuolumne','CA'),('Tupelo','AR'),('Tupelo','MS'),('Tupelo','OK'),('Tupman','CA'),('Tupper Lake','NY'),('Tuppers Plains','OH'),('Turbeville','SC'),('Turbotville','PA'),('Turin','GA'),('Turin','NY'),('Turkey','NC'),('Turkey','TX'),('Turkey City','PA'),('Turkey Creek','LA'),('Turlock','CA'),('Turner','AR'),('Turner','ME'),('Turner','MI'),('Turner','MT'),('Turner','OR'),('Turners','MO'),('Turners Falls','MA'),('Turners Station','KY'),('Turnersburg','NC'),('Turnerville','GA'),('Turney','MO'),('Turon','KS'),('Turpin','OK'),('Turrell','AR'),('Turtle Creek','PA'),('Turtle Creek','WV'),('Turtle Lake','ND'),('Turtle Lake','WI'),('Turtlepoint','PA'),('Turtletown','TN'),('Turton','SD'),('Tuscaloosa','AL'),('Tuscarawas','OH'),('Tuscarora','MD'),('Tuscarora','NV'),('Tuscarora','PA'),('Tuscola','IL'),('Tuscola','MI'),('Tuscola','TX'),('Tuscumbia','AL'),('Tuscumbia','MO'),('Tuskahoma','OK'),('Tuskegee','AL'),('Tuskegee Institute','AL'),('Tussy','OK'),('Tustin','CA'),('Tustin','MI'),('Tuthill','SD'),('Tutor Key','KY'),('Tuttle','ND'),('Tuttle','OK'),('Tutwiler','MS'),('Tuxedo','NC'),('Tuxedo Park','NY'),('Twain','CA'),('Twain Harte','CA'),('Twelve Mile','IN'),('Twentynine Palms','CA'),('Twig','MN'),('Twilight','WV'),('Twin Bridges','CA'),('Twin Bridges','MT'),('Twin Brooks','SD'),('Twin City','GA'),('Twin Falls','ID'),('Twin Lake','MI'),('Twin Lakes','CO'),('Twin Lakes','MN'),('Twin Lakes','WI'),('Twin Mountain','NH'),('Twin Oaks','OK'),('Twin Peaks','CA'),('Twin Rocks','PA'),('Twin Valley','MN'),('Twining','MI'),('Twinsburg','OH'),('Twisp','WA'),('Two Buttes','CO'),('Two Dot','MT'),('Two Harbors','MN'),('Two Rivers','AK'),('Two Rivers','WI'),('Ty Ty','GA'),('Tyaskin','MD'),('Tybee Island','GA'),('Tye','TX'),('Tygh Valley','OR'),('Tyler','AL'),('Tyler','MN'),('Tyler','TX'),('Tyler Hill','PA'),('Tylersburg','PA'),('Tylersport','PA'),('Tylersville','PA'),('Tylerton','MD'),('Tylertown','MS'),('Tynan','TX'),('Tyndall','SD'),('Tyner','IN'),('Tyner','KY'),('Tyner','NC'),('Tyngsboro','MA'),('Tyonek','AK'),('Tyringham','MA'),('Tyro','KS'),('Tyro','VA'),('Tyrone','GA'),('Tyrone','NM'),('Tyrone','NY'),('Tyrone','OK'),('Tyrone','PA'),('Tyronza','AR'); + +INSERT INTO [cities] VALUES ('U S A F Academy','CO'),('Ubly','MI'),('Ucon','ID'),('Udall','KS'),('Udall','MO'),('Udell','IA'),('Uehling','NE'),('Uhrichsville','OH'),('Ukiah','CA'),('Ukiah','OR'),('Uledi','PA'),('Ulen','MN'),('Ullin','IL'),('Ulm','AR'),('Ulm','MT'),('Ulman','MO'),('Ulmer','SC'),('Ulster','PA'),('Ulster Park','NY'),('Ulysses','KS'),('Ulysses','KY'),('Ulysses','NE'),('Ulysses','PA'),('Umatilla','FL'),('Umatilla','OR'),('Umbarger','TX'),('Umpire','AR'),('Umpqua','OR'),('Una','SC'),('Unadilla','GA'),('Unadilla','NE'),('Unadilla','NY'),('Unalakleet','AK'),('Unalaska','AK'),('Uncasville','CT'),('Uncle Sam','LA'),('Underhill','VT'),('Underhill Center','VT'),('Underwood','IA'),('Underwood','IN'),('Underwood','MN'),('Underwood','ND'),('Underwood','WA'),('Uneeda','WV'),('Unicoi','TN'),('Union','IA'),('Union','IL'),('Union','KY'),('Union','ME'),('Union','MI'),('Union','MO'),('Union','MS'),('Union','NE'),('Union','NH'),('Union','NJ'),('Union','OR'),('Union','SC'),('Union','WA'),('Union','WV'),('Union Bridge','MD'),('Union Center','SD'),('Union Center','WI'),('Union Church','MS'),('Union City','CA'),('Union City','GA'),('Union City','IN'),('Union City','MI'),('Union City','NJ'),('Union City','OH'),('Union City','OK'),('Union City','PA'),('Union City','TN'),('Union Dale','PA'),('Union Furnace','OH'),('Union Grove','AL'),('Union Grove','NC'),('Union Grove','WI'),('Union Hall','VA'),('Union Hill','IL'),('Union Hill','NY'),('Union Lake','MI'),('Union Mills','IN'),('Union Mills','NC'),('Union Pier','MI'),('Union Point','GA'),('Union Springs','AL'),('Union Springs','NY'),('Union Star','KY'),('Union Star','MO'),('Uniondale','IN'),('Uniondale','NY'),('Uniontown','AL'),('Uniontown','AR'),('Uniontown','KS'),('Uniontown','KY'),('Uniontown','MO'),('Uniontown','OH'),('Uniontown','PA'),('Uniontown','WA'),('Unionville','CT'),('Unionville','IA'),('Unionville','IN'),('Unionville','MD'),('Unionville','MI'),('Unionville','MO'),('Unionville','NY'),('Unionville','OH'),('Unionville','PA'),('Unionville','TN'),('Unionville','VA'),('Unionville Center','OH'),('Uniopolis','OH'),('United','PA'),('Unity','IL'),('Unity','ME'),('Unity','OR'),('Unity','WI'),('Unity House','PA'),('Unityville','PA'),('Universal','IN'),('Universal City','CA'),('Universal City','TX'),('University','MS'),('University Center','MI'),('University Of Richmond','VA'),('University Park','IA'),('University Park','PA'),('University Place','WA'),('Upatoi','GA'),('Upham','ND'),('Upland','CA'),('Upland','IN'),('Upland','NE'),('Upper Black Eddy','PA'),('Upper Darby','PA'),('Upper Fairmount','MD'),('Upper Falls','MD'),('Upper Jay','NY'),('Upper Lake','CA'),('Upper Marlboro','MD'),('Upper Sandusky','OH'),('Upper Tract','WV'),('Upperco','MD'),('Upperglade','WV'),('Upperstrasburg','PA'),('Upperville','VA'),('Upsala','MN'),('Upson','WI'),('Upton','KY'),('Upton','MA'),('Upton','NY'),('Upton','WY'),('Urania','LA'),('Urbana','AR'),('Urbana','IA'),('Urbana','IL'),('Urbana','IN'),('Urbana','MO'),('Urbana','OH'),('Urbandale','IA'),('Urbanna','VA'),('Uriah','AL'),('Urich','MO'),('Ursa','IL'),('Ursina','PA'),('Usk','WA'),('Ute','IA'),('Ute Park','NM'),('Utica','IL'),('Utica','KS'),('Utica','KY'),('Utica','MI'),('Utica','MN'),('Utica','MO'),('Utica','MS'),('Utica','NE'),('Utica','NY'),('Utica','OH'),('Utica','PA'),('Utica','SD'),('Utopia','TX'),('Utuado','PR'),('Uvalda','GA'),('Uvalde','TX'),('Uwchland','PA'),('Uxbridge','MA'),('Vacaville','CA'),('Vacherie','LA'),('Vader','WA'),('Vadito','NM'),('Vado','NM'),('Vaiden','MS'),('Vail','AZ'),('Vail','CO'),('Vail','IA'),('Vails Gate','NY'),('Valatie','NY'),('Valders','WI'),('Valdese','NC'),('Valdez','AK'),('Valdez','NM'),('Valdosta','GA'),('Vale','NC'),('Vale','OR'),('Vale','SD'),('Valencia','CA'),('Valencia','PA'),('Valentine','AZ'),('Valentine','NE'),('Valentine','TX'),('Valentines','VA'),('Valera','TX'),('Valhalla','NY'),('Valhermoso Springs','AL'),('Valier','IL'),('Valier','MT'),('Valier','PA'),('Valle Crucis','NC'),('Vallecito','CA'),('Vallecitos','NM'),('Vallejo','CA'),('Valles Mines','MO'),('Valley','AL'),('Valley','NE'),('Valley','WA'),('Valley Bend','WV'),('Valley Center','CA'),('Valley Center','KS'),('Valley City','ND'),('Valley City','OH'),('Valley Cottage','NY'),('Valley Falls','KS'),('Valley Falls','NY'),('Valley Farms','AZ'),('Valley Ford','CA'),('Valley Forge','PA'),('Valley Grove','WV'),('Valley Head','AL'),('Valley Head','WV'),('Valley Lee','MD'),('Valley Mills','TX'),('Valley Park','MO'),('Valley Park','MS'),('Valley Spring','TX'),('Valley Springs','AR'),('Valley Springs','CA'),('Valley Springs','SD'),('Valley Stream','NY'),('Valley View','PA'),('Valley View','TX'),('Valley Village','CA'),('Valleyford','WA'),('Valliant','OK'),('Vallonia','IN'),('Valmeyer','IL'),('Valmora','NM'),('Valmy','NV'),('Valparaiso','FL'),('Valparaiso','IN'),('Valparaiso','NE'),('Valrico','FL'),('Valyermo','CA'),('Van','TX'),('Van','WV'),('Van Alstyne','TX'),('Van Buren','AR'),('Van Buren','IN'),('Van Buren','ME'),('Van Buren','MO'),('Van Buren','OH'),('Van Buren Point','NY'),('Van Dyne','WI'),('Van Etten','NY'),('Van Horn','TX'),('Van Horne','IA'),('Van Hornesville','NY'),('Van Lear','KY'),('Van Meter','IA'),('Van Nuys','CA'),('Van Orin','IL'),('Van Tassell','WY'),('Van Vleck','TX'),('Van Vleet','MS'),('Van Voorhis','PA'),('Van Wert','IA'),('Van Wert','OH'),('Van Wyck','SC'),('Vance','AL'),('Vance','MS'),('Vance','SC'),('Vanceboro','ME'),('Vanceboro','NC'),('Vanceburg','KY'),('Vancleave','MS'),('Vancleve','KY'),('Vancourt','TX'),('Vancouver','WA'),('Vandalia','IL'),('Vandalia','MI'),('Vandalia','MO'),('Vandalia','MT'),('Vandalia','OH'),('Vandemere','NC'),('Vanderbilt','MI'),('Vanderbilt','PA'),('Vanderbilt','TX'),('Vanderbilt Beach','FL'),('Vandergrift','PA'),('Vanderpool','TX'),('Vandervoort','AR'),('Vanderwagen','NM'),('Vandiver','AL'),('Vanduser','MO'),('Vanleer','TN'),('Vanlue','OH'),('Vanndale','AR'),('Vansant','VA'),('Vantage','WA'),('Vanzant','MO'),('Vardaman','MS'),('Varina','IA'),('Varna','IL'),('Varnado','LA'),('Varnell','GA'),('Varney','KY'),('Varney','WV'),('Varnville','SC'),('Varysburg','NY'),('Vashon','WA'),('Vass','NC'),('Vassalboro','ME'),('Vassar','KS'),('Vassar','MI'),('Vaucluse','SC'),('Vaughan','MS'),('Vaughan','NC'),('Vaughn','MT'),('Vaughn','NM'),('Vaughn','WA'),('Vaughnsville','OH'),('Vauxhall','NJ'),('Veblen','SD'),('Veedersburg','IN'),('Vega','TX'),('Vega Alta','PR'),('Vega Baja','PR'),('Veguita','NM'),('Velarde','NM'),('Velma','OK'),('Velpen','IN'),('Velva','ND'),('Venango','NE'),('Venango','PA'),('Vendor','AR'),('Venedocia','OH'),('Veneta','OR'),('Venetia','PA'),('Venetie','AK'),('Venice','CA'),('Venice','FL'),('Venice','IL'),('Venice','LA'),('Ventnor City','NJ'),('Ventress','LA'),('Ventura','CA'),('Ventura','IA'),('Venus','FL'),('Venus','PA'),('Venus','TX'),('Vera','OK'),('Veradale','WA'),('Verbank','NY'),('Verbena','AL'),('Verden','OK'),('Verdi','NV'),('Verdigre','NE'),('Verdon','NE'),('Verdugo City','CA'),('Verdunville','WV'),('Vergas','MN'),('Vergennes','IL'),('Vergennes','VT'),('Veribest','TX'),('Vermilion','IL'),('Vermilion','OH'),('Vermillion','KS'),('Vermillion','MN'),('Vermillion','SD'),('Vermont','IL'),('Vermontville','MI'),('Vermontville','NY'),('Vernal','UT'),('Vernalis','CA'),('Verndale','MN'),('Verner','WV'),('Vernon','AL'),('Vernon','AZ'),('Vernon','CO'),('Vernon','FL'),('Vernon','IL'),('Vernon','IN'),('Vernon','MI'),('Vernon','NJ'),('Vernon','NY'),('Vernon','TX'),('Vernon','UT'),('Vernon','VT'),('Vernon Center','MN'),('Vernon Center','NY'),('Vernon Hill','VA'),('Vernon Hills','IL'),('Vernon Rockville','CT'),('Vernonia','OR'),('Vero Beach','FL'),('Verona','IL'),('Verona','KY'),('Verona','MO'),('Verona','MS'),('Verona','ND'),('Verona','NJ'),('Verona','NY'),('Verona','OH'),('Verona','PA'),('Verona','VA'),('Verona','WI'),('Verona Beach','NY'),('Verplanck','NY'),('Versailles','CT'),('Versailles','IL'),('Versailles','IN'),('Versailles','KY'),('Versailles','MO'),('Versailles','NY'),('Versailles','OH'),('Vershire','VT'),('Vesper','WI'),('Vest','KY'),('Vesta','MN'),('Vesta','VA'),('Vestaburg','MI'),('Vestaburg','PA'),('Vestal','NY'),('Vesuvius','VA'),('Veteran','WY'),('Vevay','IN'),('Veyo','UT'),('Vian','OK'),('Viborg','SD'),('Viburnum','MO'),('Vicco','KY'),('Vichy','MO'),('Vici','OK'),('Vickery','OH'),('Vicksburg','MI'),('Vicksburg','MS'),('Vicksburg','PA'),('Victor','CA'),('Victor','CO'),('Victor','IA'),('Victor','ID'),('Victor','MT'),('Victor','NY'),('Victor','WV'),('Victoria','IL'),('Victoria','KS'),('Victoria','MN'),('Victoria','MS'),('Victoria','TX'),('Victoria','VA'),('Victorville','CA'),('Victory Mills','NY'),('Vida','MT'),('Vida','OR'),('Vidal','CA'),('Vidalia','GA'),('Vidalia','LA'),('Vidor','TX'),('Vienna','GA'),('Vienna','IL'),('Vienna','MD'),('Vienna','ME'),('Vienna','MO'),('Vienna','NJ'),('Vienna','OH'),('Vienna','SD'),('Vienna','VA'),('Vienna','WV'),('Vieques','PR'),('Viewtown','VA'),('Viking','MN'),('Vilas','CO'),('Vilas','NC'),('Villa Grande','CA'),('Villa Grove','CO'),('Villa Grove','IL'),('Villa Maria','PA'),('Villa Park','CA'),('Villa Park','IL'),('Villa Rica','GA'),('Villa Ridge','IL'); + +INSERT INTO [cities] VALUES ('Villa Ridge','MO'),('Village','VA'),('Village Mills','TX'),('Village Of Nagog Woods','MA'),('Villalba','PR'),('Villamont','VA'),('Villanova','PA'),('Villanueva','NM'),('Villard','MN'),('Villas','NJ'),('Ville Platte','LA'),('Villisca','IA'),('Vilonia','AR'),('Vina','AL'),('Vina','CA'),('Vinalhaven','ME'),('Vincennes','IN'),('Vincent','AL'),('Vincent','IA'),('Vincent','KY'),('Vincent','OH'),('Vine Grove','KY'),('Vineburg','CA'),('Vinegar Bend','AL'),('Vineland','NJ'),('Vinemont','AL'),('Vineyard Haven','MA'),('Vining','IA'),('Vining','MN'),('Vinita','OK'),('Vinson','OK'),('Vinton','CA'),('Vinton','IA'),('Vinton','LA'),('Vinton','OH'),('Vinton','VA'),('Vintondale','PA'),('Viola','AR'),('Viola','DE'),('Viola','IA'),('Viola','ID'),('Viola','IL'),('Viola','KS'),('Viola','TN'),('Viola','WI'),('Violet','LA'),('Violet Hill','AR'),('Viper','KY'),('Virden','IL'),('Virgie','KY'),('Virgil','KS'),('Virgil','SD'),('Virgilina','VA'),('Virgin','UT'),('Virginia','IL'),('Virginia','MN'),('Virginia','NE'),('Virginia Beach','VA'),('Virginia City','MT'),('Virginia City','NV'),('Virginville','PA'),('Viroqua','WI'),('Visalia','CA'),('Vista','CA'),('Vista','MO'),('Vivian','LA'),('Vivian','SD'),('Voca','TX'),('Volant','PA'),('Volborg','MT'),('Volcano','CA'),('Volcano','HI'),('Volga','IA'),('Volga','SD'),('Volga','WV'),('Volin','SD'),('Voltaire','ND'),('Voluntown','CT'),('Von Ormy','TX'),('Vona','CO'),('Vonore','TN'),('Voorhees','NJ'),('Voorheesville','NY'),('Voss','TX'),('Vossburg','MS'),('Votaw','TX'),('Vowinckel','PA'),('Vredenburgh','AL'),('Vulcan','MI'),('Vulcan','MO'),('Waban','MA'),('Wabash','AR'),('Wabash','IN'),('Wabasha','MN'),('Wabasso','FL'),('Wabasso','MN'),('Wabbaseka','AR'),('Wabeno','WI'),('Waccabuc','NY'),('Wachapreague','VA'),('Wacissa','FL'),('Waco','GA'),('Waco','KY'),('Waco','MO'),('Waco','NC'),('Waco','NE'),('Waco','TX'),('Waconia','MN'),('Waddell','AZ'),('Waddington','NY'),('Waddy','KY'),('Wade','NC'),('Wadena','IA'),('Wadena','MN'),('Wadesboro','NC'),('Wadesville','IN'),('Wading River','NY'),('Wadley','AL'),('Wadley','GA'),('Wadmalaw Island','SC'),('Wadsworth','IL'),('Wadsworth','NV'),('Wadsworth','OH'),('Wadsworth','TX'),('Waelder','TX'),('Wagarville','AL'),('Wagener','SC'),('Waggoner','IL'),('Wagner','SD'),('Wagon Mound','NM'),('Wagoner','OK'),('Wagontown','PA'),('Wagram','NC'),('Wahiawa','HI'),('Wahkiacus','WA'),('Wahkon','MN'),('Wahoo','NE'),('Wahpeton','ND'),('Waialua','HI'),('Waianae','HI'),('Waikoloa','HI'),('Wailuku','HI'),('Waimanalo','HI'),('Waimea','HI'),('Wainscott','NY'),('Wainwright','AK'),('Wainwright','OK'),('Waipahu','HI'),('Waite','ME'),('Waite Park','MN'),('Waiteville','WV'),('Waitsburg','WA'),('Waitsfield','VT'),('Waka','TX'),('Wakarusa','IN'),('Wakarusa','KS'),('Wake','VA'),('Wake Forest','NC'),('Wake Island','HI'),('Wakeeney','KS'),('Wakefield','KS'),('Wakefield','LA'),('Wakefield','MA'),('Wakefield','MI'),('Wakefield','NE'),('Wakefield','OH'),('Wakefield','RI'),('Wakefield','VA'),('Wakeman','OH'),('Wakita','OK'),('Wakonda','SD'),('Wakpala','SD'),('Walbridge','OH'),('Walburg','TX'),('Walcott','AR'),('Walcott','IA'),('Walcott','ND'),('Walcott','WY'),('Walden','CO'),('Walden','NY'),('Waldenburg','AR'),('Waldo','AR'),('Waldo','FL'),('Waldo','KS'),('Waldo','OH'),('Waldo','WI'),('Waldoboro','ME'),('Waldorf','MD'),('Waldorf','MN'),('Waldport','OR'),('Waldron','AR'),('Waldron','IN'),('Waldron','KS'),('Waldron','MI'),('Waldron','MO'),('Waldron','WA'),('Waldwick','NJ'),('Wales','AK'),('Wales','MA'),('Wales','ND'),('Wales','UT'),('Wales','WI'),('Wales Center','NY'),('Waleska','GA'),('Walford','IA'),('Walhalla','MI'),('Walhalla','ND'),('Walhalla','SC'),('Walhonding','OH'),('Walker','IA'),('Walker','KS'),('Walker','KY'),('Walker','LA'),('Walker','MN'),('Walker','MO'),('Walker','SD'),('Walker','WV'),('Walker Valley','NY'),('Walkersville','MD'),('Walkersville','WV'),('Walkerton','IN'),('Walkerton','VA'),('Walkertown','NC'),('Walkerville','MI'),('Wall','SD'),('Wall','TX'),('Wall Lake','IA'),('Walla Walla','WA'),('Wallace','CA'),('Wallace','ID'),('Wallace','IN'),('Wallace','KS'),('Wallace','MI'),('Wallace','NC'),('Wallace','NE'),('Wallace','SC'),('Wallace','SD'),('Wallace','WV'),('Wallaceton','PA'),('Wallagrass','ME'),('Walland','TN'),('Wallback','WV'),('Wallburg','NC'),('Walled Lake','MI'),('Waller','TX'),('Walling','TN'),('Wallingford','CT'),('Wallingford','IA'),('Wallingford','KY'),('Wallingford','PA'),('Wallingford','VT'),('Wallington','NJ'),('Wallins Creek','KY'),('Wallis','TX'),('Wallisville','TX'),('Wallkill','NY'),('Walloon Lake','MI'),('Wallops Island','VA'),('Wallowa','OR'),('Wallpack Center','NJ'),('Walls','MS'),('Wallsburg','UT'),('Wallula','WA'),('Walnut','CA'),('Walnut','IA'),('Walnut','IL'),('Walnut','KS'),('Walnut','MS'),('Walnut Bottom','PA'),('Walnut Cove','NC'),('Walnut Creek','CA'),('Walnut Creek','OH'),('Walnut Grove','AL'),('Walnut Grove','CA'),('Walnut Grove','MN'),('Walnut Grove','MO'),('Walnut Grove','MS'),('Walnut Hill','IL'),('Walnut Ridge','AR'),('Walnut Shade','MO'),('Walnut Springs','TX'),('Walnutport','PA'),('Walpole','MA'),('Walpole','ME'),('Walpole','NH'),('Walsenburg','CO'),('Walsh','CO'),('Walsh','IL'),('Walshville','IL'),('Walston','PA'),('Walstonburg','NC'),('Walterboro','SC'),('Walters','OK'),('Waltersburg','PA'),('Walterville','OR'),('Walthall','MS'),('Waltham','MA'),('Waltham','MN'),('Walthill','NE'),('Walthourville','GA'),('Walton','IN'),('Walton','KS'),('Walton','KY'),('Walton','NE'),('Walton','NY'),('Walton','OR'),('Walton','WV'),('Waltonville','IL'),('Walworth','NY'),('Walworth','WI'),('Wamego','KS'),('Wampsville','NY'),('Wampum','PA'),('Wamsutter','WY'),('Wana','WV'),('Wanakena','NY'),('Wanamingo','MN'),('Wanaque','NJ'),('Wanatah','IN'),('Wanblee','SD'),('Wanchese','NC'),('Wanda','MN'),('Waneta','KY'),('Wanette','OK'),('Wann','OK'),('Wannaska','MN'),('Wantagh','NY'),('Wapakoneta','OH'),('Wapanucka','OK'),('Wapato','WA'),('Wapella','IL'),('Wapello','IA'),('Wapiti','WY'),('Wappapello','MO'),('Wappingers Falls','NY'),('Wapwallopen','PA'),('War','WV'),('Warba','MN'),('Warbranch','KY'),('Ward','AL'),('Ward','AR'),('Ward','CO'),('Ward','SC'),('Ward Cove','AK'),('Warda','TX'),('Wardell','MO'),('Warden','WA'),('Wardensville','WV'),('Wardsboro','VT'),('Wardtown','VA'),('Wardville','OK'),('Ware','MA'),('Ware Neck','VA'),('Ware Shoals','SC'),('Wareham','MA'),('Waresboro','GA'),('Waretown','NJ'),('Warfield','KY'),('Warfield','VA'),('Warfordsburg','PA'),('Waring','TX'),('Warm Springs','AR'),('Warm Springs','GA'),('Warm Springs','MT'),('Warm Springs','OR'),('Warm Springs','VA'),('Warminster','PA'),('Warne','NC'),('Warner','NH'),('Warner','OK'),('Warner','SD'),('Warner Robins','GA'),('Warner Springs','CA'),('Warners','NY'),('Warnerville','NY'),('Warnock','OH'),('Warren','AR'),('Warren','ID'),('Warren','IL'),('Warren','IN'),('Warren','MA'),('Warren','ME'),('Warren','MI'),('Warren','MN'),('Warren','NH'),('Warren','NJ'),('Warren','OH'),('Warren','OR'),('Warren','PA'),('Warren','RI'),('Warren','TX'),('Warren','VT'),('Warren Center','PA'),('Warrendale','PA'),('Warrens','WI'),('Warrensburg','IL'),('Warrensburg','MO'),('Warrensburg','NY'),('Warrensville','NC'),('Warrenton','GA'),('Warrenton','MO'),('Warrenton','NC'),('Warrenton','OR'),('Warrenton','TX'),('Warrenton','VA'),('Warrenville','IL'),('Warrenville','SC'),('Warrington','PA'),('Warrior','AL'),('Warriormine','WV'),('Warriors Mark','PA'),('Warroad','MN'),('Warsaw','IL'),('Warsaw','IN'),('Warsaw','KY'),('Warsaw','MN'),('Warsaw','MO'),('Warsaw','NC'),('Warsaw','NY'),('Warsaw','OH'),('Warsaw','VA'),('Wartburg','TN'),('Warthen','GA'),('Wartrace','TN'),('Warwick','GA'),('Warwick','MA'),('Warwick','MD'),('Warwick','ND'),('Warwick','NY'),('Warwick','RI'),('Wasco','CA'),('Wasco','IL'),('Wasco','OR'),('Wascott','WI'),('Waseca','MN'),('Washburn','IL'),('Washburn','ME'),('Washburn','MO'),('Washburn','ND'),('Washburn','TN'),('Washburn','WI'),('Washington','AR'),('Washington','CA'),('Washington','CT'),('Washington','DC'),('Washington','GA'),('Washington','IA'),('Washington','IL'),('Washington','IN'),('Washington','KS'),('Washington','KY'),('Washington','LA'),('Washington','ME'),('Washington','MI'),('Washington','MO'),('Washington','MS'),('Washington','NC'),('Washington','NE'),('Washington','NH'),('Washington','NJ'),('Washington','OK'),('Washington','PA'),('Washington','TX'),('Washington','UT'),('Washington','VA'),('Washington','VT'),('Washington','WV'),('Washington Boro','PA'),('Washington Court House','OH'),('Washington Crossing','PA'),('Washington Depot','CT'),('Washington Grove','MD'),('Washington Island','WI'),('Washington Mills','NY'),('Washington Navy Yard','DC'),('Washingtonville','NY'),('Washingtonville','OH'),('Washingtonville','PA'),('Washita','OK'),('Washoe Valley','NV'),('Washougal','WA'),('Washta','IA'),('Washtucna','WA'),('Wasilla','AK'),('Waskish','MN'),('Waskom','TX'),('Wasola','MO'),('Wassaic','NY'),('Wasta','SD'),('Wataga','IL'),('Watauga','SD'),('Watauga','TN'),('Watchung','NJ'),('Water Mill','NY'),('Water Valley','KY'),('Water Valley','MS'),('Water Valley','TX'),('Water View','VA'),('Waterboro','ME'),('Waterbury','CT'); + +INSERT INTO [cities] VALUES ('Waterbury','NE'),('Waterbury','VT'),('Waterbury Center','VT'),('Waterfall','PA'),('Waterflow','NM'),('Waterford','CA'),('Waterford','CT'),('Waterford','ME'),('Waterford','MI'),('Waterford','MS'),('Waterford','NY'),('Waterford','OH'),('Waterford','PA'),('Waterford','VA'),('Waterford','WI'),('Waterford Works','NJ'),('Waterloo','AL'),('Waterloo','IA'),('Waterloo','IL'),('Waterloo','IN'),('Waterloo','NE'),('Waterloo','NY'),('Waterloo','OH'),('Waterloo','SC'),('Waterloo','WI'),('Waterman','IL'),('Waterport','NY'),('Waterproof','LA'),('Waters','MI'),('Watersmeet','MI'),('Watertown','CT'),('Watertown','MA'),('Watertown','MN'),('Watertown','NY'),('Watertown','OH'),('Watertown','SD'),('Watertown','TN'),('Watertown','WI'),('Waterville','IA'),('Waterville','KS'),('Waterville','ME'),('Waterville','MN'),('Waterville','NY'),('Waterville','OH'),('Waterville','PA'),('Waterville','VT'),('Waterville','WA'),('Waterville Valley','NH'),('Watervliet','MI'),('Watervliet','NY'),('Watford City','ND'),('Wathena','KS'),('Watkins','CO'),('Watkins','IA'),('Watkins','MN'),('Watkins Glen','NY'),('Watkinsville','GA'),('Watonga','OK'),('Watrous','NM'),('Watseka','IL'),('Watson','AL'),('Watson','AR'),('Watson','IL'),('Watson','LA'),('Watson','MN'),('Watson','MO'),('Watson','OK'),('Watsontown','PA'),('Watsonville','CA'),('Watton','MI'),('Watts','OK'),('Wattsburg','PA'),('Wattsville','AL'),('Wattsville','VA'),('Waubay','SD'),('Waubun','MN'),('Wauchula','FL'),('Waucoma','IA'),('Wauconda','IL'),('Wauconda','WA'),('Waukau','WI'),('Waukee','IA'),('Waukegan','IL'),('Waukena','CA'),('Waukesha','WI'),('Waukomis','OK'),('Waukon','IA'),('Wauna','WA'),('Waunakee','WI'),('Wauneta','NE'),('Waupaca','WI'),('Waupun','WI'),('Wauregan','CT'),('Waurika','OK'),('Wausa','NE'),('Wausau','FL'),('Wausau','WI'),('Wausaukee','WI'),('Wauseon','OH'),('Wautoma','WI'),('Wauzeka','WI'),('Waveland','IN'),('Waveland','MS'),('Waverley','MA'),('Waverly','AL'),('Waverly','FL'),('Waverly','GA'),('Waverly','IA'),('Waverly','IL'),('Waverly','KS'),('Waverly','KY'),('Waverly','MN'),('Waverly','MO'),('Waverly','NE'),('Waverly','NY'),('Waverly','OH'),('Waverly','PA'),('Waverly','TN'),('Waverly','VA'),('Waverly','WA'),('Waverly','WV'),('Waverly Hall','GA'),('Waves','NC'),('Wawaka','IN'),('Wawarsing','NY'),('Waxahachie','TX'),('Waxhaw','NC'),('Wayan','ID'),('Waycross','GA'),('Wayland','IA'),('Wayland','KY'),('Wayland','MA'),('Wayland','MI'),('Wayland','MO'),('Wayland','NY'),('Wayland','OH'),('Waymart','PA'),('Wayne','IL'),('Wayne','ME'),('Wayne','MI'),('Wayne','NE'),('Wayne','NJ'),('Wayne','NY'),('Wayne','OH'),('Wayne','OK'),('Wayne','PA'),('Wayne','WV'),('Wayne City','IL'),('Waynesboro','GA'),('Waynesboro','MS'),('Waynesboro','PA'),('Waynesboro','TN'),('Waynesboro','VA'),('Waynesburg','KY'),('Waynesburg','OH'),('Waynesburg','PA'),('Waynesfield','OH'),('Waynesville','GA'),('Waynesville','IL'),('Waynesville','MO'),('Waynesville','NC'),('Waynesville','OH'),('Waynetown','IN'),('Waynoka','OK'),('Wayside','MS'),('Wayside','TX'),('Wayside','WV'),('Wayzata','MN'),('Weare','NH'),('Weatherby','MO'),('Weatherford','OK'),('Weatherford','TX'),('Weatherly','PA'),('Weatogue','CT'),('Weaubleau','MO'),('Weaver','AL'),('Weaverville','CA'),('Weaverville','NC'),('Webb','AL'),('Webb','IA'),('Webb','MS'),('Webb City','MO'),('Webber','KS'),('Webbers Falls','OK'),('Webberville','MI'),('Webbville','KY'),('Weber City','VA'),('Webster','FL'),('Webster','IA'),('Webster','IN'),('Webster','KY'),('Webster','MA'),('Webster','MN'),('Webster','NC'),('Webster','ND'),('Webster','NY'),('Webster','PA'),('Webster','SD'),('Webster','TX'),('Webster','WI'),('Webster City','IA'),('Webster Springs','WV'),('Websterville','VT'),('Wedderburn','OR'),('Wedgefield','SC'),('Wedowee','AL'),('Wedron','IL'),('Weed','CA'),('Weed','NM'),('Weedsport','NY'),('Weedville','PA'),('Weehawken','NJ'),('Weeksbury','KY'),('Weems','VA'),('Weeping Water','NE'),('Weesatche','TX'),('Weidman','MI'),('Weikert','PA'),('Weimar','CA'),('Weimar','TX'),('Weiner','AR'),('Weinert','TX'),('Weippe','ID'),('Weir','KS'),('Weir','MS'),('Weir','TX'),('Weirsdale','FL'),('Weirton','WV'),('Weiser','ID'),('Welaka','FL'),('Welch','MN'),('Welch','OK'),('Welch','TX'),('Welch','WV'),('Welches','OR'),('Welchs Creek','KY'),('Welcome','MD'),('Welcome','MN'),('Welcome','NC'),('Weld','ME'),('Welda','KS'),('Weldon','CA'),('Weldon','IA'),('Weldon','IL'),('Weldon','NC'),('Weldona','CO'),('Weleetka','OK'),('Wellborn','FL'),('Wellborn','TX'),('Wellersburg','PA'),('Wellesley','MA'),('Wellesley Hills','MA'),('Wellesley Island','NY'),('Wellfleet','MA'),('Wellfleet','NE'),('Wellford','SC'),('Welling','OK'),('Wellington','AL'),('Wellington','CO'),('Wellington','IL'),('Wellington','KS'),('Wellington','KY'),('Wellington','MO'),('Wellington','NV'),('Wellington','OH'),('Wellington','TX'),('Wellington','UT'),('Wellman','IA'),('Wellman','TX'),('Wellpinit','WA'),('Wells','ME'),('Wells','MI'),('Wells','MN'),('Wells','NV'),('Wells','NY'),('Wells','TX'),('Wells','VT'),('Wells Bridge','NY'),('Wells River','VT'),('Wells Tannery','PA'),('Wellsboro','PA'),('Wellsburg','IA'),('Wellsburg','NY'),('Wellsburg','WV'),('Wellston','MI'),('Wellston','OH'),('Wellston','OK'),('Wellsville','KS'),('Wellsville','MO'),('Wellsville','NY'),('Wellsville','OH'),('Wellsville','PA'),('Wellsville','UT'),('Wellton','AZ'),('Welsh','LA'),('Welton','IA'),('Wenatchee','WA'),('Wendel','CA'),('Wendel','PA'),('Wendell','ID'),('Wendell','MA'),('Wendell','MN'),('Wendell','NC'),('Wendell Depot','MA'),('Wenden','AZ'),('Wendover','KY'),('Wendover','UT'),('Wenham','MA'),('Wenona','IL'),('Wenonah','NJ'),('Wentworth','MO'),('Wentworth','NC'),('Wentworth','NH'),('Wentworth','SD'),('Wentzville','MO'),('Weogufka','AL'),('Weott','CA'),('Wernersville','PA'),('Wesco','MO'),('Weskan','KS'),('Weslaco','TX'),('Wesley','AR'),('Wesley','IA'),('Wesley','ME'),('Wesley Chapel','FL'),('Wessington','SD'),('Wessington Springs','SD'),('Wesson','MS'),('West','MS'),('West','TX'),('West Alexander','PA'),('West Alexandria','OH'),('West Alton','MO'),('West Augusta','VA'),('West Babylon','NY'),('West Baden Springs','IN'),('West Baldwin','ME'),('West Barnstable','MA'),('West Bend','IA'),('West Bend','WI'),('West Berlin','NJ'),('West Bethel','ME'),('West Blocton','AL'),('West Bloomfield','MI'),('West Bloomfield','NY'),('West Boothbay Harbor','ME'),('West Boxford','MA'),('West Boylston','MA'),('West Branch','IA'),('West Branch','MI'),('West Bridgewater','MA'),('West Brookfield','MA'),('West Brooklyn','IL'),('West Burke','VT'),('West Burlington','IA'),('West Burlington','NY'),('West Camp','NY'),('West Charleston','VT'),('West Chatham','MA'),('West Chazy','NY'),('West Chester','IA'),('West Chester','OH'),('West Chester','PA'),('West Chesterfield','MA'),('West Chesterfield','NH'),('West Chicago','IL'),('West Chop','MA'),('West Clarksville','NY'),('West College Corner','IN'),('West Columbia','SC'),('West Columbia','TX'),('West Columbia','WV'),('West Concord','MN'),('West Copake','NY'),('West Cornwall','CT'),('West Covina','CA'),('West Coxsackie','NY'),('West Creek','NJ'),('West Danville','VT'),('West Davenport','NY'),('West Decatur','PA'),('West Dennis','MA'),('West Des Moines','IA'),('West Dover','VT'),('West Dummerston','VT'),('West Eaton','NY'),('West Edmeston','NY'),('West Elizabeth','PA'),('West Elkton','OH'),('West End','NC'),('West Enfield','ME'),('West Fairlee','VT'),('West Falls','NY'),('West Falmouth','MA'),('West Fargo','ND'),('West Farmington','ME'),('West Farmington','OH'),('West Finley','PA'),('West Fork','AR'),('West Forks','ME'),('West Frankfort','IL'),('West Friendship','MD'),('West Fulton','NY'),('West Glacier','MT'),('West Glover','VT'),('West Granby','CT'),('West Green','GA'),('West Greene','AL'),('West Greenwich','RI'),('West Groton','MA'),('West Grove','IA'),('West Grove','PA'),('West Halifax','VT'),('West Hamlin','WV'),('West Harrison','IN'),('West Harrison','NY'),('West Hartford','CT'),('West Hartford','VT'),('West Hartland','CT'),('West Harwich','MA'),('West Hatfield','MA'),('West Haven','CT'),('West Haverstraw','NY'),('West Helena','AR'),('West Hempstead','NY'),('West Henrietta','NY'),('West Hickory','PA'),('West Hills','CA'),('West Hollywood','CA'),('West Hurley','NY'),('West Hyannisport','MA'),('West Islip','NY'),('West Jefferson','NC'),('West Jefferson','OH'),('West Jordan','UT'),('West Kennebunk','ME'),('West Kill','NY'),('West Kingston','RI'),('West Lafayette','IN'),('West Lafayette','OH'),('West Lebanon','IN'),('West Lebanon','NH'),('West Lebanon','NY'),('West Lebanon','PA'),('West Leisenring','PA'),('West Leyden','NY'),('West Liberty','IA'),('West Liberty','IL'),('West Liberty','KY'),('West Liberty','OH'),('West Liberty','WV'),('West Linn','OR'),('West Long Branch','NJ'),('West Louisville','KY'),('West Manchester','OH'),('West Mansfield','OH'),('West Mclean','VA'),('West Medford','MA'),('West Memphis','AR'),('West Middlesex','PA'),('West Middleton','IN'),('West Middletown','PA'),('West Mifflin','PA'),('West Milford','NJ'),('West Milford','WV'),('West Millbury','MA'),('West Millgrove','OH'),('West Milton','OH'),('West Milton','PA'),('West Mineral','KS'),('West Minot','ME'),('West Monroe','LA'),('West Monroe','NY'),('West Mystic','CT'),('West New York','NJ'),('West Newbury','MA'),('West Newbury','VT'),('West Newfield','ME'),('West Newton','IN'),('West Newton','MA'),('West Newton','PA'),('West Nottingham','NH'),('West Nyack','NY'),('West Olive','MI'),('West Oneonta','NY'),('West Orange','NJ'),('West Ossipee','NH'),('West Paducah','KY'),('West Palm Beach','FL'),('West Paris','ME'),('West Park','NY'),('West Pawlet','VT'),('West Peterborough','NH'),('West Pittsburg','PA'),('West Plains','MO'),('West Point','AR'),('West Point','CA'),('West Point','GA'),('West Point','IA'),('West Point','IL'); + +INSERT INTO [cities] VALUES ('West Point','KY'),('West Point','MS'),('West Point','NE'),('West Point','NY'),('West Point','OH'),('West Point','PA'),('West Point','TX'),('West Point','VA'),('West Poland','ME'),('West Portsmouth','OH'),('West Richland','WA'),('West Ridge','AR'),('West River','MD'),('West Rockport','ME'),('West Roxbury','MA'),('West Rupert','VT'),('West Rushville','OH'),('West Rutland','VT'),('West Sacramento','CA'),('West Salem','IL'),('West Salem','OH'),('West Salem','WI'),('West Salisbury','PA'),('West Sand Lake','NY'),('West Sayville','NY'),('West Shokan','NY'),('West Simsbury','CT'),('West Somerset','KY'),('West Springfield','MA'),('West Springfield','PA'),('West Stewartstown','NH'),('West Stockbridge','MA'),('West Stockholm','NY'),('West Suffield','CT'),('West Sunbury','PA'),('West Swanzey','NH'),('West Terre Haute','IN'),('West Tisbury','MA'),('West Topsham','VT'),('West Townsend','MA'),('West Townshend','VT'),('West Union','IA'),('West Union','IL'),('West Union','MN'),('West Union','OH'),('West Union','SC'),('West Union','WV'),('West Unity','OH'),('West Valley','NY'),('West Van Lear','KY'),('West Wardsboro','VT'),('West Wareham','MA'),('West Warren','MA'),('West Warwick','RI'),('West Wendover','NV'),('West Willow','PA'),('West Windsor','NJ'),('West Winfield','NY'),('West Yarmouth','MA'),('West Yellowstone','MT'),('West York','IL'),('Westboro','MO'),('Westboro','WI'),('Westborough','MA'),('Westbrook','CT'),('Westbrook','ME'),('Westbrook','MN'),('Westbrook','TX'),('Westbrookville','NY'),('Westbury','NY'),('Westby','MT'),('Westby','WI'),('Westchester','IL'),('Westcliffe','CO'),('Westdale','NY'),('Westerlo','NY'),('Westerly','RI'),('Western','NE'),('Western Grove','AR'),('Western Springs','IL'),('Westernport','MD'),('Westernville','NY'),('Westerville','NE'),('Westerville','OH'),('Westfall','OR'),('Westfield','IA'),('Westfield','IL'),('Westfield','IN'),('Westfield','MA'),('Westfield','ME'),('Westfield','NC'),('Westfield','NJ'),('Westfield','NY'),('Westfield','PA'),('Westfield','VT'),('Westfield','WI'),('Westfield Center','OH'),('Westfir','OR'),('Westford','MA'),('Westford','NY'),('Westford','VT'),('Westgate','IA'),('Westhampton','NY'),('Westhampton Beach','NY'),('Westhoff','TX'),('Westhope','ND'),('Westlake','LA'),('Westlake','OH'),('Westlake','OR'),('Westlake Village','CA'),('Westland','MI'),('Westland','PA'),('Westley','CA'),('Westminster','CA'),('Westminster','CO'),('Westminster','MA'),('Westminster','MD'),('Westminster','SC'),('Westminster','TX'),('Westminster','VT'),('Westminster Station','VT'),('Westmont','IL'),('Westmoreland','KS'),('Westmoreland','NH'),('Westmoreland','NY'),('Westmoreland','TN'),('Westmoreland City','PA'),('Westmorland','CA'),('Weston','CO'),('Weston','CT'),('Weston','GA'),('Weston','ID'),('Weston','MA'),('Weston','MI'),('Weston','MO'),('Weston','NE'),('Weston','OH'),('Weston','OR'),('Weston','PA'),('Weston','TX'),('Weston','VT'),('Weston','WV'),('Weston','WY'),('Westons Mills','NY'),('Westover','AL'),('Westover','MD'),('Westover','PA'),('Westphalia','IA'),('Westphalia','IN'),('Westphalia','KS'),('Westphalia','MI'),('Westphalia','MO'),('Westpoint','IN'),('Westpoint','TN'),('Westport','CA'),('Westport','CT'),('Westport','IN'),('Westport','KY'),('Westport','MA'),('Westport','NY'),('Westport','PA'),('Westport','SD'),('Westport','TN'),('Westport','WA'),('Westport Point','MA'),('Westside','IA'),('Westtown','NY'),('Westtown','PA'),('Westview','KY'),('Westville','FL'),('Westville','IL'),('Westville','IN'),('Westville','NJ'),('Westville','OH'),('Westville','OK'),('Westville','SC'),('Westwego','LA'),('Westwood','CA'),('Westwood','MA'),('Westwood','NJ'),('Wethersfield','CT'),('Wetmore','CO'),('Wetmore','KS'),('Wetmore','MI'),('Wetumka','OK'),('Wetumpka','AL'),('Wever','IA'),('Wevertown','NY'),('Wewahitchka','FL'),('Wewoka','OK'),('Wexford','PA'),('Weyanoke','LA'),('Weyauwega','WI'),('Weyerhaeuser','WI'),('Weyers Cave','VA'),('Weymouth','MA'),('Whaleyville','MD'),('Wharncliffe','WV'),('Wharton','NJ'),('Wharton','OH'),('Wharton','TX'),('Wharton','WV'),('What Cheer','IA'),('Whately','MA'),('Whatley','AL'),('Wheat Ridge','CO'),('Wheatcroft','KY'),('Wheatfield','IN'),('Wheatland','CA'),('Wheatland','IA'),('Wheatland','IN'),('Wheatland','MO'),('Wheatland','ND'),('Wheatland','OK'),('Wheatland','PA'),('Wheatland','WY'),('Wheatley','AR'),('Wheaton','IL'),('Wheaton','MN'),('Wheaton','MO'),('Wheeler','IL'),('Wheeler','IN'),('Wheeler','MI'),('Wheeler','MS'),('Wheeler','OR'),('Wheeler','TX'),('Wheeler','WI'),('Wheeler Army Airfield','HI'),('Wheelersburg','OH'),('Wheeling','IL'),('Wheeling','MO'),('Wheeling','WV'),('Wheelock','TX'),('Wheelwright','KY'),('Wheelwright','MA'),('Whelen Springs','AR'),('Whick','KY'),('Whigham','GA'),('Whippany','NJ'),('Whipple','OH'),('Whippleville','NY'),('Whiskeytown','CA'),('Whitakers','NC'),('White','GA'),('White','PA'),('White','SD'),('White Bird','ID'),('White Bluff','TN'),('White Castle','LA'),('White City','KS'),('White City','OR'),('White Cloud','KS'),('White Cloud','MI'),('White Cottage','OH'),('White Deer','PA'),('White Deer','TX'),('White Earth','MN'),('White Earth','ND'),('White Hall','AR'),('White Hall','IL'),('White Hall','MD'),('White Hall','VA'),('White Haven','PA'),('White Heath','IL'),('White Horse Beach','MA'),('White House','TN'),('White Lake','MI'),('White Lake','NY'),('White Lake','SD'),('White Lake','WI'),('White Marsh','MD'),('White Marsh','VA'),('White Mills','KY'),('White Mills','PA'),('White Mountain','AK'),('White Mountain Lake','AZ'),('White Oak','GA'),('White Oak','NC'),('White Oak','TX'),('White Oak','WV'),('White Owl','SD'),('White Pigeon','MI'),('White Pine','MI'),('White Pine','TN'),('White Plains','GA'),('White Plains','KY'),('White Plains','MD'),('White Plains','NC'),('White Plains','NY'),('White Plains','VA'),('White Post','VA'),('White River','SD'),('White River Junction','VT'),('White Rock','SC'),('White Salmon','WA'),('White Sands Missile Range','NM'),('White Springs','FL'),('White Stone','SC'),('White Stone','VA'),('White Sulphur Springs','MT'),('White Sulphur Springs','NY'),('White Sulphur Springs','WV'),('White Swan','WA'),('White Water','CA'),('Whiteclay','NE'),('Whiteface','TX'),('Whitefield','ME'),('Whitefield','NH'),('Whitefield','OK'),('Whitefish','MT'),('Whiteford','MD'),('Whitehall','MI'),('Whitehall','MT'),('Whitehall','NY'),('Whitehall','PA'),('Whitehall','WI'),('Whitehorse','SD'),('Whitehouse','NJ'),('Whitehouse','OH'),('Whitehouse','TX'),('Whitehouse Station','NJ'),('Whiteland','IN'),('Whitelaw','WI'),('Whiteman Air Force Base','MO'),('Whiteoak','MO'),('Whiteriver','AZ'),('Whiterocks','UT'),('Whites City','NM'),('Whites Creek','TN'),('Whitesboro','NJ'),('Whitesboro','NY'),('Whitesboro','OK'),('Whitesboro','TX'),('Whitesburg','GA'),('Whitesburg','KY'),('Whitesburg','TN'),('Whiteside','MO'),('Whiteside','TN'),('Whitestone','NY'),('Whitestown','IN'),('Whitesville','KY'),('Whitesville','NY'),('Whitesville','WV'),('Whitetail','MT'),('Whitethorn','CA'),('Whitetop','VA'),('Whiteville','NC'),('Whiteville','TN'),('Whitewater','CO'),('Whitewater','KS'),('Whitewater','MO'),('Whitewater','MT'),('Whitewater','WI'),('Whitewood','SD'),('Whitewood','VA'),('Whitewright','TX'),('Whitfield','MS'),('Whitharral','TX'),('Whiting','IA'),('Whiting','IN'),('Whiting','KS'),('Whiting','ME'),('Whiting','VT'),('Whitingham','VT'),('Whitinsville','MA'),('Whitlash','MT'),('Whitley City','KY'),('Whitleyville','TN'),('Whitman','MA'),('Whitman','NE'),('Whitman','WV'),('Whitmer','WV'),('Whitmire','SC'),('Whitmore','CA'),('Whitmore Lake','MI'),('Whitney','NE'),('Whitney','PA'),('Whitney','TX'),('Whitney Point','NY'),('Whitsett','NC'),('Whitsett','TX'),('Whitt','TX'),('Whittaker','MI'),('Whittemore','IA'),('Whittemore','MI'),('Whitten','IA'),('Whittier','AK'),('Whittier','CA'),('Whittier','NC'),('Whittington','IL'),('Whitwell','TN'),('Wibaux','MT'),('Wichita','KS'),('Wichita Falls','TX'),('Wickatunk','NJ'),('Wickenburg','AZ'),('Wickes','AR'),('Wickett','TX'),('Wickhaven','PA'),('Wickliffe','KY'),('Wickliffe','OH'),('Wicomico','VA'),('Wicomico Church','VA'),('Wiconisco','PA'),('Wideman','AR'),('Widen','WV'),('Widener','AR'),('Widnoon','PA'),('Wiergate','TX'),('Wiggins','CO'),('Wiggins','MS'),('Wikieup','AZ'),('Wilber','NE'),('Wilberforce','OH'),('Wilbraham','MA'),('Wilbur','OR'),('Wilbur','WA'),('Wilburn','AR'),('Wilburton','OK'),('Wilburton','PA'),('Wilcoe','WV'),('Wilcox','NE'),('Wilcox','PA'),('Wild Horse','CO'),('Wild Rose','WI'),('Wilder','ID'),('Wilder','TN'),('Wilder','VT'),('Wildersville','TN'),('Wilderville','OR'),('Wildie','KY'),('Wildomar','CA'),('Wildorado','TX'),('Wildrose','ND'),('Wildsville','LA'),('Wildwood','FL'),('Wildwood','GA'),('Wildwood','NJ'),('Wildwood','PA'),('Wiley','CO'),('Wiley','GA'),('Wiley Ford','WV'),('Wileyville','WV'),('Wilkes Barre','PA'),('Wilkesboro','NC'),('Wilkeson','WA'),('Wilkesville','OH'),('Wilkinson','IN'),('Wilkinson','WV'),('Willacoochee','GA'),('Willamina','OR'),('Willard','KY'),('Willard','MO'),('Willard','MT'),('Willard','NC'),('Willard','NM'),('Willard','NY'),('Willard','OH'),('Willard','UT'),('Willard','WI'),('Willards','MD'),('Willcox','AZ'),('Willernie','MN'),('Willet','NY'),('Williams','AZ'),('Williams','CA'),('Williams','IA'),('Williams','IN'),('Williams','MN'),('Williams','OR'),('Williams','SC'),('Williams Bay','WI'),('Williamsburg','IA'),('Williamsburg','IN'),('Williamsburg','KS'),('Williamsburg','KY'),('Williamsburg','MA'),('Williamsburg','MI'),('Williamsburg','MO'),('Williamsburg','NM'),('Williamsburg','OH'),('Williamsburg','PA'),('Williamsburg','VA'),('Williamsburg','WV'),('Williamsfield','IL'),('Williamsfield','OH'),('Williamson','GA'),('Williamson','IA'),('Williamson','NY'),('Williamson','PA'),('Williamson','WV'),('Williamsport','IN'),('Williamsport','KY'),('Williamsport','MD'),('Williamsport','OH'),('Williamsport','PA'),('Williamsport','TN'),('Williamston','MI'),('Williamston','NC'),('Williamston','SC'); + +INSERT INTO [cities] VALUES ('Williamstown','KY'),('Williamstown','MA'),('Williamstown','MO'),('Williamstown','NJ'),('Williamstown','NY'),('Williamstown','OH'),('Williamstown','PA'),('Williamstown','VT'),('Williamstown','WV'),('Williamsville','IL'),('Williamsville','MO'),('Williamsville','VA'),('Williamsville','VT'),('Williford','AR'),('Willimantic','CT'),('Willingboro','NJ'),('Willington','CT'),('Willis','MI'),('Willis','TX'),('Willis','VA'),('Willis Wharf','VA'),('Willisburg','KY'),('Williston','FL'),('Williston','NC'),('Williston','ND'),('Williston','OH'),('Williston','SC'),('Williston','TN'),('Williston','VT'),('Williston Park','NY'),('Willisville','AR'),('Willisville','IL'),('Willits','CA'),('Willmar','MN'),('Willoughby','OH'),('Willow','AK'),('Willow','NY'),('Willow','OK'),('Willow Beach','AZ'),('Willow City','ND'),('Willow City','TX'),('Willow Creek','CA'),('Willow Creek','MT'),('Willow Grove','PA'),('Willow Hill','IL'),('Willow Hill','PA'),('Willow Island','NE'),('Willow Lake','SD'),('Willow River','MN'),('Willow Spring','NC'),('Willow Springs','IL'),('Willow Springs','MO'),('Willow Street','PA'),('Willow Wood','OH'),('Willowbrook','IL'),('Willows','CA'),('Wills Point','TX'),('Willsboro','NY'),('Willseyville','NY'),('Willshire','OH'),('Wilmar','AR'),('Wilmer','AL'),('Wilmer','TX'),('Wilmerding','PA'),('Wilmette','IL'),('Wilmington','CA'),('Wilmington','DE'),('Wilmington','IL'),('Wilmington','MA'),('Wilmington','NC'),('Wilmington','NY'),('Wilmington','OH'),('Wilmington','VT'),('Wilmont','MN'),('Wilmore','KS'),('Wilmore','KY'),('Wilmore','PA'),('Wilmot','AR'),('Wilmot','NH'),('Wilmot','OH'),('Wilmot','SD'),('Wilmot','WI'),('Wilsall','MT'),('Wilsey','KS'),('Wilseyville','CA'),('Wilson','AR'),('Wilson','KS'),('Wilson','LA'),('Wilson','MI'),('Wilson','NC'),('Wilson','NY'),('Wilson','OK'),('Wilson','TX'),('Wilson','WI'),('Wilson','WY'),('Wilson Creek','WA'),('Wilsonburg','WV'),('Wilsondale','WV'),('Wilsons','VA'),('Wilsons Mills','NC'),('Wilsonville','AL'),('Wilsonville','IL'),('Wilsonville','NE'),('Wilsonville','OR'),('Wilton','AL'),('Wilton','AR'),('Wilton','CA'),('Wilton','CT'),('Wilton','IA'),('Wilton','ME'),('Wilton','MN'),('Wilton','ND'),('Wilton','NH'),('Wilton','WI'),('Wimauma','FL'),('Wimberley','TX'),('Wimbledon','ND'),('Winamac','IN'),('Winburne','PA'),('Winchendon','MA'),('Winchendon Springs','MA'),('Winchester','AR'),('Winchester','CA'),('Winchester','ID'),('Winchester','IL'),('Winchester','IN'),('Winchester','KS'),('Winchester','KY'),('Winchester','MA'),('Winchester','NH'),('Winchester','OH'),('Winchester','OR'),('Winchester','TN'),('Winchester','VA'),('Winchester Center','CT'),('Wind Gap','PA'),('Wind Ridge','PA'),('Windber','PA'),('Winder','GA'),('Windermere','FL'),('Windfall','IN'),('Windham','CT'),('Windham','ME'),('Windham','NH'),('Windham','NY'),('Windham','OH'),('Windom','KS'),('Windom','MN'),('Windom','TX'),('Window Rock','AZ'),('Windsor','CA'),('Windsor','CO'),('Windsor','CT'),('Windsor','IL'),('Windsor','KY'),('Windsor','MA'),('Windsor','ME'),('Windsor','MO'),('Windsor','NC'),('Windsor','NJ'),('Windsor','NY'),('Windsor','OH'),('Windsor','PA'),('Windsor','SC'),('Windsor','VA'),('Windsor','VT'),('Windsor','WI'),('Windsor Heights','WV'),('Windsor Locks','CT'),('Windsor Mill','MD'),('Windthorst','TX'),('Windyville','MO'),('Winesburg','OH'),('Winfall','NC'),('Winfield','AL'),('Winfield','IA'),('Winfield','IL'),('Winfield','KS'),('Winfield','MO'),('Winfield','PA'),('Winfield','TN'),('Winfield','TX'),('Winfield','WV'),('Winfred','SD'),('Wing','AL'),('Wing','ND'),('Wingate','IN'),('Wingate','MD'),('Wingate','NC'),('Wingate','TX'),('Wingdale','NY'),('Winger','MN'),('Wingett Run','OH'),('Wingina','VA'),('Wingo','KY'),('Winifred','MT'),('Winifrede','WV'),('Winigan','MO'),('Wink','TX'),('Winkelman','AZ'),('Winlock','WA'),('Winn','ME'),('Winn','MI'),('Winnabow','NC'),('Winnebago','IL'),('Winnebago','MN'),('Winnebago','NE'),('Winnebago','WI'),('Winneconne','WI'),('Winnemucca','NV'),('Winner','SD'),('Winnetka','CA'),('Winnetka','IL'),('Winnetoon','NE'),('Winnett','MT'),('Winnfield','LA'),('Winnie','TX'),('Winnisquam','NH'),('Winnsboro','LA'),('Winnsboro','SC'),('Winnsboro','TX'),('Winona','KS'),('Winona','MN'),('Winona','MO'),('Winona','MS'),('Winona','OH'),('Winona','TX'),('Winona','WV'),('Winona Lake','IN'),('Winooski','VT'),('Winside','NE'),('Winslow','AR'),('Winslow','AZ'),('Winslow','IL'),('Winslow','IN'),('Winslow','NE'),('Winslow','NJ'),('Winsted','CT'),('Winsted','MN'),('Winston','GA'),('Winston','KY'),('Winston','MO'),('Winston','MT'),('Winston','NM'),('Winston','OR'),('Winston Salem','NC'),('Winstonville','MS'),('Winter','WI'),('Winter Beach','FL'),('Winter Garden','FL'),('Winter Harbor','ME'),('Winter Haven','FL'),('Winter Park','CO'),('Winter Park','FL'),('Winter Springs','FL'),('Winterhaven','CA'),('Winterport','ME'),('Winters','CA'),('Winters','TX'),('Winterset','IA'),('Winterthur','DE'),('Winterville','GA'),('Winterville','MS'),('Winterville','NC'),('Winthrop','AR'),('Winthrop','IA'),('Winthrop','MA'),('Winthrop','ME'),('Winthrop','MN'),('Winthrop','NY'),('Winthrop','WA'),('Winthrop Harbor','IL'),('Winton','CA'),('Winton','MN'),('Winton','NC'),('Wiota','IA'),('Wirt','MN'),('Wirtz','VA'),('Wiscasset','ME'),('Wisconsin Dells','WI'),('Wisconsin Rapids','WI'),('Wisdom','MT'),('Wise','NC'),('Wise','VA'),('Wise River','MT'),('Wiseman','AR'),('Wishek','ND'),('Wishon','CA'),('Wishram','WA'),('Wisner','LA'),('Wisner','NE'),('Wister','OK'),('Withams','VA'),('Withee','WI'),('Witherbee','NY'),('Witmer','PA'),('Witt','IL'),('Witten','SD'),('Wittenberg','WI'),('Wittensville','KY'),('Witter','AR'),('Witter Springs','CA'),('Wittman','MD'),('Wittmann','AZ'),('Witts Springs','AR'),('Wixom','MI'),('Woburn','MA'),('Woden','IA'),('Woden','TX'),('Wofford Heights','CA'),('Wolbach','NE'),('Wolcott','CO'),('Wolcott','CT'),('Wolcott','IN'),('Wolcott','NY'),('Wolcott','VT'),('Wolcottville','IN'),('Wolf','WY'),('Wolf Creek','MT'),('Wolf Creek','OR'),('Wolf Island','MO'),('Wolf Lake','IL'),('Wolf Lake','MN'),('Wolf Point','MT'),('Wolf Run','OH'),('Wolfcreek','WV'),('Wolfe','WV'),('Wolfe City','TX'),('Wolfeboro','NH'),('Wolfeboro Falls','NH'),('Wolfforth','TX'),('Wolflake','IN'),('Wolford','ND'),('Wolford','VA'),('Wolftown','VA'),('Wolsey','SD'),('Wolverine','MI'),('Wolverton','MN'),('Womelsdorf','PA'),('Wonalancet','NH'),('Wonder Lake','IL'),('Wonewoc','WI'),('Wood','PA'),('Wood','SD'),('Wood Dale','IL'),('Wood Lake','MN'),('Wood Lake','NE'),('Wood Ridge','NJ'),('Wood River','IL'),('Wood River','NE'),('Wood River Junction','RI'),('Woodacre','CA'),('Woodberry Forest','VA'),('Woodbine','GA'),('Woodbine','IA'),('Woodbine','KS'),('Woodbine','KY'),('Woodbine','MD'),('Woodbine','NJ'),('Woodbourne','NY'),('Woodbridge','CA'),('Woodbridge','CT'),('Woodbridge','NJ'),('Woodbridge','VA'),('Woodburn','IA'),('Woodburn','IN'),('Woodburn','KY'),('Woodburn','OR'),('Woodbury','CT'),('Woodbury','GA'),('Woodbury','KY'),('Woodbury','NJ'),('Woodbury','NY'),('Woodbury','PA'),('Woodbury','TN'),('Woodbury','VT'),('Woodbury Heights','NJ'),('Woodcliff Lake','NJ'),('Woodford','VA'),('Woodford','WI'),('Woodgate','NY'),('Woodhaven','NY'),('Woodhull','IL'),('Woodhull','NY'),('Woodinville','WA'),('Woodlake','CA'),('Woodlake','TX'),('Woodland','AL'),('Woodland','CA'),('Woodland','GA'),('Woodland','IL'),('Woodland','MI'),('Woodland','MS'),('Woodland','NC'),('Woodland','PA'),('Woodland','WA'),('Woodland','WI'),('Woodland Hills','CA'),('Woodland Mills','TN'),('Woodland Park','CO'),('Woodlawn','IL'),('Woodlawn','TN'),('Woodlawn','TX'),('Woodlawn','VA'),('Woodleaf','NC'),('Woodlyn','PA'),('Woodman','WI'),('Woodmere','NY'),('Woodridge','IL'),('Woodridge','NY'),('Woodrow','CO'),('Woodruff','AZ'),('Woodruff','SC'),('Woodruff','UT'),('Woodruff','WI'),('Woods Cross','UT'),('Woods Cross Roads','VA'),('Woods Hole','MA'),('Woodsboro','MD'),('Woodsboro','TX'),('Woodsfield','OH'),('Woodside','DE'),('Woodside','NY'),('Woodson','AR'),('Woodson','IL'),('Woodson','TX'),('Woodstock','AL'),('Woodstock','CT'),('Woodstock','GA'),('Woodstock','IL'),('Woodstock','MD'),('Woodstock','MN'),('Woodstock','NH'),('Woodstock','NY'),('Woodstock','OH'),('Woodstock','VA'),('Woodstock','VT'),('Woodstock Valley','CT'),('Woodston','KS'),('Woodstown','NJ'),('Woodsville','NH'),('Woodville','AL'),('Woodville','FL'),('Woodville','MA'),('Woodville','MS'),('Woodville','OH'),('Woodville','TX'),('Woodville','VA'),('Woodville','WI'),('Woodville','WV'),('Woodward','IA'),('Woodward','OK'),('Woodward','PA'),('Woodway','TX'),('Woodworth','LA'),('Woodworth','ND'),('Woodworth','WI'),('Woody','CA'),('Woody Creek','CO'),('Wooldridge','MO'),('Woolford','MD'),('Woollum','KY'),('Woolrich','PA'),('Woolstock','IA'),('Woolwich','ME'),('Woolwine','VA'),('Woonsocket','RI'),('Woonsocket','SD'),('Wooster','AR'),('Wooster','OH'),('Woosung','IL'),('Wooton','KY'),('Worcester','MA'),('Worcester','NY'),('Worcester','PA'),('Worcester','VT'),('Worden','IL'),('Worden','MT'),('Worland','WY'),('Worley','ID'),('Woronoco','MA'),('Worth','IL'),('Worth','MO'),('Wortham','TX'),('Worthing','SD'),('Worthington','IA'),('Worthington','IN'),('Worthington','KY'),('Worthington','MA'),('Worthington','MN'),('Worthington','MO'),('Worthington','PA'),('Worthington','WV'),('Worthington Springs','FL'),('Worthville','KY'),('Worthville','PA'),('Worton','MD'),('Wounded Knee','SD'),('Woxall','PA'),('Wrangell','AK'); + +INSERT INTO [cities] VALUES ('Wray','CO'),('Wray','GA'),('Wren','OH'),('Wrens','GA'),('Wrenshall','MN'),('Wrentham','MA'),('Wright','AR'),('Wright','KS'),('Wright','MN'),('Wright','WY'),('Wright City','MO'),('Wright City','OK'),('Wrights','IL'),('Wrightsboro','TX'),('Wrightstown','NJ'),('Wrightstown','WI'),('Wrightsville','AR'),('Wrightsville','GA'),('Wrightsville','PA'),('Wrightsville Beach','NC'),('Wrightwood','CA'),('Wrigley','KY'),('Wurtsboro','NY'),('Wyaconda','MO'),('Wyalusing','PA'),('Wyandanch','NY'),('Wyandotte','MI'),('Wyandotte','OK'),('Wyanet','IL'),('Wyano','PA'),('Wyarno','WY'),('Wyatt','IN'),('Wyatt','MO'),('Wyatt','WV'),('Wyckoff','NJ'),('Wyco','WV'),('Wycombe','PA'),('Wye Mills','MD'),('Wykoff','MN'),('Wylie','TX'),('Wylliesburg','VA'),('Wymore','NE'),('Wynantskill','NY'),('Wyncote','PA'),('Wyndmere','ND'),('Wynnburg','TN'),('Wynne','AR'),('Wynnewood','OK'),('Wynnewood','PA'),('Wynona','OK'),('Wynot','NE'),('Wyocena','WI'),('Wyola','MT'),('Wyoming','IA'),('Wyoming','IL'),('Wyoming','MI'),('Wyoming','MN'),('Wyoming','NY'),('Wyoming','PA'),('Wyoming','RI'),('Wyoming','WV'),('Wysox','PA'),('Wytheville','VA'),('Wytopitlock','ME'),('Xenia','IL'),('Xenia','OH'),('Yabucoa','PR'),('Yachats','OR'),('Yacolt','WA'),('Yadkinville','NC'),('Yakima','WA'),('Yakutat','AK'),('Yalaha','FL'),('Yale','IA'),('Yale','IL'),('Yale','MI'),('Yale','OK'),('Yale','SD'),('Yale','VA'),('Yamhill','OR'),('Yampa','CO'),('Yancey','TX'),('Yanceyville','NC'),('Yankeetown','FL'),('Yankton','SD'),('Yantic','CT'),('Yantis','TX'),('Yaphank','NY'),('Yarmouth','IA'),('Yarmouth','ME'),('Yarmouth Port','MA'),('Yarnell','AZ'),('Yatahey','NM'),('Yates Center','KS'),('Yates City','IL'),('Yatesboro','PA'),('Yatesville','GA'),('Yauco','PR'),('Yawkey','WV'),('Yazoo City','MS'),('Yeaddiss','KY'),('Yeagertown','PA'),('Yellow Jacket','CO'),('Yellow Pine','ID'),('Yellow Spring','WV'),('Yellow Springs','OH'),('Yellowstone National Park','WY'),('Yellowtail','MT'),('Yellville','AR'),('Yelm','WA'),('Yemassee','SC'),('Yeoman','IN'),('Yerington','NV'),('Yerkes','KY'),('Yermo','CA'),('Yeso','NM'),('Yettem','CA'),('Yoakum','TX'),('Yoder','CO'),('Yoder','IN'),('Yoder','KS'),('Yoder','WY'),('Yolo','CA'),('Yolyn','WV'),('Yoncalla','OR'),('Yonkers','NY'),('Yorba Linda','CA'),('York','AL'),('York','ME'),('York','ND'),('York','NE'),('York','NY'),('York','PA'),('York','SC'),('York Beach','ME'),('York Harbor','ME'),('York Haven','PA'),('York New Salem','PA'),('York Springs','PA'),('Yorklyn','DE'),('Yorkshire','NY'),('Yorkshire','OH'),('Yorktown','AR'),('Yorktown','IA'),('Yorktown','IN'),('Yorktown','TX'),('Yorktown','VA'),('Yorktown Heights','NY'),('Yorkville','CA'),('Yorkville','IL'),('Yorkville','NY'),('Yorkville','OH'),('Yorkville','TN'),('Yosemite','KY'),('Yosemite National Park','CA'),('Young','AZ'),('Young America','IN'),('Young America','MN'),('Young Harris','GA'),('Youngstown','FL'),('Youngstown','NY'),('Youngstown','OH'),('Youngstown','PA'),('Youngsville','LA'),('Youngsville','NC'),('Youngsville','NM'),('Youngsville','NY'),('Youngsville','PA'),('Youngtown','AZ'),('Youngwood','PA'),('Yountville','CA'),('Ypsilanti','MI'),('Ypsilanti','ND'),('Yreka','CA'),('Yuba City','CA'),('Yucaipa','CA'),('Yucca','AZ'),('Yucca Valley','CA'),('Yukon','MO'),('Yukon','OK'),('Yukon','PA'),('Yulan','NY'),('Yulee','FL'),('Yuma','AZ'),('Yuma','CO'),('Yuma','TN'),('Yutan','NE'),('Zacata','VA'),('Zachary','LA'),('Zachow','WI'),('Zahl','ND'),('Zaleski','OH'),('Zalma','MO'),('Zamora','CA'),('Zanesfield','OH'),('Zanesville','IN'),('Zanesville','OH'),('Zanoni','MO'),('Zap','ND'),('Zapata','TX'),('Zarephath','NJ'),('Zavalla','TX'),('Zearing','IA'),('Zebulon','GA'),('Zebulon','NC'),('Zeeland','MI'),('Zeeland','ND'),('Zeigler','IL'),('Zelienople','PA'),('Zellwood','FL'),('Zenda','KS'),('Zenda','WI'),('Zenia','CA'),('Zephyr','TX'),('Zephyr Cove','NV'),('Zephyrhills','FL'),('Zieglerville','PA'),('Zillah','WA'),('Zimmerman','MN'),('Zion','IL'),('Zion Grove','PA'),('Zionhill','PA'),('Zionsville','IN'),('Zionsville','PA'),('Zionville','NC'),('Zirconia','NC'),('Zoar','OH'),('Zoe','KY'),('Zolfo Springs','FL'),('Zortman','MT'),('Zullinger','PA'),('Zumbro Falls','MN'),('Zumbrota','MN'),('Zuni','NM'),('Zuni','VA'),('Zurich','MT'),('Zwingle','IA'),('Zwolle','LA'); diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js index 2145591..1634730 100755 --- a/test/mssql/tests-adapter.js +++ b/test/mssql/tests-adapter.js @@ -37,18 +37,17 @@ const connection_released = qb => { describe('QueryBuilder() - MS SQL Adapter', () => { const driver = 'mssql'; const settings = { - host: '127.0.0.1', + host: 'localhost', database: 'mock_db', user: 'travis', + password: 'Password123', version: '4.1.0', - port: 3306, + port: 1433, options: { encrypt: false } }; - - afterEach(done => { // const qb = new QueryBuilder(Object.assign({}, settings), driver); // qb.disconnect(); @@ -235,11 +234,11 @@ describe('QueryBuilder() - MS SQL Adapter', () => { qb.connect(err => { expect(err).to.not.be.instanceof(Error); - qb.query("select * from [_employees] where [FirstName] like 'Kyl%' and [Department] = 111", (err, res) => { + qb.query("select * from [cities] where [city] like 'Z%' and [state_code] = 'FL'", (err, res) => { check(done, () => { - expect(err).to.not.be.instanceof(Error); - expect(res).to.not.be.empty; - expect(res).to.have.length(1); + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); }); }); }); From 6671b0d61d0d9f1845bb6cfddf3443efa548f9c0 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 19 Jun 2018 11:39:30 -0400 Subject: [PATCH 145/200] Latest updates to the mssql adapter. --- README.md | 2 +- drivers/mssql/query_builder.js | 2 -- test/create_mock_dbs.sh | 1 + test/mssql/tests-adapter.js | 29 ++++++++++++++--------------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2eacafb..445b7cf 100755 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ The primary benefits of this module (currently) are: # Database Drivers ## Currently Written: - MySQL / MariaDB +- Microsoft SQL Server ## Coming Soon: - Postgres -- Microsoft SQL Server - Oracle - SQLite - MongoDB diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index 33c9b47..1bb3883 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -278,8 +278,6 @@ const QueryBuilder = function() { }; const qb_escape = (qb,str) => { - const mysql = require('mysql'); - if (typeof str === 'boolean') { str = (str === false ? 0 : 1); } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 659474a..a8a7931 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,2 +1,3 @@ #!/usr/bin/env bash ./test/mysql/create_mysql_mock.sh +#./test/mysql/create_mssql_mock.sh diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js index 1634730..bfab119 100755 --- a/test/mssql/tests-adapter.js +++ b/test/mssql/tests-adapter.js @@ -201,21 +201,20 @@ describe('QueryBuilder() - MS SQL Adapter', () => { }); }); }); - // it('should allow us to escape certain values', done => { - // const qb = new QueryBuilder(Object.assign({}, settings), driver); - // qb.connect(err => { - // check(done, () => { - // should.exist(qb.escape); - // qb.escape.should.be.a('function'); - // expect(qb.escape(null)).to.be.eql('NULL'); - // expect(qb.escape('3')).to.be.eql(3); - // expect(qb.escape(false)).to.be.eql(0); - // expect(qb.escape(true)).to.be.eql(1); - // expect(qb.escape(null)).to.be.eql('NULL'); - // qb.disconnect(); - // }); - // }); - // }); + it('should allow us to escape certain values', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.escape); + qb.escape.should.be.a('function'); + expect(qb.escape(null)).to.be.eql('NULL'); + expect(qb.escape('3')).to.be.eql(3); + expect(qb.escape(false)).to.be.eql(0); + expect(qb.escape(true)).to.be.eql(1); + qb.disconnect(); + }); + }); + }); // it('should allow us to escape identifiers the MS SQL way', done => { // const qb = new QueryBuilder(Object.assign({}, settings), driver); // qb.connect(err => { From b009dd9873589c8f51caa2e11773f7734b1d65ef Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 20 Jun 2018 19:56:18 -0400 Subject: [PATCH 146/200] Converted all MySQL driver libraries to ES6 Class syntax. This fixes #26. --- Makefile | 2 +- drivers/mssql/query_builder.js | 4 +- drivers/mysql/adapter.js | 64 + drivers/mysql/adapters.js | 232 --- drivers/mysql/adapters/cluster.js | 10 + drivers/mysql/adapters/pool.js | 59 + drivers/mysql/adapters/single.js | 55 + drivers/mysql/adapters_old.js | 233 --- drivers/mysql/query_builder.js | 2162 ++++++++++++----------- drivers/mysql/query_exec.js | 322 ++-- index.js | 20 +- test/mysql/tests-adapter.js | 45 +- test/mysql/tests-compilation_methods.js | 3 +- test/mysql/tests-count.js | 101 +- test/mysql/tests-delete.js | 3 +- test/mysql/tests-distinct.js | 5 +- test/mysql/tests-empty_table.js | 5 +- test/mysql/tests-from.js | 5 +- test/mysql/tests-general.js | 7 +- test/mysql/tests-get.js | 3 +- test/mysql/tests-groupby.js | 5 +- test/mysql/tests-having.js | 5 +- test/mysql/tests-insert.js | 3 +- test/mysql/tests-insert_batch.js | 5 +- test/mysql/tests-join.js | 3 +- test/mysql/tests-like.js | 3 +- test/mysql/tests-limit.js | 5 +- test/mysql/tests-multiple-queries.js | 29 + test/mysql/tests-offset.js | 5 +- test/mysql/tests-orderby.js | 5 +- test/mysql/tests-select.js | 3 +- test/mysql/tests-set.js | 3 +- test/mysql/tests-truncate.js | 5 +- test/mysql/tests-update.js | 5 +- test/mysql/tests-update_batch.js | 5 +- test/mysql/tests-where.js | 5 +- test/mysql/tests-where_in.js | 5 +- 37 files changed, 1638 insertions(+), 1801 deletions(-) create mode 100755 drivers/mysql/adapter.js delete mode 100755 drivers/mysql/adapters.js create mode 100755 drivers/mysql/adapters/cluster.js create mode 100755 drivers/mysql/adapters/pool.js create mode 100755 drivers/mysql/adapters/single.js delete mode 100755 drivers/mysql/adapters_old.js create mode 100755 test/mysql/tests-multiple-queries.js diff --git a/Makefile b/Makefile index 4e994ba..ca0bff9 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = test/mssql/*.js +TESTS = test/mysql/*.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index 1bb3883..f10018d 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -1172,7 +1172,7 @@ const QueryBuilder = function() { throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + throw new Error("set(): Infinity and NaN are not valid values in MS SQL!"); } // Escape the key to be DRY @@ -1297,7 +1297,7 @@ const QueryBuilder = function() { throw new Error("set(): Invalid value provided!"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + throw new Error("set(): Infinity and NaN are not valid values in MS SQL!"); } } } diff --git a/drivers/mysql/adapter.js b/drivers/mysql/adapter.js new file mode 100755 index 0000000..94c3504 --- /dev/null +++ b/drivers/mysql/adapter.js @@ -0,0 +1,64 @@ +const QueryExec = require('./query_exec'); + +class Adapter extends QueryExec { + constructor(settings) { + super(settings); + + // Verify that an instance of Node QueryBuilder was passed in + if (!settings || typeof settings !== 'object') { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + this._original_settings = settings; + this._connection_settings = settings; + + // Enable debugging if necessary + this.debugging = false; + if (this._connection_settings.hasOwnProperty('debug') && this._connection_settings.debug === true) { + this.debugging = true; + delete this._connection_settings.debug; + } + + // Verify that required fields are provided... + if (Object.keys(this._connection_settings).length === 0) throw new Error("No connection information provided!"); + if (!this._connection_settings.hasOwnProperty('host')) this._connection_settings.host = 'localhost'; + if (!this._connection_settings.hasOwnProperty('user')) { console.log("Settings:", this._connection_settings); throw new Error("No user property provided. Hint: It can be NULL"); } + + this.map_connection_settings(); + } + + // **************************************************************************** + // Map generic NQB connection settings to node-mysql's format + // ---- + // NOTE: MySQL connection settings names are the same as Node Querybuilder, + // it's just good practice to go ahead and do this in case things change. + // **************************************************************************** + map_connection_settings() { + const nqb_settings = JSON.parse(JSON.stringify(this._connection_settings)); + + this._connection_settings = { + host: nqb_settings.host, + user: nqb_settings.user, + password: nqb_settings.password, + }; + + if (nqb_settings.hasOwnProperty('database')) { + this._connection_settings.database = nqb_settings.database; + delete nqb_settings.database; + } + if (nqb_settings.hasOwnProperty('port')) { + this._connection_settings.port = nqb_settings.port; + delete nqb_settings.port; + } + + // Remove mapped settings: + delete nqb_settings.host; + delete nqb_settings.user; + delete nqb_settings.password; + + // Merge any driver-specific settings into connection settings + this._connection_settings = Object.assign(this._connection_settings, nqb_settings); + } +} + +module.exports = Adapter; diff --git a/drivers/mysql/adapters.js b/drivers/mysql/adapters.js deleted file mode 100755 index f0f9a01..0000000 --- a/drivers/mysql/adapters.js +++ /dev/null @@ -1,232 +0,0 @@ -// Load MySQL Driver -const mysql = require('mysql'); - -class Adapter { - constructor(nqb) { - // Verify that an instance of Node QueryBuilder was passed in - if (!nqb || typeof nqb !== 'object') { - throw new Error("No/Invalid QueryBuilder super object instance supplied."); - } - - // Store QB super object as class prop - this.nqb = Object.assign({}, nqb); - - // Verify setting property exists - if (!this.nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } - - // Enable debugging if necessary - this.debugging = false; - if (this.nqb.settings.hasOwnProperty('qb_debug') && this.nqb.settings.qb_debug === true) { - this.debugging = true; - delete this.nqb.settings.qb_debug; - } - - // Verify that required fields are provided... - if (Object.keys(this.nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!this.nqb.settings.hasOwnProperty('host')) this.nqb.settings.host = 'localhost'; - if (!this.nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!this.nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); - - this.map_connection_settings(); - } - - // **************************************************************************** - // Map generic NQB connection settings to node-mysql's format - // ---- - // NOTE: MySQL connection settings names are the same as Node Querybuilder, - // it's just good practice to go ahead and do this in case things change. - // **************************************************************************** - map_connection_settings() { - const nqb_settings = JSON.parse(JSON.stringify(this.nqb.settings)); - - this.connection_settings = { - host: nqb_settings.host, - user: nqb_settings.user, - password: nqb_settings.password, - }; - - if (nqb_settings.hasOwnProperty('database')) { - this.connection_settings.database = nqb_settings.database; - delete nqb_settings.database; - } - if (nqb_settings.hasOwnProperty('port')) { - this.connection_settings.port = nqb_settings.port; - delete nqb_settings.port; - } - - // Remove mapped settings: - delete nqb_settings.host; - delete nqb_settings.user; - delete nqb_settings.password; - - // Merge any driver-specific settings into connection settings - this.connection_settings = Object.assign(this.connection_settings, nqb_settings); - } - - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object - // **************************************************************************** - get_query_builder() { - try { - return require('./query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + this.nqb.driver + ": " + e); - } - } - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object - // **************************************************************************** - get_query_exec(qb, conn) { - try { - return require('./query_exec.js').QueryExec(qb, conn); - } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + this.nqb.driver + ": " + e); - } - } -} - - -// ----------------------------------------------------------------------------------------------------------------------------- - - -class Single extends Adapter { - constructor(nqb, settings) { - super(nqb); - - // Set defaults - this.pool = null; - this._connection = null; - - // If the Pool object is instatiating this Adapter, use it's connection - if (settings && settings.pool) { - this.pool = settings.pool.pool; - this._connection = settings.pool.connection; - } - // Otherwise, let's create a new connection - else { - this._connection = new mysql.createConnection(this.connection_settings); - } - - if (!this._connection) throw new Error("No connection could be established!"); - - this.qb = this.get_query_builder(); - this.qe = this.get_query_exec(this.qb, this._connection); - - const self = this; - - return Object.assign({ - connection_settings: function() { - return self.connection_settings; - }, - - connect: function(callback) { - return self._connection.connect(callback); - }, - - connection: function() { - return self._connection; - }, - - escape: function(str) { - return self._connection.escape(str); - }, - - escape_id: function(str) { - return self._connection.escapeId(str); - }, - - disconnect: function(callback) { - return self._connection.end(callback); - }, - - release: function() { - if (!self.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - self.pool.releaseConnection(self._connection); - } - }, this.qb, this.qe); - } -} - - -// ----------------------------------------------------------------------------------------------------------------------------- - - -class Pool extends Adapter { - constructor(nqb) { - super(nqb); - - // Create pool for node-querybuild object if it doesn't already have one. - if (!this.nqb.hasOwnProperty('pool') || this.nqb.pool.length === 0) { - // Create connection Pool - this.nqb.pool = mysql.createPool(this.connection_settings); - - // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) - if (this.debugging === true) { - this.nqb.pool.getConnection((err, connection) => { - connection.query('SELECT 1 + 1 AS solution', (err) => { - connection.release(); - if (err) { - console.error(err); - } else { - console.log('mysql connection pool created'); - } - }); - }); - } - } - - const self = this; - - return { - pool: function() { - return self.nqb.pool; - }, - get_connection: function(callback) { - if (null === self.nqb.pool) { - const error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - - self.nqb.pool.getConnection((err, connection) => { - if (err) throw err; - const adapter = new Single(self.nqb, { - pool: { - pool: self.nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - self.nqb.pool.end(callback); - } - } - } -} - - -// ----------------------------------------------------------------------------------------------------------------------------- - - -class Cluster extends Adapter { - constructor(nqb) { - super(nqb); - return {}; - } -} - -exports.Adapters = {Single,Pool,Cluster}; diff --git a/drivers/mysql/adapters/cluster.js b/drivers/mysql/adapters/cluster.js new file mode 100755 index 0000000..0fba6cf --- /dev/null +++ b/drivers/mysql/adapters/cluster.js @@ -0,0 +1,10 @@ +const Adapter = require('../adapter.js'); + +class Cluster extends Adapter { + constructor(nqb) { + super(nqb); + return {}; + } +} + +module.exports = Cluster; diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js new file mode 100755 index 0000000..a3653d4 --- /dev/null +++ b/drivers/mysql/adapters/pool.js @@ -0,0 +1,59 @@ +const mysql = require('mysql'); +const Adapter = require('../adapter.js'); +const Single = require('./single.js'); + +class Pool extends Adapter { + constructor(settings) { + super(settings); + + // Create pool for node-querybuild object if it doesn't already have one. + if (!this.hasOwnProperty('pool') || this._pool.length === 0) { + // Create connection Pool + this._pool = mysql.createPool(this._connection_settings); + + // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) + if (this.debugging === true) { + this._pool.getConnection((err, connection) => { + connection.query('SELECT 1 + 1 AS solution', err => { + connection.release(); + if (err) { + console.error(err); + } else { + console.log('mysql connection pool created'); + } + }); + }); + } + } + } + + pool() { + return this._pool; + } + + get_connection(cb) { + if (!this._pool) { + const error_msg = "Connection pool not available!"; + if (console && console.hasOwnProperty('error')) console.error(error_msg); + throw new Error(error_msg); + } + + this._pool.getConnection((err, connection) => { + if (err) throw err; + const adapter = new Single(this._original_settings, { + pool: { + pool: this._pool, + connection: connection + } + }); + + cb(adapter); + }); + } + + disconnect(cb) { + this._pool.end(cb); + } +} + +module.exports = Pool; diff --git a/drivers/mysql/adapters/single.js b/drivers/mysql/adapters/single.js new file mode 100755 index 0000000..61d8b63 --- /dev/null +++ b/drivers/mysql/adapters/single.js @@ -0,0 +1,55 @@ +// Load MySQL Driver +const mysql = require('mysql'); +const Adapter = require('../adapter.js'); + +class Single extends Adapter { + constructor(settings, pool) { + super(settings, pool); + + // Set defaults + this.pool = null; + this._connection = null; + + // If the Pool object is instatiating this Adapter, use it's connection + if (pool && pool.pool) { + this.pool = pool.pool.pool; // NOTE: That truely is insane looking... ¯\_(ツ)_/¯ + this._connection = pool.pool.connection; + } + // Otherwise, let's create a new connection + else { + this._connection = new mysql.createConnection(this._connection_settings); + } + + if (!this._connection) throw new Error("No connection could be established!"); + + // this.qb = this.get_query_builder(); + // this.qe = this.get_query_exec(this.qb, this._connection); + } + + connection_settings() { + return this._connection_settings; + } + + connect(cb) { + return this._connection.connect(cb); + } + + connection() { + return this._connection; + } + + escape_id(str) { + return this._connection.escapeId(str); + } + + disconnect(callback) { + return this._connection.end(callback); + } + + release() { + if (!this.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + this.pool.releaseConnection(this._connection); + } +} + +module.exports = Single; diff --git a/drivers/mysql/adapters_old.js b/drivers/mysql/adapters_old.js deleted file mode 100755 index 026a3a2..0000000 --- a/drivers/mysql/adapters_old.js +++ /dev/null @@ -1,233 +0,0 @@ -const Adapters = function(nqb) { - // Load MySQL Driver - const mysql = require('mysql'); - - // Verify setting property exists - if (!nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } - - // Enable debugging if necessary - this.debugging = false; - if (nqb.settings.hasOwnProperty('qb_debug') && nqb.settings.qb_debug === true) { - this.debugging = true; - delete nqb.settings.qb_debug; - } - - // Verify that required fields are provided... - if (Object.keys(nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!nqb.settings.hasOwnProperty('host')) nqb.settings.host = 'localhost'; - if (!nqb.settings.hasOwnProperty('user')) throw new Error("No user property provided. Hint: It can be NULL"); - //if (!nqb.settings.hasOwnProperty('password')) throw new Error("No connection password provided. Hint: It can be NULL"); - - this.connection_settings = {}; - const that = this; - - // **************************************************************************** - // Map generic NQB connection settings to node-mysql's format - // ---- - // NOTE: MySQL connection settings names are the same as Node Querybuilder, - // it's just good practice to go ahead and do this in case things change. - // **************************************************************************** - const map_connection_settings = () => { - this.connection_settings = { - host: nqb.settings.host, - user: nqb.settings.user, - password: nqb.settings.password - } - if (nqb.settings.hasOwnProperty('database')) { - this.connection_settings.database = nqb.settings.database; - delete nqb.settings.database - } - if (nqb.settings.hasOwnProperty('port')) { - this.connection_settings.port = nqb.settings.port; - delete nqb.settings.port - } - - // Remove mapped settings: - delete nqb.settings.host - delete nqb.settings.user - delete nqb.settings.password - - // Merge any driver-specific settings into connection settings - this.connection_settings = Object.assign(this.connection_settings, nqb.settings); - } - - map_connection_settings(); - - - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object - // **************************************************************************** - const get_query_builder = () => { - try { - return require('./query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error("Couldn't load the QueryBuilder library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object - // **************************************************************************** - const get_query_exec = (qb, conn) => { - try { - return require('./query_exec.js').QueryExec(qb, conn); - } catch(e) { - throw new Error("Couldn't load the QueryExec library for " + nqb.driver + ": " + e); - } - }; - - // **************************************************************************** - // Generic Single Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Adapter = function(settings) { - let pool, connection; - - // If the Pool object is instatiating this Adapter, use it's connection - if (settings && settings.pool) { - pool = settings.pool.pool; - connection = settings.pool.connection; - } - // Otherwise, let's create a new connection - else { - connection = new mysql.createConnection(that.connection_settings); - } - - const qb = get_query_builder(); - const qe = get_query_exec(qb, connection); - - const adapter = Object.assign({ - connection_settings: function() { - return that.connection_settings; - }, - - connect: function(callback) { - return connection.connect(callback); - }, - - connection: function() { - return connection; - }, - - escape: function(str) { - return connection.escape(str); - }, - - escape_id: function(str) { - return connection.escapeId(str); - }, - - disconnect: function(callback) { - return connection.end(callback); - }, - - release: function() { - if (!pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - pool.releaseConnection(connection); - } - }, qb, qe); - - return adapter; - }; - - // **************************************************************************** - // Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Pool = function() { - // Return Pool Object - const return_pool = () => { - return { - pool: function() { - return nqb.pool; - }, - get_connection: function(callback) { - if (null === nqb.pool) { - const error_msg = "Connection pool not available!"; - if (console && console.hasOwnProperty('error')) console.error(error_msg); - throw new Error(error_msg); - } - - nqb.pool.getConnection((err, connection) => { - if (err) throw err; - const adapter = new Adapter({ - pool: { - pool: nqb.pool, - connection: connection - } - }); - - callback(adapter); - }); - }, - disconnect: function(callback) { - nqb.pool.end(callback); - } - } - }; - - // Create pool for node-querybuild object if it doesn't already have one. - if (!nqb.hasOwnProperty('pool') || nqb.pool.length === 0) { - // Create connection Pool - nqb.pool = mysql.createPool(that.connection_settings); - - // Test connection pool (asynchronous -- this shouldn't prevent the pool from initially loading) - if (that.debugging === true) { - nqb.pool.getConnection((err, connection) => { - connection.query('SELECT 1 + 1 AS solution', (err) => { - connection.release(); - if (err) { - console.error(err); - } else { - console.log('mysql connection pool created'); - } - }); - }); - } - } - return return_pool(); - }; - - // **************************************************************************** - // Clustered Connection Pool Adapter - // ----- - // @return Object Adapter object - // **************************************************************************** - const Cluster = () => { - - }; - - // **************************************************************************** - // Basic Description - // ----- - // @param - // @return - // **************************************************************************** - const determine_adapter = () => { - switch(nqb.connection_type) { - case 'cluster': - return new Cluster(); - case 'pool': - return new Pool(); - case 'single': - default: - return new Adapter({}); - } - } - - return determine_adapter(); -}; - -exports.Adapters = Adapters; diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 33c9b47..9e4f212 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,7 +1,24 @@ -const QueryBuilder = function() { +class QueryBuilder { + constructor() { + this.where_array = []; + this.where_in_array = []; + this.from_array = []; + this.join_array = []; + this.select_array = []; + this.set_array = []; // has to be array to work as reference + this.order_by_array = []; + this.group_by_array = []; + this.having_array = []; + this.limit_to = []; // has to be array to work as reference + this.offset_val = []; // has to be array to work as reference + this.join_clause = []; + this.last_query_string = []; // has to be array to work as reference + this.distinct_clause = []; // has to be array to work as reference + this.aliased_tables = []; + } - // ------------------------------ GENERIC FUNCTIONS ------------------------------// - const array_values = item => { + // ------------------------------ GENERIC METHODS ------------------------------// + _array_values(item) { const keys = Object.keys(item); const length = keys.length; const values = Array(length); @@ -9,10 +26,9 @@ const QueryBuilder = function() { values[i] = item[keys[i]]; } return values; - }; - - const prepare_for_limit_and_offset = (item, type = 'limit') => { + } + _prepare_for_limit_and_offset(item, type = 'limit') { type = type.toLowerCase(); if (!/^(string|number)$/.test(typeof item)) { @@ -52,7 +68,7 @@ const QueryBuilder = function() { return item; } - const extract_having_parts = (key,key_array) => { + _extract_having_parts(key, key_array) { let m; key = key.trim().replace(/\s+/g,' '); const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting @@ -87,7 +103,7 @@ const QueryBuilder = function() { } // Simply setting all properties to [] causes reference issues in the parent class. - const clear_array = (a,debug) => { + _clear_array(a, debug) { if (debug === true) { console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); console.dir(a); @@ -108,14 +124,14 @@ const QueryBuilder = function() { console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); console.dir(a); } - }; + } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - const track_aliases = (qb,table) => { + _track_aliases(table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (let i in table) { const t = table[i]; - track_aliases(qb,t); + this._track_aliases(t); } return; } @@ -123,7 +139,7 @@ const QueryBuilder = function() { // Does the string contain a comma? If so, we need to separate // the string into discreet statements if (table.indexOf(',') !== -1) { - return track_aliases(qb,table.split(',')); + return this._track_aliases(table.split(',')); } // if a table alias is used we can recognize it by a space @@ -135,28 +151,28 @@ const QueryBuilder = function() { const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist - if (qb.aliased_tables.indexOf(alias) == -1) { - qb.aliased_tables.push(alias); + if (this.aliased_tables.indexOf(alias) == -1) { + this.aliased_tables.push(alias); } } - }; + } - const create_aliases_from_table = item => { + _create_aliases_from_table(item) { if (item.indexOf('.') !== -1) { return item.split('.').reverse()[0]; } return item; - }; + } - const escape_identifiers = (item = '*') => { + _escape_identifiers(item = '*') { if (item === '*') { return item; } if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (let i in item) { - item[i] = escape_identifiers(item[i]); + item[i] = this._escape_identifiers(item[i]); } return item; } @@ -176,7 +192,7 @@ const QueryBuilder = function() { return str.replace(/[`]+/g,'`'); }; - const protect_identifiers = (qb,item,protect_identifiers) => { + _protect_identifiers(item, protect_identifiers) { if (item === '') return item; protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); @@ -184,14 +200,16 @@ const QueryBuilder = function() { if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { const escaped_array = {}; - for (k in item) { + for (let k in item) { const v = item[k]; - escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); + escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v); } - return escaped_array; } + // Make sure item is a string... + if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); + // Convert tabs or multiple spaces into single spaces item = item.replace(/\s+/g, ' '); @@ -203,10 +221,10 @@ const QueryBuilder = function() { let alias; if (has_alias >= 0) { alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = escape_identifiers(alias); + alias = this._escape_identifiers(alias); if (alias != '') alias = ' AS ' + alias; - item = item.substr(0,has_alias + 1); + item = item.substr(0, has_alias + 1); } else { alias = ''; } @@ -220,13 +238,13 @@ const QueryBuilder = function() { // Basically we remove everything to the right of the first space if (/\sAS\s/ig.test(item)) { const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0,alias_index); + alias = (protect_identifiers ? item.substr(alias_index, 4) + this._escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0, alias_index); } else if (item.indexOf(' ') !== -1) { const alias_index = item.indexOf(' '); - alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); item = item.substr(0,alias_index); } @@ -240,12 +258,12 @@ const QueryBuilder = function() { // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item - if (qb.aliased_tables.indexOf(first_seg) !== -1) { + if (this.aliased_tables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { for (let key in parts) { const val = parts[key]; if (val !== '*') { - parts[key] = escape_identifiers(val); + parts[key] = this._escape_identifiers(val); } } @@ -255,19 +273,19 @@ const QueryBuilder = function() { } if (protect_identifiers === true) { - item = escape_identifiers(item); + item = this._escape_identifiers(item); } return item + alias; } if (protect_identifiers === true) { - item = escape_identifiers(item); + item = this._escape_identifiers(item); } return item + alias; }; - const has_operator = function (str) { + _has_operator(str) { if (typeof str === 'string' && str.length > 0) { const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); if (!match) { @@ -277,7 +295,7 @@ const QueryBuilder = function() { return true; }; - const qb_escape = (qb,str) => { + _qb_escape(str) { const mysql = require('mysql'); if (typeof str === 'boolean') { @@ -293,59 +311,59 @@ const QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// - const build_where_clause = qb => { + _build_where_clause() { let sql = ''; - if (qb.where_array.length > 0) { + if (this.where_array.length > 0) { sql += " WHERE "; } - sql += qb.where_array.join(" "); + sql += this.where_array.join(" "); return sql; }; - const build_from_clause = qb => { + _build_from_clause() { let sql = ''; - if (qb.from_array.length > 0) { + if (this.from_array.length > 0) { sql += " FROM "; } else { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); } - sql += qb.from_array.join(', '); + sql += this.from_array.join(', '); return sql; }; - const build_join_string = qb => { + _build_join_string() { let sql = ''; - sql += qb.join_array.join(' '); + sql += this.join_array.join(' '); if (sql.length > 0) sql = ' ' + sql; return sql; }; - const build_group_by_clause = qb => { - if (qb.group_by_array.length <= 0) return ''; + _build_group_by_clause() { + if (this.group_by_array.length <= 0) return ''; let sql = ' GROUP BY '; - sql += qb.group_by_array.join(', '); + sql += this.group_by_array.join(', '); return sql; }; - const build_having_clause = qb => { - if (qb.having_array.length <= 0) return ''; + _build_having_clause() { + if (this.having_array.length <= 0) return ''; let sql = ' HAVING '; - sql += qb.having_array.join(' '); + sql += this.having_array.join(' '); return sql; }; - const build_order_by_clause = qb => { - if (qb.order_by_array.length <= 0) return ''; + _build_order_by_clause() { + if (this.order_by_array.length <= 0) return ''; let sql = ' ORDER BY '; - sql += qb.order_by_array.join(', '); + sql += this.order_by_array.join(', '); return sql; }; - const build_limit_clause = (sql, limit, offset) => { + _build_limit_clause(sql, limit, offset) { if (!limit) return sql; sql += ' '; @@ -359,54 +377,54 @@ const QueryBuilder = function() { return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - const compile_select = qb => { - const distinct_clause = qb.distinct_clause[0] || ''; + _compile_select() { + const distinct_clause = this.distinct_clause[0] || ''; let sql = 'SELECT ' + distinct_clause; - if (qb.select_array.length === 0) { + if (this.select_array.length === 0) { sql += '*'; } else { - sql += qb.select_array.join(', '); + sql += this.select_array.join(', '); } - sql += build_from_clause(qb) - + build_join_string(qb) - + build_where_clause(qb) - + build_group_by_clause(qb) - + build_having_clause(qb) - + build_order_by_clause(qb); + sql += this._build_from_clause() + + this._build_join_string() + + this._build_where_clause() + + this._build_group_by_clause() + + this._build_having_clause() + + this._build_order_by_clause(); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - sql = build_limit_clause(sql,limit_to,offset_val); + sql = this._build_limit_clause(sql, limit_to, offset_val); return sql; }; - const compile_delete = qb => { - if (qb.from_array.length === 0) { + _compile_delete() { + if (this.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); return ''; } - qb.from_array = qb.from_array.slice(0,1); + this.from_array = this.from_array.slice(0,1); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - const sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); - return build_limit_clause(sql,limit_to,offset_val); + const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - const compile_update = qb => { + _compile_update() { const valstr = []; - for (let i in qb.set_array) { - const key = Object.keys(qb.set_array[i])[0]; - const val = qb.set_array[i][key]; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; valstr.push(key + ' = ' + val); } - if (qb.from_array.length !== 1) { - if (qb.from_array.length === 0) { + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; } @@ -414,24 +432,24 @@ const QueryBuilder = function() { return ''; } - const table = qb.from_array.toString(); + const table = this.from_array.toString(); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - let sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); - sql += build_where_clause(qb); - sql += build_order_by_clause(qb); - return build_limit_clause(sql, limit_to, offset_val); + let sql = `UPDATE (${table}) SET ${valstr.join(', ')}`; + sql += this._build_where_clause(); + sql += this._build_order_by_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - const compile_insert = (qb, ignore, suffix='') => { + _compile_insert(ignore, suffix='') { const keys = []; const values = []; - for (let i in qb.set_array) { - const key = Object.keys(qb.set_array[i])[0]; - const val = qb.set_array[i][key]; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; keys.push(key); values.push(val); @@ -439,10 +457,10 @@ const QueryBuilder = function() { const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - if (qb.from_array.length === 1) { - const table = qb.from_array.toString(); + if (this.from_array.length === 1) { + const table = this.from_array.toString(); } else { - if (qb.from_array.length === 0) { + if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build INSERT querty with!"); return ''; } @@ -450,1260 +468,1274 @@ const QueryBuilder = function() { return ''; } - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; }; - // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// - return { - where_array: [], - where_in_array: [], - from_array: [], - join_array: [], - select_array: [], - set_array: [], // has to be array to work as reference - order_by_array: [], - group_by_array: [], - having_array: [], - limit_to: [], // has to be array to work as reference - offset_val: [], // has to be array to work as reference - join_clause: [], - last_query_string: [], // has to be array to work as reference - distinct_clause: [], // has to be array to work as reference - aliased_tables: [], - - reset_query: function(new_last_query,debug) { - clear_array(this.where_array,debug); - clear_array(this.where_in_array); - clear_array(this.from_array); - clear_array(this.join_array); - clear_array(this.select_array); - clear_array(this.set_array); - clear_array(this.order_by_array); - clear_array(this.group_by_array); - clear_array(this.having_array); - clear_array(this.limit_to); - clear_array(this.offset_val); - clear_array(this.join_clause); - clear_array(this.distinct_clause); - clear_array(this.aliased_tables); - - clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - }, - - where: function(key, value = null, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); - } - - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - }, - - or_where: function(key, value=null, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); - } - return this._where(key, value, 'OR ', escape); - }, - - _where: function(key, value=null, type='AND ', escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } - } + reset_query(new_last_query,debug) { + this._clear_array(this.where_array, debug); + this._clear_array(this.where_in_array); + this._clear_array(this.from_array); + this._clear_array(this.join_array); + this._clear_array(this.select_array); + this._clear_array(this.set_array); + this._clear_array(this.order_by_array); + this._clear_array(this.group_by_array); + this._clear_array(this.having_array); + this._clear_array(this.limit_to); + this._clear_array(this.offset_val); + this._clear_array(this.join_clause); + this._clear_array(this.distinct_clause); + this._clear_array(this.aliased_tables); + + this._clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + } + + where(key, value = null, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + } + + or_where(key, value=null, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + } - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { - const filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - const that = this; - const parse_statement = (statement,joiner) => { - const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || /AND/i.test(joiner)) { - that.where(key, value, true); - } else { - that.or_where(key, value, true); - } + _where(key, value=null, type='AND ', escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { + const filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + const self = this; + const parse_statement = (statement, joiner) => { + const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || /AND/i.test(joiner)) { + self.where(key, value, true); + } else { + self.or_where(key, value, true); } - }; - parse_statement(filters.shift(),null); - while (filters.length > 0) { - const joiner = filters.shift(); - const statement = filters.shift(); - parse_statement(statement, joiner); } - return this; + }; + parse_statement(filters.shift(), null); + while (filters.length > 0) { + const joiner = filters.shift(); + const statement = filters.shift(); + parse_statement(statement, joiner); } + return this; } - - const key_array = {}; - key_array[key] = value; - key = key_array; - } - - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); } - for (let k in key) { - let v = key[k]; + const key_array = {}; + key_array[key] = value; + key = key_array; + } - if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { - return this._where_in(k,v,false,type,escape); - } + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } - const prefix = (this.where_array.length == 0 ? '' : type); + for (let k in key) { + let v = key[k]; - if (v === null && !has_operator(k)) { - k += ' IS NULL'; - } + if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { + return this._where_in(k, v, false, type, escape); + } - if (v !== null) { - if (escape === true) { - k = protect_identifiers(this,k,escape); - v = ' ' + qb_escape(this,v); - } + const prefix = (this.where_array.length == 0 ? '' : type); - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + qb_escape(this,v); - } + if (v === null && !this._has_operator(k)) { + k += ' IS NULL'; + } - if (!has_operator(k)) { - k += ' ='; - } - } - else { - k = protect_identifiers(this,k,escape); + if (v !== null) { + if (escape === true) { + k = this._protect_identifiers(k,escape); + v = ' ' + this._qb_escape(v); } - if (v) { - this.where_array.push(prefix+k+v); + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + this._qb_escape(v); } - else { - this.where_array.push(prefix+k); + + if (!this._has_operator(k)) { + k += ' ='; } } + else { + k = this._protect_identifiers(k,escape); + } - return this; - }, + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } - where_in: function(key, values, escape) { - return this._where_in(key,values,false,'AND ', escape); - }, + return this; + } - or_where_in: function(key, values, escape) { - return this._where_in(key,values,false,'OR ', escape); - }, + where_in(key, values, escape) { + return this._where_in(key, values, false, 'AND ', escape); + } - where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'AND ', escape); - }, + or_where_in(key, values, escape) { + return this._where_in(key, values, false, 'OR ', escape); + } - or_where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'OR ', escape); - }, + where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'AND ', escape); + } - _where_in: function(key='', values=[], not, type='AND ', escape) { - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); + or_where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'OR ', escape); + } - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } + _where_in(key='', values=[], not, type='AND ', escape) { + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); - // Values must be an array... - if (!Array.isArray(values)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } - for (let i in values) { - this.where_in_array.push(qb_escape(this,values[i])); + // Values must be an array... + if (!Array.isArray(values)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); } + } - const prefix = (this.where_array.length == 0 ? '' : type); - const where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); + for (let i in values) { + this.where_in_array.push(this._qb_escape(values[i])); + } - // reset the array for multiple calls - clear_array(this.where_in_array); - return this; - }, + const prefix = (this.where_array.length == 0 ? '' : type); + const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); - like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - }, + // reset the array for multiple calls + this._clear_array(this.where_in_array); + return this; + } - not_like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - }, + like(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + } - or_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - }, + not_like(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + } - or_not_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - }, + or_like(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + } - _like: function(field, match, type='AND ', side='both', not='') { - match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); + or_not_like(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + } - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if (!/^(string|object)$/.test(typeof field)) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } + _like(field, match, type='AND ', side='both', not='') { + match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); - if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if (!/^(string|object)$/.test(typeof field)) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } - const field_array = {}; - field_array[field] = match; - field = field_array; + if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } - for(let k in field) { - let like_statement; - const v = field[k]; - k = protect_identifiers(this,k.trim()); - - // Make sure value is only string, number, or boolean - if (!/^(string|number|boolean)$/.test(typeof v)) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } + const field_array = {}; + field_array[field] = match; + field = field_array; + } - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } + for (let k in field) { + let like_statement; + const v = field[k]; + k = this._protect_identifiers(k.trim()); - this._where(like_statement,null,type,false); + // Make sure value is only string, number, or boolean + if (!/^(string|number|boolean)$/.test(typeof v)) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } - return this; - }, - - from: function(from_param) { - if (!Array.isArray(from_param)) { - from_param = [from_param]; + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); } - for (let i in from_param) { - let val = from_param[i]; - if (typeof val !== 'string' || val.trim() === '') continue; + this._where(like_statement, null, type, false); + } - if (val.indexOf(',') !== -1) { - const objects = val.split(','); - for (let j in objects) { - const v = objects[j].trim(); + return this; + } - track_aliases(this,v); + from(from_param) { + if (!Array.isArray(from_param)) { + from_param = [from_param]; + } + for (let i in from_param) { + let val = from_param[i]; - this.from_array.push(protect_identifiers(this,v,true)); - } - } - else { - val = val.trim(); + if (typeof val !== 'string' || val.trim() === '') continue; - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - track_aliases(this,val); + if (val.indexOf(',') !== -1) { + const objects = val.split(','); + for (let j in objects) { + const v = objects[j].trim(); - this.from_array.push(protect_identifiers(this,val,true)); + this._track_aliases(v); + + this.from_array.push(this._protect_identifiers(v, true)); } } + else { + val = val.trim(); - return this; - }, + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + this._track_aliases(val); - join: function(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); + this.from_array.push(this._protect_identifiers(val, true)); } + } - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); + return this; + } - const valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + join(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); - direction += ' '; - } + const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - track_aliases(this,table); + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while(k = regex.exec(relation)) { - matches.push(k); - } + direction += ' '; + } - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); - new_relation += matches[j][0]; - } + this._track_aliases(table); - relation = ' ON ' + new_relation; + // Split multiple conditions + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; + if (escape === true && m) { + while (k = regex.exec(relation)) { + matches.push(k); } - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); - } - else if (!has_operator(relation)) { - relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; + let new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); + new_relation += matches[j][0]; } - // Do we want to escape the table name? - if (escape === true) { - table = protect_identifiers(this,table,true); - } + relation = ' ON ' + new_relation; + } - const join = direction + 'JOIN ' + table + relation; + // Split apart the condition and protect the identifiers + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); + } + else if (!this._has_operator(relation)) { + relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; + } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } + else { + relation = ' '; + } - this.join_array.push(join); - return this; - }, - - select: function(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Array.isArray(select)) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } + // Do we want to escape the table name? + if (escape === true) { + table = this._protect_identifiers(table,true); + } + + const join = direction + 'JOIN ' + table + relation; + + this.join_array.push(join); + return this; + } + + select(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + else if (Array.isArray(select)) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } - if (typeof escape !== 'boolean') escape = true; + if (typeof escape !== 'boolean') escape = true; - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - // Prevent it from trying to parse select statements with functions and if statements - if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - - // Identify individual statements within select string - let m, open_paren_index, inner_parenthesis; - const reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + // Prevent it from trying to parse select statements with functions and if statements + if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + + // Identify individual statements within select string + let m, open_paren_index, inner_parenthesis; + const reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; } } } - else { - select = [select]; - } + } + else { + select = [select]; } } + } - for (let i in select) { - const val = select[i].trim(); + for (let i in select) { + const val = select[i].trim(); - if (val !== '') { - this.select_array.push(protect_identifiers(this,val,escape)); - } + if (val !== '') { + this.select_array.push(this._protect_identifiers(val, escape)); } - return this; - }, - - select_min: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MIN'); - }, + } + return this; + } - select_max: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MAX'); - }, + select_min(select,alias) { + return this._min_max_avg_sum(select, alias, 'MIN'); + } - select_avg: function(select,alias) { - return this._min_max_avg_sum(select,alias,'AVG'); - }, + select_max(select,alias) { + return this._min_max_avg_sum(select, alias, 'MAX'); + } - select_sum: function(select,alias) { - return this._min_max_avg_sum(select,alias,'SUM'); - }, + select_avg(select,alias) { + return this._min_max_avg_sum(select, alias, 'AVG'); + } - _min_max_avg_sum: function(select='',alias='',type='MAX') { + select_sum(select,alias) { + return this._min_max_avg_sum(select, alias, 'SUM'); + } - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } + _min_max_avg_sum(select='', alias='', type='MAX') { - type = type.toUpperCase(); + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } - if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } + type = type.toUpperCase(); - if (alias == '') { - alias = create_aliases_from_table(select.trim()); - } + if (['MAX', 'MIN', 'AVG', 'SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } - const sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + if (alias == '') { + alias = this._create_aliases_from_table(select.trim()); + } - this.select_array.push(sql); + const sql = `${type}(${this._protect_identifiers(select.trim())}) AS ${alias}`; - return this; - }, + this.select_array.push(sql); - distinct: function(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + return this; + } - if (do_distinct) { - clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - clear_array(this.distinct_clause); - } - return this; - }, + distinct(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - group_by: function(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (!Array.isArray(by)) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } + if (do_distinct) { + this._clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + this._clear_array(this.distinct_clause); + } + return this; + } + group_by(by) { + if (typeof by === 'string') { + by = by.trim(); if (by.length <= 0) { throw new Error("You haven't provided any fields to group by!"); } + by = by.split(','); + } - for (let key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } + if (!Array.isArray(by)) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } - const val = by[key].trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } - if (val !== '') { - this.group_by_array.push(protect_identifiers(this,val)); - } + for (let key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); } - return this; - }, - having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - }, + const val = by[key].trim(); - or_having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - }, + if (val !== '') { + this.group_by_array.push(this._protect_identifiers(val)); + } + } + return this; + } - _having: function(key, value, type='AND ', escape) { + having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + } + + or_having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + } + + _having(key, value, type='AND ', escape) { - let m; - let key_array = {}; - const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - const key_is_array = Array.isArray(key); + let m; + let key_array = {}; + const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + const key_is_array = Array.isArray(key); - if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = this._extract_having_parts(key, key_array); + } } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (let i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); } else { - key = extract_having_parts(key,key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (let i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = extract_having_parts(key[i],key_array); - } + key_array = this._extract_having_parts(key[i], key_array); } - key = key_array; } + key = key_array; } } - else { - throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); - } - + } + else { + throw new Error(`Improper value supplied as the second parameter (${typeof value}) of the having() method.`); + } - for (let k in key) { - let v = key[k]; - const prefix = (this.having_array.length == 0 ? '' : type); - if (escape === true) { - k = protect_identifiers(this,k); - } + for (let k in key) { + let v = key[k]; + const prefix = (this.having_array.length == 0 ? '' : type); - if (v === null) { - k += ' IS'; - } - else if (!has_operator(k)) { - k += ' ='; - } + if (escape === true) { + k = this._protect_identifiers(k); + } - if (v != '') { - v = ' ' + qb_escape(this,v); - } + if (v === null) { + k += ' IS'; + } + else if (!this._has_operator(k)) { + k += ' ='; + } - this.having_array.push(prefix + k + v); + if (v != '') { + v = ' ' + this._qb_escape(v); } - return this; - }, + this.having_array.push(prefix + k + v); + } - order_by: function(orderby, direction) { - let m; - const rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + return this; + } + + order_by(orderby, direction) { + let m; + const rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(rand_word); + return this; + } + + // normalize orderby to be an array of items + if (!Array.isArray(orderby)) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { this.order_by_array.push(rand_word); return this; } - - // normalize orderby to be an array of items - if (!Array.isArray(orderby)) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } + else { + throw new Error("No field provided to order by!"); } + } - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } - for (let i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); + for (let i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.order_by_array.push(rand_word); + return this; + } + orderby[i] = {field: this._protect_identifiers(m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (/^(ASC|DESC)$/i.test(direction) || direction === '') { + orderby[i] = {field: this._protect_identifiers(orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; } else { - if (/^(ASC|DESC)$/i.test(direction) || direction === '') { - orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); } - return this; - }, + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } - limit: function(limit, offset) { - clear_array(this.limit_to); - this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); + return this; + } - if (offset !== undefined) { - return this.offset(offset); - } + limit(limit, offset) { + this._clear_array(this.limit_to); + this.limit_to.push(this._prepare_for_limit_and_offset(limit, 'limit')); - return this; - }, + if (offset !== undefined) { + return this.offset(offset); + } - offset: function(offset) { - clear_array(this.offset_val); - this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); - return this; - }, + return this; + } - set: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); + offset(offset) { + this._clear_array(this.offset_val); + this.offset_val.push(this._prepare_for_limit_and_offset(offset, 'offset')); + return this; + } - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) { - throw new Error("set(): Invalid field name provided!"); - } + set(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); - if (typeof value === 'undefined') - throw new Error("set(): First param was string but no value (second param) provided to set!"); + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) throw new Error("set(): Invalid field name provided!"); - const key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } + const key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } - // Add each key:value pair to the set_array - for (let i in key) { - let v = key[i]; - if (typeof v === 'undefined') continue; + // Add each key:value pair to the set_array + for (let i in key) { + let v = key[i]; + if (typeof v === 'undefined') continue; - if (v instanceof Date) v = v.toString(); + if (v instanceof Date) v = v.toString(); - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } - // Escape the key to be DRY - const escaped_key = protect_identifiers(this,i,escape); + // Escape the key to be DRY + const escaped_key = this._protect_identifiers(i,escape); - // Build a temporary object with escaped key and val - const temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = qb_escape(this,v); - } + // Build a temporary object with escaped key and val + const temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = this._qb_escape(v); + } - // Determine if this key has already been set - let found_index = null; - for (let j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } + // Determine if this key has already been set + let found_index = null; + for (let j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; } + } - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); } + } - //console.dir(this.set_array); + //console.dir(this.set_array); - return this; - }, + return this; + } - insert: function(table, set, ignore, suffix) { - table = table || '' - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + insert(table, set, ignore, suffix) { + return this._insert(table, set, ignore, suffix); + } - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } + _insert(table, set, ignore, suffix) { + table = table || '' + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (Array.isArray(set)) { - return this.insert_batch(table, set, ignore, suffix); - } + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } + if (Array.isArray(set)) { + return this.insert_batch(table, set, ignore, suffix); + } - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); } + } - table = table.trim(); + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } - if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } + table = table.trim(); - if (table === '') { - if (this.from_array.length == 0) { - throw new Error('insert(): No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - return compile_insert(this, ignore, suffix); - }, + if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } - insert_ignore: function(table, set, suffix) { - return this.insert(table, set, true, suffix); - }, + if (table === '') { + if (this.from_array.length == 0) throw new Error('insert(): No tables set to insert into!'); + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + return this._compile_insert(ignore, suffix); + } - insert_batch: function(table,set=null,ignore,suffix) { + insert_ignore(table, set, suffix) { + return this._insert_ignore(table, set, suffix); + } - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; + _insert_ignore(table, set, suffix) { + return this.insert(table, set, true, suffix); + } - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } + insert_batch(table,set=null,ignore,suffix) { + return this._insert_batch(table, set, ignore, suffix); + } - table = table.trim(); + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } - if (!Array.isArray(set)) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - for (let key in set) { - const row = set[key]; - const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (let i in row) { - const v = row[i]; + if (!Array.isArray(set)) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } + for (let key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (let i in row) { + const v = row[i]; + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); } } } + } - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } - const map = []; - const columns = []; + const map = []; + const columns = []; - // Obtain all the column names - for (let key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(protect_identifiers(this,key)); - } + // Obtain all the column names + for (let key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(this._protect_identifiers(key)); } } + } - for (let i = 0; i < set.length; i++) { - (i => { - const row = []; - for (let key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(qb_escape(this,set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + for (let i = 0; i < set.length; i++) { + (i => { + const row = []; + for (let key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(this._qb_escape(set[i][key])); } - map.push('(' + row.join(', ') + ')'); - })(i); - } + } + if (row.length != columns.length) { + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(', ') + ')'); + })(i); + } + + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + } - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; - }, + get(table) { + return this._get(table); + } - get: function(table) { - if (typeof table === 'string' || Array.isArray(table)) { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } + _get(table) { + if (typeof table === 'string' || Array.isArray(table)) { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); } - return compile_select(this); - }, + } + return this._compile_select(this); + } - get_where: function(table=null, where=null) { + get_where(table=null, where=null) { + return this._get_where(table, where); + } - // Check if table is either a string or array - if (typeof table !== 'string' && !Array.isArray(table)) - throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); + _get_where(table=null, where=null) { + // Check if table is either a string or array + if (typeof table !== 'string' && !Array.isArray(table)) + throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - // If table is a string, make sure it's not empty - if (typeof table === 'string' && table.trim().length <= 0) - throw new Error("Invalid table string specified!"); + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); - // If table is array, make sure there are only strings in there and that they are non-empty strings - if (Array.isArray(table)) { - for (let v in table) { - if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { - throw new Error("Invalid table string specified in array of tables!"); - break; - } + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (let v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; } } + } - this.from(table); + this.from(table); - if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - this.where(where); + this.where(where); - return compile_select(this); - }, + return this._compile_select(); + } - count: function(table) { - if (typeof table === 'string') { - this.from(table); - } + count(table) { + return this._count(table); + } - const sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') - + build_from_clause(this) - + build_join_string(this) - + build_where_clause(this); + _count(table) { + if (typeof table === 'string') { + this.from(table); + } + + const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') + + this._build_from_clause() + + this._build_join_string() + + this._build_where_clause(); + + return sql; + } - return sql; - }, + update(table, set, where=null) { + return this._update(table, set, where); + } - update: function(table, set, where=null) { + _update(table, set, where=null) { - table = table || ''; - set = set || null; + table = table || ''; + set = set || null; - // Send to batch_update if the data param is an array - if (Array.isArray(set)) { - let index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + // Send to batch_update if the data param is an array + if (Array.isArray(set)) { + let index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; } } - - // If set is a number, boolean, a non-empty string, or regex, fail - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); } + } - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } + // If set is a number, boolean, a non-empty string, or regex, fail + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); } + } - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } - table = table.trim(); + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } + table = table.trim(); - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - return compile_update(this); - }, + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } - update_batch: function(table='', set=null, index=null, where=null) { + return this._compile_update(); + } + update_batch(table='', set=null, index=null, where=null) { + return this._update_batch(table, set, index, where); + } - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } + _update_batch(table='', set=null, index=null, where=null) { + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } - // Check to make sure we have a dataset - if (!Array.isArray(set)) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } + // Check to make sure we have a dataset + if (!Array.isArray(set)) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (let i in set) { - const clean = {}; - const row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - const keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (let j in row) { - clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); - } - this.set_array.push(clean); + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (let i in set) { + const clean = {}; + const row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + const keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (let j in row) { + clean[this._protect_identifiers(j)] = this._qb_escape(row[j]); } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + this.set_array.push(clean); } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); } + } - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } - table = table.trim(); + table = table.trim(); - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } - // Verify there is a table in the from_array - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); + // Verify there is a table in the from_array + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } - table = this.from_array.toString(); + table = this.from_array.toString(); - // Limit to 100 rows per batch - const batches = []; - for (let i = 0, total = this.set_array.length; i < total; i += 100) { - const when_then = {}; - const ids = []; - const where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); - const chunk = this.set_array.slice(i,100); + // Limit to 100 rows per batch + const batches = []; + for (let i = 0, total = this.set_array.length; i < total; i += 100) { + const when_then = {}; + const ids = []; + const where = (this.where_array.length > 0 ? this._build_where_clause() + ' AND ' : ''); + const chunk = this.set_array.slice(i,100); - // Escape the index - index = protect_identifiers(this, index); + // Escape the index + index = this._protect_identifiers(index); - for (let j in chunk) { - ids.push(chunk[j][index]); + for (let j in chunk) { + ids.push(chunk[j][index]); - const keys = Object.keys(chunk[j]); - for (let k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + const keys = Object.keys(chunk[j]); + for (let k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; } + when_then[keys[k]].push(`WHEN ${index} = ${ids[j]} THEN ${chunk[j][keys[k]]} `); } } + } - // Build the actual SQL statement - let sql = 'UPDATE (' + table + ') SET '; - let cases = ''; - - for (let l in when_then) { - cases += l + ' = CASE '; + // Build the actual SQL statement + let sql = `UPDATE (${table}) SET `; + let cases = ''; - for (let m in when_then[l]) { - cases += when_then[l][m]; - } + for (let l in when_then) { + cases += l + ' = CASE '; - cases += 'ELSE ' + l + ' END, '; + for (let m in when_then[l]) { + cases += when_then[l][m]; } - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); + cases += `ELSE ${l} END, `; } - return batches; - }, + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - delete: function(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + // Add query to batch + batches.push(sql); + } - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } + return batches; + } + + delete(table, where) { + return this._delete(table, where); + } - return compile_delete(this); - }, + _delete(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } - get_compiled_select: function(table) { - if (typeof table !== 'undefined') { - track_aliases(this,table); - this.from(table); + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); } else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } + this.where(where); } + } - return compile_select(this); - }, + return this._compile_delete(); + } - get_compiled_delete: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); + get_compiled_select(table) { + if (typeof table !== 'undefined') { + this._track_aliases(table); + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; } + } - return compile_delete(this); - }, + return this._compile_select(); + } - get_compiled_update: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_update(this); - }, + get_compiled_delete(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } - get_compiled_insert: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_insert(this); - }, + return this._compile_delete(); + } + + get_compiled_update(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_update(); + } - compile_select: function(table) { - return this.get_compiled_select(table); - }, + get_compiled_insert(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_insert(); + } - compile_delete: function(table) { - return this.get_compiled_delete(table); - }, + compile_select(table) { + return this.get_compiled_select(table); + } - compile_update: function(table) { - return this.get_compiled_update(table); - }, + compile_delete(table) { + return this.get_compiled_delete(table); + } - compile_insert: function(table) { - return this.get_compiled_insert(table); - }, + compile_update(table) { + return this.get_compiled_update(table); + } - last_query: function() { - return this.last_query_string[0] || ''; - }, + compile_insert(table) { + return this.get_compiled_insert(table); + } - escape: function(val) { - return qb_escape(this, val); - }, + last_query() { + return this.last_query_string[0] || ''; + } - empty_table: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + escape(val) { + return this._qb_escape(val); + } - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } + empty_table(table) { + return this._empty_table(table); + } - return "DELETE FROM " + this.from_array[0]; - }, + _empty_table(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } - truncate: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } + return "DELETE FROM " + this.from_array[0]; + } + + truncate(table) { + return this._truncate(table); + } + + _truncate(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } - return "TRUNCATE " + this.from_array[0]; - }, + return "TRUNCATE " + this.from_array[0]; } -}; +} -exports.QueryBuilder = QueryBuilder; +module.exports = QueryBuilder; diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 452661a..855b740 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -1,14 +1,16 @@ +const QueryBuilder = require('./query_builder.js'); + // **************************************************************************** // QueryBuilder "Query Execution" methods. -// ----- -// @param Object qb The QueryBuilder object -// @param Object adapter The connection adapter object // **************************************************************************** -const QueryExec = function (qb, conn) { +class QueryExec extends QueryBuilder { + constructor() { + super(); + } - const exec = (sql, callback) => { - if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { - conn.query(sql, (err, results) => { + _exec(sql, cb) { + if (Object.prototype.toString.call(this._connection) == Object.prototype.toString.call({})) { + this._connection.query(sql, (err, results) => { // Standardize some important properties if (!err && results.length > 0) { @@ -28,181 +30,179 @@ const QueryExec = function (qb, conn) { } } - callback(err, results); + cb(err, results); }); } else { - throw new Error("No connection object supplied to the Query Exec Library!"); + throw new Error("No connection object available to the Query Exec Library!"); } - }; - - return { - query: function(sql, callback) { - exec(sql, callback); - }, + } - count: function(table, callback) { - if (typeof table === 'function' && typeof callback !== 'function') { - table = null; - callback = table; - } + query(sql, cb) { + this._exec(sql, cb); + } - const sql = qb.count(table); - qb.reset_query(sql); - exec(sql, (err, row) => { - if (!err) { - //console.dir(row[0].numrows); - callback(err, row[0].numrows); - } - else { - callback(err, row); - } - }); - }, + count(table, cb) { + if (typeof table === 'function' && typeof cb !== 'function') { + table = null; + cb = table; + } - get: function(table,callback,conn) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; + const sql = this._count(table); + this.reset_query(sql); + this._exec(sql, (err, row) => { + if (!err) { + //console.dir(row[0].numrows); + cb(err, row[0].numrows); } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); + else { + cb(err, row); } + }); + } - const sql = qb.get(table); - qb.reset_query(sql); - exec(sql,callback); - }, + get(table,cb,conn) { + // The table parameter is optional, it could be the cb... + if (typeof table === 'function' && typeof cb !== 'function') { + cb = table; + } + else if (typeof table === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your 'get' call!"); + } - get_where: function(table,where,callback) { - if (typeof table !== 'string' && !Array.isArray(table)) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); - } - if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); - } - const sql = qb.get_where(table,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert: function(table,set,callback,ignore,suffix) { - const sql = qb.insert(table,set,ignore,suffix); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_ignore: function(table,set,on_dupe,callback) { - if (typeof on_dupe === 'function') { - callback = on_dupe; - on_dupe = null; - } - const sql = qb.insert_ignore(table,set,on_dupe); - qb.reset_query(sql); - exec(sql,callback); - }, - - insert_batch: function(table,set,callback) { - const sql = qb.insert_batch(table,set); - qb.reset_query(sql); - exec(sql,callback); - }, - - update: function(table,set,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } + const sql = this._get(table); + this.reset_query(sql); + this._exec(sql,cb); + } - const sql = qb.update(table,set,where); - qb.reset_query(sql); - exec(sql,callback); - }, - - // TODO: Write this complicated-ass function - update_batch: function(table,set,index,where,callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } + get_where(table,where,cb) { + if (typeof table !== 'string' && !Array.isArray(table)) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } + const sql = this._get_where(table,where); + this.reset_query(sql); + this._exec(sql,cb); + } - const sqls = qb.update_batch(table,set,index,where); - const results = null; - const errors = []; - - // Execute each batch of (at least) 100 - (function next_batch() { - const sql = sqls.shift(); - qb.reset_query(sql); - - exec(sql, (err, res) => { - if (!err) { - if (null === results) { - results = res; - } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; - } - } else { - errors.push(err); - } + insert(table,set,cb,ignore,suffix) { + const sql = this._insert(table,set,ignore,suffix); + this.reset_query(sql); + this._exec(sql,cb); + } - if (sqls.length > 0) { - setTimeout(next_batch,0); + insert_ignore(table,set,on_dupe,cb) { + if (typeof on_dupe === 'function') { + cb = on_dupe; + on_dupe = null; + } + const sql = this._insert_ignore(table,set,on_dupe); + this.reset_query(sql); + this._exec(sql,cb); + } + + insert_batch(table,set,cb) { + const sql = this._insert_batch(table,set); + this.reset_query(sql); + this._exec(sql,cb); + } + + update(table,set,where,cb) { + // The where parameter is optional, it could be the cb... + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = null; + } + else if (typeof where === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sql = this._update(table,set,where); + this.reset_query(sql); + this._exec(sql,cb); + } + + // TODO: Write this complicated-ass function + update_batch(table,set,index,where,cb) { + // The where parameter is optional, it could be the cb... + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = null; + } + else if (typeof where === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sqls = this._update_batch(table,set,index,where); + const results = null; + const errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + const sql = sqls.shift(); + this.reset_query(sql); + + this._exec(sql, (err, res) => { + if (!err) { + if (null === results) { + results = res; } else { - return callback(errors, results); + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; } - }); - })(); - }, - - delete: function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } + } else { + errors.push(err); + } - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } + if (sqls.length > 0) { + setTimeout(next_batch,0); + } else { + return cb(errors, results); + } + }); + })(); + } - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } + delete(table, where, cb) { + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = undefined; + } + + if (typeof table === 'function' && typeof cb !== 'function') { + cb = table; + table = undefined; + where = undefined; + } - const sql = qb.delete(table, where); + if (typeof cb !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } - qb.reset_query(sql); - exec(sql,callback); - }, + const sql = this._delete(table, where); - empty_table: function(table, callback) { - const sql = qb.empty_table(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, + this.reset_query(sql); + this._exec(sql,cb); + } + + empty_table(table, cb) { + const sql = this._empty_table(table,cb); + this.reset_query(sql); + this._exec(sql,cb); + } - truncate: function(table, callback) { - const sql = qb.truncate(table,callback); - qb.reset_query(sql); - exec(sql,callback); - }, + truncate(table, cb) { + const sql = this._truncate(table,cb); + this.reset_query(sql); + this._exec(sql,cb); } } -exports.QueryExec = QueryExec; +module.exports = QueryExec; diff --git a/index.js b/index.js index cce7f24..098eb97 100755 --- a/index.js +++ b/index.js @@ -117,21 +117,27 @@ class QueryBuilder { // @return VOID This method responds asychronously via a callback // **************************************************************************** get_adapter() { - try { - const {Cluster,Pool,Single} = require(this.driver_info.path + 'adapters.js').Adapters; + const settings = Object.assign({}, this.settings); + + let Single; + try { switch (this.connection_type) { case 'cluster': - return new Cluster(this); + const Cluster = require(`${this.driver_info.path}/adapters/cluster.js`); + return new Cluster(settings); case 'pool': - return new Pool(this); + const Pool = require(`${this.driver_info.path}/adapters/pool.js`) + return new Pool(settings); case 'single': - return new Single(this, {}); + Single = require(`${this.driver_info.path}/adapters/single.js`) + return new Single(settings, {}); default: - return new Single(this, {}); + Single = require(`${this.driver_info.path}/adapters/single.js`) + return new Single(settings, {}); } } catch(e) { - throw new Error(`Couldn't load the Connection library for ${this.driver} (${JSON.stringify(this.settings)}): ${e}`); + throw new Error(`Couldn't load the "${this.connection_type}" Adapter library for ${this.driver} (${JSON.stringify(this.settings)}): ${e}`); } } } diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 538f710..686cf96 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -46,20 +46,38 @@ describe('QueryBuilder() - MySQL Adapter', () => { it('should be a function/class', () => { QueryBuilder.should.be.a('function'); }); - it('should have all the QueryBuilder methods', () => { + it('should have all the QueryBuilder properties', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); - const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; + const children = [ + 'where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array', + 'group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables', + ]; expect(qb).to.include.keys(children); }); + it('should have all the QueryBuilder methods', () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + const children = [ + 'reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like', + 'not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum', + '_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set', + ]; + children.forEach(v => { + expect(qb).to.respondTo(v); + }); + }); it('should have all the QueryExec methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; - expect(qb).to.include.keys(children); + children.forEach(v => { + expect(qb).to.respondTo(v); + }); }); it('should have all the miscellaneous methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); const children = ['last_query','escape','empty_table','truncate']; - expect(qb).to.include.keys(children); + children.forEach(v => { + expect(qb).to.respondTo(v); + }); }); it('should establish a single connection given valid connection credentials', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); @@ -104,7 +122,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { expect(e, 'should not get a connection').to.be.instanceof(Error); return; } - expect(qb, 'should have connect property').to.have.property('connect'); + expect(qb, 'should have connect method').to.respondTo('connect'); qb.connect(err => { check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); @@ -121,7 +139,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { expect(e, 'should not get a connection').to.be.instanceof(Error); return; } - expect(qb, 'should have connect property').to.have.property('connect'); + expect(qb, 'should have connect method').to.respondTo('connect'); qb.connect(err => { check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); @@ -138,7 +156,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { expect(e, 'should not get a connection').to.be.instanceof(Error); return; } - expect(qb, 'should have connect property').to.have.property('connect'); + expect(qb, 'should have connect method').to.respondTo('connect'); qb.connect(err => { check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); @@ -155,7 +173,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { expect(e, 'should not get a connection').to.be.instanceof(Error); return; } - expect(qb, 'should have connect property').to.have.property('connect'); + expect(qb, 'should have connect method').to.respondTo('connect'); qb.connect(err => { check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); @@ -170,7 +188,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { expect(e, 'should not get a connection').to.be.instanceof(Error); return; } - expect(qb, 'should have connect property').to.have.property('connect'); + expect(qb, 'should have connect method').to.respondTo('connect'); qb.connect(err => { check(done, () => { expect(err, 'should not be connected').to.be.instanceof(Error); @@ -254,8 +272,13 @@ describe('QueryBuilder() - MySQL Adapter', () => { }); it('should create a connection pool object if asked', () => { const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - expect(pool).to.be.instanceof.object; - expect(pool).to.include.keys(['pool','get_connection','disconnect']); + const methods = ['pool','get_connection','disconnect']; + + expect(pool, 'pool should be object').to.be.instanceof(Object); + methods.forEach(v => { + expect(pool, `pool should have method: ${v}`).to.respondTo(v); + }); + pool.pool.should.be.a('function'); pool.get_connection.should.be.a('function'); pool.disconnect.should.be.a('function'); diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js index 2e4d949..7e02e3a 100755 --- a/test/mysql/tests-compilation_methods.js +++ b/test/mysql/tests-compilation_methods.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('get_compiled_select()', () => { it('should exist', () => { diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js index d5d18a4..8731815 100755 --- a/test/mysql/tests-count.js +++ b/test/mysql/tests-count.js @@ -1,55 +1,56 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('count()', () => { - it('should exist', () => { - should.exist(qb.count); - }); - it('should be a function', () => { - qb.count.should.be.a('function'); - }); - it('should require that an item already exists in the from_array if one is not provided as the first parameter', () => { - qb.reset_query(); - expect(() => qb.count(), 'no tables supplied in chain').to.throw(Error); - expect(() => qb.from('galaxies').count(), 'table supplied by from()').to.not.throw(Error); - expect(() => qb.count('galaxies'), 'table supplied as first parameter').to.not.throw(Error); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - const sql = qb.count('galaxies'); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.count('galaxies'); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should create a properly-escaped SELECT query', () => { - qb.reset_query(); - const sql = qb.count('galaxies'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); - }); - it('should include WHERE statements', () => { - qb.reset_query(); - const sql = qb.where({type:'spiral'}).count('galaxies'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 'spiral'"); - }); - it('should work when table/view/procedure is provided earlier in chain but not in count() method', () => { - qb.reset_query(); - const sql = qb.from('galaxies').count(); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); - }); - it('should work with multiple tables/views/stored procedures', () => { - qb.reset_query(); - const sql = qb.from(['planets','galaxies']).count(); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `planets`, `galaxies`"); - }); - it('should include any joins that were added in the chain', () => { - qb.reset_query(); - const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); - }); + it('should exist', () => { + should.exist(qb.count); + }); + it('should be a function', () => { + qb.count.should.be.a('function'); + }); + it('should require that an item already exists in the from_array if one is not provided as the first parameter', () => { + qb.reset_query(); + expect(() => qb.count(), 'no tables supplied in chain').to.throw(Error); + expect(() => qb.from('galaxies').count(), 'table supplied by from()').to.not.throw(Error); + expect(() => qb.count('galaxies'), 'table supplied as first parameter').to.not.throw(Error); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should create a properly-escaped SELECT query', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); + }); + it('should include WHERE statements', () => { + qb.reset_query(); + const sql = qb.where({type:'spiral'}).count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies` WHERE `type` = 'spiral'"); + }); + it('should work when table/view/procedure is provided earlier in chain but not in count() method', () => { + qb.reset_query(); + const sql = qb.from('galaxies').count(); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `galaxies`"); + }); + it('should work with multiple tables/views/stored procedures', () => { + qb.reset_query(); + const sql = qb.from(['planets','galaxies']).count(); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `planets`, `galaxies`"); + }); + it('should include any joins that were added in the chain', () => { + qb.reset_query(); + const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + }); }); diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js index 2339874..4744e41 100755 --- a/test/mysql/tests-delete.js +++ b/test/mysql/tests-delete.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('delete()', () => { it('should exist', () => { diff --git a/test/mysql/tests-distinct.js b/test/mysql/tests-distinct.js index 233d7a6..0cb4afd 100755 --- a/test/mysql/tests-distinct.js +++ b/test/mysql/tests-distinct.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('distinct()', () => { it('should exist', () => { @@ -14,4 +15,4 @@ describe('distinct()', () => { qb.distinct(); qb.distinct_clause.should.eql(['DISTINCT ']); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-empty_table.js b/test/mysql/tests-empty_table.js index 5634c32..80895fa 100755 --- a/test/mysql/tests-empty_table.js +++ b/test/mysql/tests-empty_table.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('emtpy_table()', () => { it('should exist', () => { @@ -47,4 +48,4 @@ describe('emtpy_table()', () => { const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); sql.should.eql("DELETE FROM `galaxies`"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js index 9e8ddce..6f97d0e 100755 --- a/test/mysql/tests-from.js +++ b/test/mysql/tests-from.js @@ -1,5 +1,6 @@ const should = require('chai').should(); -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('from()', () => { it('should exist', () => { @@ -119,4 +120,4 @@ describe('from()', () => { qb.from(['','foobar']); qb.from_array.should.eql(['`foobar`']); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-general.js b/test/mysql/tests-general.js index b27380d..7c8ce90 100755 --- a/test/mysql/tests-general.js +++ b/test/mysql/tests-general.js @@ -2,7 +2,8 @@ const should = require('chai').should(); describe('QueryBuilder', () => { it('actually exists and can be initialized', () => { - const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); - qb.should.be.instanceOf(Object); + const QueryBuilder = require('../../drivers/mysql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js index 7493ec0..b8ba156 100755 --- a/test/mysql/tests-get.js +++ b/test/mysql/tests-get.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('get()', () => { it('should exist', () => { diff --git a/test/mysql/tests-groupby.js b/test/mysql/tests-groupby.js index 51be939..d26e68f 100755 --- a/test/mysql/tests-groupby.js +++ b/test/mysql/tests-groupby.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('group_by()', () => { it('should exist', () => { @@ -48,4 +49,4 @@ describe('group_by()', () => { expect(() => qb.group_by(['planet_type']), 'array of string(s) provided').to.not.throw(Error); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js index eac8240..1a4da12 100755 --- a/test/mysql/tests-having.js +++ b/test/mysql/tests-having.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('having()', () => { it('should exist', () => { @@ -108,4 +109,4 @@ describe('or_having()', () => { qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js index fce7afa..7fb93eb 100755 --- a/test/mysql/tests-insert.js +++ b/test/mysql/tests-insert.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); const test_data = {id:3, name:'Milky Way', type: 'spiral'}; const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js index 841c234..966ae59 100755 --- a/test/mysql/tests-insert_batch.js +++ b/test/mysql/tests-insert_batch.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; @@ -100,4 +101,4 @@ describe('insert_batch()', () => { const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js index f532bc5..ec659f3 100755 --- a/test/mysql/tests-join.js +++ b/test/mysql/tests-join.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('join()', () => { it('should exist', () => { diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js index 7aea272..c0d698d 100755 --- a/test/mysql/tests-like.js +++ b/test/mysql/tests-like.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('like()', () => { it('should exist', () => { diff --git a/test/mysql/tests-limit.js b/test/mysql/tests-limit.js index cf7ac1c..1616b35 100755 --- a/test/mysql/tests-limit.js +++ b/test/mysql/tests-limit.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('limit()', () => { it('should exist', () => { @@ -83,4 +84,4 @@ describe('limit()', () => { qb.limit_to.should.eql([100]); qb.offset_val.should.eql([30]); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-multiple-queries.js b/test/mysql/tests-multiple-queries.js new file mode 100755 index 0000000..0fcfb4f --- /dev/null +++ b/test/mysql/tests-multiple-queries.js @@ -0,0 +1,29 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306, + debug: false, +}; +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); + +describe('Multiple Queries', () => { + it('should not get confused about table after delete records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); diff --git a/test/mysql/tests-offset.js b/test/mysql/tests-offset.js index 4ca29d4..3857573 100755 --- a/test/mysql/tests-offset.js +++ b/test/mysql/tests-offset.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('offset()', () => { it('should exist', () => { @@ -56,4 +57,4 @@ describe('offset()', () => { qb.offset(10).offset(20).offset(100); qb.offset_val.should.eql([100]); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-orderby.js b/test/mysql/tests-orderby.js index 8ce91ef..470a4c2 100755 --- a/test/mysql/tests-orderby.js +++ b/test/mysql/tests-orderby.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('order_by()', () => { it('should exist', () => { @@ -127,4 +128,4 @@ describe('order_by()', () => { qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js index f9119b7..2fe8e96 100755 --- a/test/mysql/tests-select.js +++ b/test/mysql/tests-select.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('select()', () => { it('should exist', () => { diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js index a6d8e6e..eb70c76 100755 --- a/test/mysql/tests-set.js +++ b/test/mysql/tests-set.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('set()', () => { it('should exist', () => { diff --git a/test/mysql/tests-truncate.js b/test/mysql/tests-truncate.js index 02082aa..939abe5 100755 --- a/test/mysql/tests-truncate.js +++ b/test/mysql/tests-truncate.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('truncate()', () => { it('should exist', () => { @@ -21,4 +22,4 @@ describe('truncate()', () => { const sql = qb.truncate('galaxies'); sql.should.eql('TRUNCATE `galaxies`'); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js index 053547c..7992a80 100755 --- a/test/mysql/tests-update.js +++ b/test/mysql/tests-update.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = {name:'Milky Way', type: 'spiral'}; @@ -123,4 +124,4 @@ describe('update()', () => { const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); }); */ -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-update_batch.js b/test/mysql/tests-update_batch.js index 217aff9..b81d4c0 100755 --- a/test/mysql/tests-update_batch.js +++ b/test/mysql/tests-update_batch.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; @@ -17,4 +18,4 @@ describe('update_batch()', () => { const sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js index 41f5d11..c3447ef 100755 --- a/test/mysql/tests-where.js +++ b/test/mysql/tests-where.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('where()', () => { it('should exist', () => { @@ -170,4 +171,4 @@ describe('or_where()', () => { qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); }); -}); \ No newline at end of file +}); diff --git a/test/mysql/tests-where_in.js b/test/mysql/tests-where_in.js index 527dce7..1abe16f 100755 --- a/test/mysql/tests-where_in.js +++ b/test/mysql/tests-where_in.js @@ -1,6 +1,7 @@ const should = require('chai').should(); const expect = require('chai').expect; -const qb = require('../../drivers/mysql/query_builder.js').QueryBuilder(); +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); describe('where_in()', () => { it('should exist', () => { @@ -173,4 +174,4 @@ describe('or_where_not_in()', () => { qb.or_where_not_in('planet_position',[1,2,3],false); qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); }); -}); \ No newline at end of file +}); From 50efcdd069dd2559a53aff9ba1ede90e8a680810 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 14:17:48 -0400 Subject: [PATCH 147/200] Updated MS SQL driver files to be all in the form of ES6 classes and updated tests. --- Makefile | 2 +- drivers/mssql/adapter.js | 104 ++ drivers/mssql/adapters.js | 279 ---- drivers/mssql/adapters/cluster.js | 10 + drivers/mssql/adapters/pool.js | 51 + drivers/mssql/adapters/single.js | 64 + drivers/mssql/query_builder.js | 2164 +++++++++++++++-------------- drivers/mssql/query_exec.js | 327 ++--- drivers/mysql/adapter.js | 30 +- drivers/mysql/adapters/cluster.js | 4 +- test/mssql/tests-adapter.js | 29 +- test/mysql/tests-adapter.js | 2 +- 12 files changed, 1533 insertions(+), 1533 deletions(-) create mode 100755 drivers/mssql/adapter.js delete mode 100755 drivers/mssql/adapters.js create mode 100755 drivers/mssql/adapters/cluster.js create mode 100755 drivers/mssql/adapters/pool.js create mode 100755 drivers/mssql/adapters/single.js diff --git a/Makefile b/Makefile index ca0bff9..4e994ba 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = test/mysql/*.js +TESTS = test/mssql/*.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/mssql/adapter.js b/drivers/mssql/adapter.js new file mode 100755 index 0000000..e9b5d68 --- /dev/null +++ b/drivers/mssql/adapter.js @@ -0,0 +1,104 @@ +const QueryExec = require('./query_exec'); + +class Adapter extends QueryExec { + constructor(settings) { + super(settings); + + // Verify that an instance of Node QueryBuilder was passed in + if (!settings || typeof settings !== 'object') { + throw new Error("No connection settings provided to initialize QueryBuilder!"); + } + + this._original_settings = settings; + this._connection_settings = settings; + + // Enable debugging if necessary + this.debugging = false; + if (this._connection_settings.hasOwnProperty('debug') && this._connection_settings.debug === true) { + this.debugging = true; + delete this._connection_settings.debug; + } + + // Verify that required fields are provided... + if (Object.keys(this._connection_settings).length === 0) throw new Error("No connection information provided!"); + if (!this._connection_settings.hasOwnProperty('host')) this._connection_settings.host = 'localhost'; + if (!this._connection_settings.hasOwnProperty('user')) { console.log("Settings:", this._connection_settings); throw new Error("No user property provided. Hint: It can be NULL"); } + + this.map_connection_settings(); + } + + // **************************************************************************** + // Map generic NQB connection settings to mssql's format + // **************************************************************************** + map_connection_settings() { + const settings = Object.assign({}, this._connection_settings); + + this._connection_settings = { + server: settings.host, + userName: settings.user, + password: settings.password, + options: { + port: 1433, + encrypt: false, + rowCollectionOnRequestCompletion: true, + fallbackToDefaultDb: false, + debug: { + packet: this.debugging, + data: this.debugging, + payload: this.debugging, + token: this.debugging, + } + } + }; + + if (settings.hasOwnProperty('database')) { + this._connection_settings.options.database = settings.database; + delete settings.database; + } + if (settings.hasOwnProperty('port')) { + this._connection_settings.options.port = settings.port; + delete settings.port; + } + + // Remove mapped connection settings: + delete settings.host; + delete settings.user; + delete settings.password; + + // Set default pool settings + this.pool_settings = { + min: 10, + max: 10, + acquireTimeout: 60000, + log: this.debugging, + }; + + // Override default pool settings + if (settings.hasOwnProperty('pool_size')) { + this.pool_settings.max = settings.pool_size; + delete settings.pool_size; + } + if (settings.hasOwnProperty('pool_min')) { + this.pool_settings.min = settings.pool_min; + delete settings.pool_min; + } + if (settings.hasOwnProperty('acquireTimeout')) { + this.pool_settings.acquireTimeout = settings.acquireTimeout; + delete settings.acquireTimeout; + } + + + if (settings.hasOwnProperty('options') && typeof settings.options === 'object') { + let options = this._connection_settings.options; + options = Object.assign(options, settings.options); + options.debug = this._connection_settings.options.debug; + this._connection_settings.options = options; + delete settings.options; + } + + // Merge any additional driver-specific settings into connection settings + this._connection_settings = Object.assign(this._connection_settings, settings); + } +} + +module.exports = Adapter; diff --git a/drivers/mssql/adapters.js b/drivers/mssql/adapters.js deleted file mode 100755 index 61bbd50..0000000 --- a/drivers/mssql/adapters.js +++ /dev/null @@ -1,279 +0,0 @@ -const ConnectionPool = require('tedious-connection-pool'); -const Connection = require('tedious').Connection; -class Adapter { - constructor(nqb) { - // Verify that an instance of Node QueryBuilder was passed in - if (!nqb || typeof nqb !== 'object') { - throw new Error("No/Invalid QueryBuilder super object instance supplied."); - } - - // Store QB super object as class prop - this.nqb = Object.assign({}, nqb); - - // Verify setting property exists - if (!this.nqb.hasOwnProperty('settings')) { - throw new Error("No connection settings provided to initialize QueryBuilder!"); - } - - // Enable debugging if necessary - this.debugging = false; - if (this.nqb.settings.hasOwnProperty('debug') && this.nqb.settings.debug === true) { - this.debugging = true; - delete this.nqb.settings.debug; - } - - // Verify that required fields are provided... - if (Object.keys(this.nqb.settings).length === 0) throw new Error("No connection information provided!"); - if (!this.nqb.settings.hasOwnProperty('host')) this.nqb.settings.host = 'localhost'; - if (!this.nqb.settings.hasOwnProperty('user')) { console.log("Settings:", this.nqb.settings); throw new Error("No user property provided. Hint: It can be NULL"); } - - this.map_connection_settings(); - - } - - // **************************************************************************** - // Map generic NQB connection settings to mssql's format - // **************************************************************************** - map_connection_settings() { - const settings = Object.assign({}, this.nqb.settings); - - this._connection_settings = { - server: settings.host, - userName: settings.user, - password: settings.password, - options: { - port: 1433, - encrypt: false, - rowCollectionOnRequestCompletion: true, - fallbackToDefaultDb: false, - debug: { - packet: this.debugging, - data: this.debugging, - payload: this.debugging, - token: this.debugging, - } - } - }; - - if (settings.hasOwnProperty('database')) { - this._connection_settings.options.database = settings.database; - delete settings.database; - } - if (settings.hasOwnProperty('port')) { - this._connection_settings.options.port = settings.port; - delete settings.port; - } - - // Remove mapped connection settings: - delete settings.host; - delete settings.user; - delete settings.password; - - // Set default pool settings - this.pool_settings = { - min: 10, - max: 10, - acquireTimeout: 60000, - log: this.debugging, - }; - - // Override default pool settings - if (settings.hasOwnProperty('pool_size')) { - this.pool_settings.max = settings.pool_size; - delete settings.pool_size; - } - if (settings.hasOwnProperty('pool_min')) { - this.pool_settings.min = settings.pool_min; - delete settings.pool_min; - } - if (settings.hasOwnProperty('acquireTimeout')) { - this.pool_settings.acquireTimeout = settings.acquireTimeout; - delete settings.acquireTimeout; - } - - - if (settings.hasOwnProperty('options') && typeof settings.options === 'object') { - let options = this._connection_settings.options; - options = Object.assign(options, settings.options); - options.debug = this._connection_settings.options.debug; - this._connection_settings.options = options; - delete settings.options; - } - - // Merge any additional driver-specific settings into connection settings - this._connection_settings = Object.assign(this._connection_settings, settings); - } - - // **************************************************************************** - // Try to load the driver's query builder library and modify QueryBuilder object - // ----- - // @param Object qb The QueryBuilder object - // @return Object QueryBuilder object - // **************************************************************************** - get_query_builder() { - try { - return require('./query_builder.js').QueryBuilder(); - } catch(e) { - throw new Error(`Couldn't load the QueryBuilder library for ${this.nqb.driver}: ${e}`); - } - } - - // **************************************************************************** - // Get the the driver's QueryExec object so that queries can actually be - // executed by this library. - // ----- - // @param Object qb The QueryBuilder object - // @param Object conn The Connnection object - // @return Object QueryExec Object - // **************************************************************************** - get_query_exec(qb, conn) { - try { - return require('./query_exec.js').QueryExec(qb, conn); - } catch(e) { - throw new Error(`Couldn't load the QueryExec library for ${this.nqb.driver}: ${e}`); - } - } -} - - -// **************************************************************************** -// Generic Single Adapter -// ----- -// @return Object Single Adapter object -// **************************************************************************** -class Single extends Adapter { - constructor(nqb, settings) { - super(nqb); - - // Set defaults - this.pool = null; - this._connection = null; - - // If the Pool object is instatiating this Adapter, use it's connection - if (settings && settings.pool) { - this.pool = settings.pool.pool; - this._connection = settings.pool.connection; - } - // Otherwise, let's create a new connection - else { - const self = this; - function SQLConnection() {}; - SQLConnection.prototype.connect = function(callback) { - this.connection = new Connection(self._connection_settings); - this.connection.on('error', callback); - this.connection.on('connect', callback); - return this.connection; - } - this.sql_connection = new SQLConnection(); - } - - this.qb = this.get_query_builder(); - this.qe = this.get_query_exec(this.qb, this.sql_connection); - - const self = this; - - return Object.assign({ - connection_settings: function() { - return {connection_settings: self._connection_settings, pool_settings: self.pool_settings}; - }, - - connect: function(callback) { - if (self._connection) return self._connection; - self._connection = self.sql_connection.connect(callback); - }, - - connection: function() { - return self._connection; - }, - - escape: function(str) { - throw new Error("The `escape` method is not supported with the mssql driver!"); - }, - - escape_id: function(str) { - throw new Error("The `escape` method is not supported with the mssql driver!"); - }, - - disconnect: function(callback) { - if (self.pool) { - self.pool.drain(); - } else { - self._connection.close(); - } - if (callback && typeof callback === 'function') callback(null); - }, - - release: function() { - if (!self.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); - self.pool.release(self._connection); - } - }, self.qb, self.qe); - } -} - -// **************************************************************************** -// Connection Pool Adapter -// ----- -// @return Object Adapter object -// **************************************************************************** -class Pool extends Adapter { - constructor(nqb) { - super(nqb); - - // Create pool for node-querybuilder object if it doesn't already have one. - if (!this.nqb.hasOwnProperty('pool') || this.nqb.pool.length === 0) { - // Create connection Pool - const ps = Object.assign({}, this.pool_settings); - const cs = Object.assign({}, this._connection_settings); - this.nqb.pool = new ConnectionPool(ps, cs); - this.nqb.pool.on('error', err => { - if (this.debugging === true) console.error(err); - }); - } - - const self = this; - - return { - pool: function() { - return self.nqb.pool; - }, - get_connection: function(callback) { - if (self.nqb.pool === null) { - const error_msg = "Connection pool not available!"; - if (self.debugging === true) console.error(error_msg); - throw new Error(error_msg); - } - - self.nqb.pool.acquire((err, connection) => { - if (err) throw err; - const adapter = new Single(self.nqb, { - pool: { - pool: self.nqb.pool, - connection: connection, - } - }); - - callback(adapter); - }); - - }, - disconnect: function(callback) { - self.nqb.pool.drain(callback); - } - } - } -} - -// **************************************************************************** -// Clustered Connection Pool Adapter -// ----- -// @return Object Adapter object -// **************************************************************************** -class Cluster extends Adapter { - constructor(nqb) { - super(nqb); - return {}; - } -} - -exports.Adapters = {Single,Pool,Cluster}; diff --git a/drivers/mssql/adapters/cluster.js b/drivers/mssql/adapters/cluster.js new file mode 100755 index 0000000..53dc5cc --- /dev/null +++ b/drivers/mssql/adapters/cluster.js @@ -0,0 +1,10 @@ +const Adapter = require('../adapter.js'); + +class Cluster extends Adapter { + constructor(settings) { + super(settings); + return {}; + } +} + +module.exports = Cluster; diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js new file mode 100755 index 0000000..7aa5fff --- /dev/null +++ b/drivers/mssql/adapters/pool.js @@ -0,0 +1,51 @@ +// Load Tedious connection pool library +const ConnectionPool = require('tedious-connection-pool'); +const Adapter = require('../adapter.js'); +const Single = require('./single.js'); + +class Pool extends Adapter { + constructor(settings) { + super(settings); + + // Create pool for node-querybuilder object if it doesn't already have one. + if (!this.hasOwnProperty('pool') || this._pool.length === 0) { + // Create connection Pool + const ps = Object.assign({}, this.pool_settings); + const cs = Object.assign({}, this._connection_settings); + this._pool = new ConnectionPool(ps, cs); + this._pool.on('error', err => { + if (this.debugging === true) console.error(err); + }); + } + } + + pool() { + return this._pool; + } + + get_connection(cb) { + if (!this._pool) { + const error_msg = "Connection pool not available!"; + if (self.debugging === true) console.error(error_msg); + throw new Error(error_msg); + } + + this._pool.acquire((err, connection) => { + if (err) throw err; + const adapter = new Single(this._original_settings, { + pool: { + pool: this._pool, + connection: connection, + } + }); + + cb(adapter); + }); + } + + disconnect(cb) { + this._pool.drain(cb); + } +} + +module.exports = Pool; diff --git a/drivers/mssql/adapters/single.js b/drivers/mssql/adapters/single.js new file mode 100755 index 0000000..9976ce4 --- /dev/null +++ b/drivers/mssql/adapters/single.js @@ -0,0 +1,64 @@ +// Load Tedious connection library +const Connection = require('tedious').Connection; +const Adapter = require('../adapter.js'); + +class Single extends Adapter { + constructor(settings, pool) { + super(settings, pool); + + // Set defaults + this.pool = null; + this._connection = null; + + // If the Pool object is instatiating this Adapter, use it's connection + if (pool && pool.pool) { + this.pool = pool.pool.pool; // NOTE: That truely is insane looking... ¯\_(ツ)_/¯ + this._connection = pool.pool.connection; + } + // Otherwise, let's create a new connection + else { + const self = this; + function SQLConnection() {}; + SQLConnection.prototype.connect = function(cb) { + this.connection = new Connection(self._connection_settings); + this.connection.on('error', cb); + this.connection.on('connect', cb); + return this.connection; + } + this.sql_connection = new SQLConnection(); + } + } + + connection_settings() { + return {connection_settings: this._connection_settings, pool_settings: this.pool_settings}; + } + + connect(cb) { + if (this._connection) return this._connection; + this._connection = this.sql_connection.connect(cb); + } + + connection() { + return this._connection; + } + + escape_id(str) { + throw new Error("The `escape` method is not supported with the mssql driver!"); + } + + disconnect(callback) { + if (this.pool) { + this.pool.drain(); + } else { + this._connection.close(); + } + if (callback && typeof callback === 'function') callback(null); + } + + release() { + if (!this.pool) throw new Error("You cannot release a non-pooled connection from a connection pool!"); + this.pool.release(this._connection); + } +} + +module.exports = Single; diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index f10018d..9e4f212 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -1,7 +1,24 @@ -const QueryBuilder = function() { +class QueryBuilder { + constructor() { + this.where_array = []; + this.where_in_array = []; + this.from_array = []; + this.join_array = []; + this.select_array = []; + this.set_array = []; // has to be array to work as reference + this.order_by_array = []; + this.group_by_array = []; + this.having_array = []; + this.limit_to = []; // has to be array to work as reference + this.offset_val = []; // has to be array to work as reference + this.join_clause = []; + this.last_query_string = []; // has to be array to work as reference + this.distinct_clause = []; // has to be array to work as reference + this.aliased_tables = []; + } - // ------------------------------ GENERIC FUNCTIONS ------------------------------// - const array_values = item => { + // ------------------------------ GENERIC METHODS ------------------------------// + _array_values(item) { const keys = Object.keys(item); const length = keys.length; const values = Array(length); @@ -9,10 +26,9 @@ const QueryBuilder = function() { values[i] = item[keys[i]]; } return values; - }; - - const prepare_for_limit_and_offset = (item, type = 'limit') => { + } + _prepare_for_limit_and_offset(item, type = 'limit') { type = type.toLowerCase(); if (!/^(string|number)$/.test(typeof item)) { @@ -52,7 +68,7 @@ const QueryBuilder = function() { return item; } - const extract_having_parts = (key,key_array) => { + _extract_having_parts(key, key_array) { let m; key = key.trim().replace(/\s+/g,' '); const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting @@ -87,7 +103,7 @@ const QueryBuilder = function() { } // Simply setting all properties to [] causes reference issues in the parent class. - const clear_array = (a,debug) => { + _clear_array(a, debug) { if (debug === true) { console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); console.dir(a); @@ -108,14 +124,14 @@ const QueryBuilder = function() { console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); console.dir(a); } - }; + } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - const track_aliases = (qb,table) => { + _track_aliases(table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (let i in table) { const t = table[i]; - track_aliases(qb,t); + this._track_aliases(t); } return; } @@ -123,7 +139,7 @@ const QueryBuilder = function() { // Does the string contain a comma? If so, we need to separate // the string into discreet statements if (table.indexOf(',') !== -1) { - return track_aliases(qb,table.split(',')); + return this._track_aliases(table.split(',')); } // if a table alias is used we can recognize it by a space @@ -135,28 +151,28 @@ const QueryBuilder = function() { const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); // Store the alias, if it doesn't already exist - if (qb.aliased_tables.indexOf(alias) == -1) { - qb.aliased_tables.push(alias); + if (this.aliased_tables.indexOf(alias) == -1) { + this.aliased_tables.push(alias); } } - }; + } - const create_aliases_from_table = item => { + _create_aliases_from_table(item) { if (item.indexOf('.') !== -1) { return item.split('.').reverse()[0]; } return item; - }; + } - const escape_identifiers = (item = '*') => { + _escape_identifiers(item = '*') { if (item === '*') { return item; } if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (let i in item) { - item[i] = escape_identifiers(item[i]); + item[i] = this._escape_identifiers(item[i]); } return item; } @@ -176,7 +192,7 @@ const QueryBuilder = function() { return str.replace(/[`]+/g,'`'); }; - const protect_identifiers = (qb,item,protect_identifiers) => { + _protect_identifiers(item, protect_identifiers) { if (item === '') return item; protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); @@ -184,14 +200,16 @@ const QueryBuilder = function() { if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { const escaped_array = {}; - for (k in item) { + for (let k in item) { const v = item[k]; - escaped_array[protect_identifiers(qb,k)] = protect_identifiers(qb,v); + escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v); } - return escaped_array; } + // Make sure item is a string... + if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); + // Convert tabs or multiple spaces into single spaces item = item.replace(/\s+/g, ' '); @@ -203,10 +221,10 @@ const QueryBuilder = function() { let alias; if (has_alias >= 0) { alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = escape_identifiers(alias); + alias = this._escape_identifiers(alias); if (alias != '') alias = ' AS ' + alias; - item = item.substr(0,has_alias + 1); + item = item.substr(0, has_alias + 1); } else { alias = ''; } @@ -220,13 +238,13 @@ const QueryBuilder = function() { // Basically we remove everything to the right of the first space if (/\sAS\s/ig.test(item)) { const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - alias = (protect_identifiers ? item.substr(alias_index,4) + escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0,alias_index); + alias = (protect_identifiers ? item.substr(alias_index, 4) + this._escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); + item = item.substr(0, alias_index); } else if (item.indexOf(' ') !== -1) { const alias_index = item.indexOf(' '); - alias = (protect_identifiers && ! has_operator(item.substr(alias_index + 1)) ? ' ' + escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); item = item.substr(0,alias_index); } @@ -240,12 +258,12 @@ const QueryBuilder = function() { // Does the first segment of the exploded item match // one of the aliases previously identified? If so, // we have nothing more to do other than escape the item - if (qb.aliased_tables.indexOf(first_seg) !== -1) { + if (this.aliased_tables.indexOf(first_seg) !== -1) { if (protect_identifiers === true) { for (let key in parts) { const val = parts[key]; if (val !== '*') { - parts[key] = escape_identifiers(val); + parts[key] = this._escape_identifiers(val); } } @@ -255,19 +273,19 @@ const QueryBuilder = function() { } if (protect_identifiers === true) { - item = escape_identifiers(item); + item = this._escape_identifiers(item); } return item + alias; } if (protect_identifiers === true) { - item = escape_identifiers(item); + item = this._escape_identifiers(item); } return item + alias; }; - const has_operator = function (str) { + _has_operator(str) { if (typeof str === 'string' && str.length > 0) { const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); if (!match) { @@ -277,7 +295,9 @@ const QueryBuilder = function() { return true; }; - const qb_escape = (qb,str) => { + _qb_escape(str) { + const mysql = require('mysql'); + if (typeof str === 'boolean') { str = (str === false ? 0 : 1); } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { @@ -291,59 +311,59 @@ const QueryBuilder = function() { // ---------------------------- SQL BUILD TOOLS ----------------------------// - const build_where_clause = qb => { + _build_where_clause() { let sql = ''; - if (qb.where_array.length > 0) { + if (this.where_array.length > 0) { sql += " WHERE "; } - sql += qb.where_array.join(" "); + sql += this.where_array.join(" "); return sql; }; - const build_from_clause = qb => { + _build_from_clause() { let sql = ''; - if (qb.from_array.length > 0) { + if (this.from_array.length > 0) { sql += " FROM "; } else { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); } - sql += qb.from_array.join(', '); + sql += this.from_array.join(', '); return sql; }; - const build_join_string = qb => { + _build_join_string() { let sql = ''; - sql += qb.join_array.join(' '); + sql += this.join_array.join(' '); if (sql.length > 0) sql = ' ' + sql; return sql; }; - const build_group_by_clause = qb => { - if (qb.group_by_array.length <= 0) return ''; + _build_group_by_clause() { + if (this.group_by_array.length <= 0) return ''; let sql = ' GROUP BY '; - sql += qb.group_by_array.join(', '); + sql += this.group_by_array.join(', '); return sql; }; - const build_having_clause = qb => { - if (qb.having_array.length <= 0) return ''; + _build_having_clause() { + if (this.having_array.length <= 0) return ''; let sql = ' HAVING '; - sql += qb.having_array.join(' '); + sql += this.having_array.join(' '); return sql; }; - const build_order_by_clause = qb => { - if (qb.order_by_array.length <= 0) return ''; + _build_order_by_clause() { + if (this.order_by_array.length <= 0) return ''; let sql = ' ORDER BY '; - sql += qb.order_by_array.join(', '); + sql += this.order_by_array.join(', '); return sql; }; - const build_limit_clause = (sql, limit, offset) => { + _build_limit_clause(sql, limit, offset) { if (!limit) return sql; sql += ' '; @@ -357,54 +377,54 @@ const QueryBuilder = function() { return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - const compile_select = qb => { - const distinct_clause = qb.distinct_clause[0] || ''; + _compile_select() { + const distinct_clause = this.distinct_clause[0] || ''; let sql = 'SELECT ' + distinct_clause; - if (qb.select_array.length === 0) { + if (this.select_array.length === 0) { sql += '*'; } else { - sql += qb.select_array.join(', '); + sql += this.select_array.join(', '); } - sql += build_from_clause(qb) - + build_join_string(qb) - + build_where_clause(qb) - + build_group_by_clause(qb) - + build_having_clause(qb) - + build_order_by_clause(qb); + sql += this._build_from_clause() + + this._build_join_string() + + this._build_where_clause() + + this._build_group_by_clause() + + this._build_having_clause() + + this._build_order_by_clause(); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - sql = build_limit_clause(sql,limit_to,offset_val); + sql = this._build_limit_clause(sql, limit_to, offset_val); return sql; }; - const compile_delete = qb => { - if (qb.from_array.length === 0) { + _compile_delete() { + if (this.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); return ''; } - qb.from_array = qb.from_array.slice(0,1); + this.from_array = this.from_array.slice(0,1); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - const sql = 'DELETE' + build_from_clause(qb) + build_where_clause(qb); - return build_limit_clause(sql,limit_to,offset_val); + const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - const compile_update = qb => { + _compile_update() { const valstr = []; - for (let i in qb.set_array) { - const key = Object.keys(qb.set_array[i])[0]; - const val = qb.set_array[i][key]; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; valstr.push(key + ' = ' + val); } - if (qb.from_array.length !== 1) { - if (qb.from_array.length === 0) { + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build UPDATE query with!"); return ''; } @@ -412,24 +432,24 @@ const QueryBuilder = function() { return ''; } - const table = qb.from_array.toString(); + const table = this.from_array.toString(); - const limit_to = qb.limit_to[0] || false; - const offset_val = qb.offset_val[0] || false; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - let sql = 'UPDATE (' + table + ") SET " + valstr.join(', '); - sql += build_where_clause(qb); - sql += build_order_by_clause(qb); - return build_limit_clause(sql, limit_to, offset_val); + let sql = `UPDATE (${table}) SET ${valstr.join(', ')}`; + sql += this._build_where_clause(); + sql += this._build_order_by_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - const compile_insert = (qb, ignore, suffix='') => { + _compile_insert(ignore, suffix='') { const keys = []; const values = []; - for (let i in qb.set_array) { - const key = Object.keys(qb.set_array[i])[0]; - const val = qb.set_array[i][key]; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; keys.push(key); values.push(val); @@ -437,10 +457,10 @@ const QueryBuilder = function() { const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - if (qb.from_array.length === 1) { - const table = qb.from_array.toString(); + if (this.from_array.length === 1) { + const table = this.from_array.toString(); } else { - if (qb.from_array.length === 0) { + if (this.from_array.length === 0) { throw new Error("You haven't provided any tables to build INSERT querty with!"); return ''; } @@ -448,1260 +468,1274 @@ const QueryBuilder = function() { return ''; } - return verb + 'INTO ' + qb.from_array[0] + ' (' + keys.join(', ') + ') VALUES (' + values.join(', ') + ')' + suffix; + return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; }; - // ---------------------------- ACTUAL QUERY BUILDER ----------------------------// - return { - where_array: [], - where_in_array: [], - from_array: [], - join_array: [], - select_array: [], - set_array: [], // has to be array to work as reference - order_by_array: [], - group_by_array: [], - having_array: [], - limit_to: [], // has to be array to work as reference - offset_val: [], // has to be array to work as reference - join_clause: [], - last_query_string: [], // has to be array to work as reference - distinct_clause: [], // has to be array to work as reference - aliased_tables: [], - - reset_query: function(new_last_query,debug) { - clear_array(this.where_array,debug); - clear_array(this.where_in_array); - clear_array(this.from_array); - clear_array(this.join_array); - clear_array(this.select_array); - clear_array(this.set_array); - clear_array(this.order_by_array); - clear_array(this.group_by_array); - clear_array(this.having_array); - clear_array(this.limit_to); - clear_array(this.offset_val); - clear_array(this.join_clause); - clear_array(this.distinct_clause); - clear_array(this.aliased_tables); - - clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - }, - - where: function(key, value = null, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); - } - - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - }, - - or_where: function(key, value=null, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); - } - return this._where(key, value, 'OR ', escape); - }, - - _where: function(key, value=null, type='AND ', escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } - } + reset_query(new_last_query,debug) { + this._clear_array(this.where_array, debug); + this._clear_array(this.where_in_array); + this._clear_array(this.from_array); + this._clear_array(this.join_array); + this._clear_array(this.select_array); + this._clear_array(this.set_array); + this._clear_array(this.order_by_array); + this._clear_array(this.group_by_array); + this._clear_array(this.having_array); + this._clear_array(this.limit_to); + this._clear_array(this.offset_val); + this._clear_array(this.join_clause); + this._clear_array(this.distinct_clause); + this._clear_array(this.aliased_tables); + + this._clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + } + + where(key, value = null, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + } + + or_where(key, value=null, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + } + + _where(key, value=null, type='AND ', escape) { + escape = (typeof escape === 'boolean' ? escape : true); - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { - const filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - const that = this; - const parse_statement = (statement,joiner) => { - const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || /AND/i.test(joiner)) { - that.where(key, value, true); - } else { - that.or_where(key, value, true); - } + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { + const filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + const self = this; + const parse_statement = (statement, joiner) => { + const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || /AND/i.test(joiner)) { + self.where(key, value, true); + } else { + self.or_where(key, value, true); } - }; - parse_statement(filters.shift(),null); - while (filters.length > 0) { - const joiner = filters.shift(); - const statement = filters.shift(); - parse_statement(statement, joiner); } - return this; + }; + parse_statement(filters.shift(), null); + while (filters.length > 0) { + const joiner = filters.shift(); + const statement = filters.shift(); + parse_statement(statement, joiner); } + return this; } - - const key_array = {}; - key_array[key] = value; - key = key_array; } - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); - } - - for (let k in key) { - let v = key[k]; + const key_array = {}; + key_array[key] = value; + key = key_array; + } - if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { - return this._where_in(k,v,false,type,escape); - } + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } - const prefix = (this.where_array.length == 0 ? '' : type); + for (let k in key) { + let v = key[k]; - if (v === null && !has_operator(k)) { - k += ' IS NULL'; - } + if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { + return this._where_in(k, v, false, type, escape); + } - if (v !== null) { - if (escape === true) { - k = protect_identifiers(this,k,escape); - v = ' ' + qb_escape(this,v); - } + const prefix = (this.where_array.length == 0 ? '' : type); - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + qb_escape(this,v); - } + if (v === null && !this._has_operator(k)) { + k += ' IS NULL'; + } - if (!has_operator(k)) { - k += ' ='; - } - } - else { - k = protect_identifiers(this,k,escape); + if (v !== null) { + if (escape === true) { + k = this._protect_identifiers(k,escape); + v = ' ' + this._qb_escape(v); } - if (v) { - this.where_array.push(prefix+k+v); + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + this._qb_escape(v); } - else { - this.where_array.push(prefix+k); + + if (!this._has_operator(k)) { + k += ' ='; } } + else { + k = this._protect_identifiers(k,escape); + } - return this; - }, + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } - where_in: function(key, values, escape) { - return this._where_in(key,values,false,'AND ', escape); - }, + return this; + } - or_where_in: function(key, values, escape) { - return this._where_in(key,values,false,'OR ', escape); - }, + where_in(key, values, escape) { + return this._where_in(key, values, false, 'AND ', escape); + } - where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'AND ', escape); - }, + or_where_in(key, values, escape) { + return this._where_in(key, values, false, 'OR ', escape); + } - or_where_not_in: function(key, values, escape) { - return this._where_in(key,values,true,'OR ', escape); - }, + where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'AND ', escape); + } - _where_in: function(key='', values=[], not, type='AND ', escape) { - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); + or_where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'OR ', escape); + } - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } + _where_in(key='', values=[], not, type='AND ', escape) { + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); - // Values must be an array... - if (!Array.isArray(values)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } - for (let i in values) { - this.where_in_array.push(qb_escape(this,values[i])); + // Values must be an array... + if (!Array.isArray(values)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); } + } - const prefix = (this.where_array.length == 0 ? '' : type); - const where_in = prefix + protect_identifiers(this,key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); + for (let i in values) { + this.where_in_array.push(this._qb_escape(values[i])); + } - // reset the array for multiple calls - clear_array(this.where_in_array); - return this; - }, + const prefix = (this.where_array.length == 0 ? '' : type); + const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); - like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - }, + // reset the array for multiple calls + this._clear_array(this.where_in_array); + return this; + } - not_like: function(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - }, + like(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + } - or_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - }, + not_like(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + } - or_not_like: function(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - }, + or_like(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + } - _like: function(field, match, type='AND ', side='both', not='') { - match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); + or_not_like(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + } - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if (!/^(string|object)$/.test(typeof field)) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } + _like(field, match, type='AND ', side='both', not='') { + match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); - if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if (!/^(string|object)$/.test(typeof field)) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } - const field_array = {}; - field_array[field] = match; - field = field_array; + if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); } - for(let k in field) { - let like_statement; - const v = field[k]; - k = protect_identifiers(this,k.trim()); + const field_array = {}; + field_array[field] = match; + field = field_array; + } - // Make sure value is only string, number, or boolean - if (!/^(string|number|boolean)$/.test(typeof v)) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } + for (let k in field) { + let like_statement; + const v = field[k]; + k = this._protect_identifiers(k.trim()); - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } - - this._where(like_statement,null,type,false); + // Make sure value is only string, number, or boolean + if (!/^(string|number|boolean)$/.test(typeof v)) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } - return this; - }, - - from: function(from_param) { - if (!Array.isArray(from_param)) { - from_param = [from_param]; + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); } - for (let i in from_param) { - let val = from_param[i]; - if (typeof val !== 'string' || val.trim() === '') continue; + this._where(like_statement, null, type, false); + } - if (val.indexOf(',') !== -1) { - const objects = val.split(','); - for (let j in objects) { - const v = objects[j].trim(); + return this; + } - track_aliases(this,v); + from(from_param) { + if (!Array.isArray(from_param)) { + from_param = [from_param]; + } + for (let i in from_param) { + let val = from_param[i]; - this.from_array.push(protect_identifiers(this,v,true)); - } - } - else { - val = val.trim(); + if (typeof val !== 'string' || val.trim() === '') continue; - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - track_aliases(this,val); + if (val.indexOf(',') !== -1) { + const objects = val.split(','); + for (let j in objects) { + const v = objects[j].trim(); - this.from_array.push(protect_identifiers(this,val,true)); + this._track_aliases(v); + + this.from_array.push(this._protect_identifiers(v, true)); } } + else { + val = val.trim(); - return this; - }, + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + this._track_aliases(val); - join: function(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); + this.from_array.push(this._protect_identifiers(val, true)); } + } - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); + return this; + } - const valid_directions = ['LEFT','RIGHT','OUTER','INNER','LEFT OUTER','RIGHT OUTER']; + join(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); - direction += ' '; - } + const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - track_aliases(this,table); + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while(k = regex.exec(relation)) { - matches.push(k); - } + direction += ' '; + } - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? protect_identifiers(this,temp_match[1],escape) + temp_match[2] + protect_identifiers(this,temp_match[3],escape) : temp); - new_relation += matches[j][0]; - } + this._track_aliases(table); - relation = ' ON ' + new_relation; + // Split multiple conditions + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; + if (escape === true && m) { + while (k = regex.exec(relation)) { + matches.push(k); } - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + protect_identifiers(this,match[1],true) + match[2] + protect_identifiers(this,match[3],true); - } - else if (!has_operator(relation)) { - relation = ' USING (' + (escape ? escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; + let new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); + new_relation += matches[j][0]; } - // Do we want to escape the table name? - if (escape === true) { - table = protect_identifiers(this,table,true); - } + relation = ' ON ' + new_relation; + } - const join = direction + 'JOIN ' + table + relation; + // Split apart the condition and protect the identifiers + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); + } + else if (!this._has_operator(relation)) { + relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; + } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } + else { + relation = ' '; + } - this.join_array.push(join); - return this; - }, - - select: function(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Array.isArray(select)) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } + // Do we want to escape the table name? + if (escape === true) { + table = this._protect_identifiers(table,true); + } + + const join = direction + 'JOIN ' + table + relation; + + this.join_array.push(join); + return this; + } + + select(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + else if (Array.isArray(select)) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } - if (typeof escape !== 'boolean') escape = true; + if (typeof escape !== 'boolean') escape = true; - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - // Prevent it from trying to parse select statements with functions and if statements - if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - - // Identify individual statements within select string - let m, open_paren_index, inner_parenthesis; - const reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + // Prevent it from trying to parse select statements with functions and if statements + if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + + // Identify individual statements within select string + let m, open_paren_index, inner_parenthesis; + const reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; } } } - else { - select = [select]; - } + } + else { + select = [select]; } } + } - for (let i in select) { - const val = select[i].trim(); + for (let i in select) { + const val = select[i].trim(); - if (val !== '') { - this.select_array.push(protect_identifiers(this,val,escape)); - } + if (val !== '') { + this.select_array.push(this._protect_identifiers(val, escape)); } - return this; - }, - - select_min: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MIN'); - }, + } + return this; + } - select_max: function(select,alias) { - return this._min_max_avg_sum(select,alias,'MAX'); - }, + select_min(select,alias) { + return this._min_max_avg_sum(select, alias, 'MIN'); + } - select_avg: function(select,alias) { - return this._min_max_avg_sum(select,alias,'AVG'); - }, + select_max(select,alias) { + return this._min_max_avg_sum(select, alias, 'MAX'); + } - select_sum: function(select,alias) { - return this._min_max_avg_sum(select,alias,'SUM'); - }, + select_avg(select,alias) { + return this._min_max_avg_sum(select, alias, 'AVG'); + } - _min_max_avg_sum: function(select='',alias='',type='MAX') { + select_sum(select,alias) { + return this._min_max_avg_sum(select, alias, 'SUM'); + } - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } + _min_max_avg_sum(select='', alias='', type='MAX') { - type = type.toUpperCase(); + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } - if (['MAX','MIN','AVG','SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } + type = type.toUpperCase(); - if (alias == '') { - alias = create_aliases_from_table(select.trim()); - } + if (['MAX', 'MIN', 'AVG', 'SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } - const sql = type + '(' + protect_identifiers(this,select.trim()) + ') AS ' + alias; + if (alias == '') { + alias = this._create_aliases_from_table(select.trim()); + } - this.select_array.push(sql); + const sql = `${type}(${this._protect_identifiers(select.trim())}) AS ${alias}`; - return this; - }, + this.select_array.push(sql); - distinct: function(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + return this; + } - if (do_distinct) { - clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - clear_array(this.distinct_clause); - } - return this; - }, + distinct(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - group_by: function(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (!Array.isArray(by)) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } + if (do_distinct) { + this._clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + this._clear_array(this.distinct_clause); + } + return this; + } + group_by(by) { + if (typeof by === 'string') { + by = by.trim(); if (by.length <= 0) { throw new Error("You haven't provided any fields to group by!"); } + by = by.split(','); + } - for (let key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } + if (!Array.isArray(by)) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } - const val = by[key].trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } - if (val !== '') { - this.group_by_array.push(protect_identifiers(this,val)); - } + for (let key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); } - return this; - }, - having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - }, + const val = by[key].trim(); - or_having: function(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - }, + if (val !== '') { + this.group_by_array.push(this._protect_identifiers(val)); + } + } + return this; + } - _having: function(key, value, type='AND ', escape) { + having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + } + + or_having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + } + + _having(key, value, type='AND ', escape) { - let m; - let key_array = {}; - const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - const key_is_array = Array.isArray(key); + let m; + let key_array = {}; + const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + const key_is_array = Array.isArray(key); - if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = this._extract_having_parts(key, key_array); + } } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (let i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); } else { - key = extract_having_parts(key,key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (let i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = extract_having_parts(key[i],key_array); - } + key_array = this._extract_having_parts(key[i], key_array); } - key = key_array; } + key = key_array; } } - else { - throw new Error("Improper value supplied as the second parameter (" + typeof value + ") of the having() method."); - } - + } + else { + throw new Error(`Improper value supplied as the second parameter (${typeof value}) of the having() method.`); + } - for (let k in key) { - let v = key[k]; - const prefix = (this.having_array.length == 0 ? '' : type); - if (escape === true) { - k = protect_identifiers(this,k); - } + for (let k in key) { + let v = key[k]; + const prefix = (this.having_array.length == 0 ? '' : type); - if (v === null) { - k += ' IS'; - } - else if (!has_operator(k)) { - k += ' ='; - } + if (escape === true) { + k = this._protect_identifiers(k); + } - if (v != '') { - v = ' ' + qb_escape(this,v); - } + if (v === null) { + k += ' IS'; + } + else if (!this._has_operator(k)) { + k += ' ='; + } - this.having_array.push(prefix + k + v); + if (v != '') { + v = ' ' + this._qb_escape(v); } - return this; - }, + this.having_array.push(prefix + k + v); + } - order_by: function(orderby, direction) { - let m; - const rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + return this; + } + + order_by(orderby, direction) { + let m; + const rand_word = 'RAND()'; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(rand_word); + return this; + } + + // normalize orderby to be an array of items + if (!Array.isArray(orderby)) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { this.order_by_array.push(rand_word); return this; } - - // normalize orderby to be an array of items - if (!Array.isArray(orderby)) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } + else { + throw new Error("No field provided to order by!"); } + } - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } - for (let i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); + for (let i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: protect_identifiers(this,m[1].trim()), direction: m[2].trim().toUpperCase()}; + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { + if (m[2].trim() === 'RAND()') { + this.order_by_array.push(rand_word); + return this; + } + orderby[i] = {field: this._protect_identifiers(m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (/^(ASC|DESC)$/i.test(direction) || direction === '') { + orderby[i] = {field: this._protect_identifiers(orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; } else { - if (/^(ASC|DESC)$/i.test(direction) || direction === '') { - orderby[i] = {field: protect_identifiers(this,orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); } - return this; - }, + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } - limit: function(limit, offset) { - clear_array(this.limit_to); - this.limit_to.push(prepare_for_limit_and_offset(limit,'limit')); + return this; + } - if (offset !== undefined) { - return this.offset(offset); - } + limit(limit, offset) { + this._clear_array(this.limit_to); + this.limit_to.push(this._prepare_for_limit_and_offset(limit, 'limit')); - return this; - }, + if (offset !== undefined) { + return this.offset(offset); + } - offset: function(offset) { - clear_array(this.offset_val); - this.offset_val.push(prepare_for_limit_and_offset(offset,'offset')); - return this; - }, + return this; + } - set: function(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); + offset(offset) { + this._clear_array(this.offset_val); + this.offset_val.push(this._prepare_for_limit_and_offset(offset, 'offset')); + return this; + } - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) { - throw new Error("set(): Invalid field name provided!"); - } + set(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); - if (typeof value === 'undefined') - throw new Error("set(): First param was string but no value (second param) provided to set!"); + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) throw new Error("set(): Invalid field name provided!"); - const key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } + const key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } - // Add each key:value pair to the set_array - for (let i in key) { - let v = key[i]; - if (typeof v === 'undefined') continue; + // Add each key:value pair to the set_array + for (let i in key) { + let v = key[i]; + if (typeof v === 'undefined') continue; - if (v instanceof Date) v = v.toString(); + if (v instanceof Date) v = v.toString(); - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MS SQL!"); - } + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } - // Escape the key to be DRY - const escaped_key = protect_identifiers(this,i,escape); + // Escape the key to be DRY + const escaped_key = this._protect_identifiers(i,escape); - // Build a temporary object with escaped key and val - const temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = qb_escape(this,v); - } + // Build a temporary object with escaped key and val + const temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = this._qb_escape(v); + } - // Determine if this key has already been set - let found_index = null; - for (let j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } + // Determine if this key has already been set + let found_index = null; + for (let j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; } + } - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); } + } - //console.dir(this.set_array); + //console.dir(this.set_array); - return this; - }, + return this; + } - insert: function(table, set, ignore, suffix) { - table = table || '' - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + insert(table, set, ignore, suffix) { + return this._insert(table, set, ignore, suffix); + } - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } + _insert(table, set, ignore, suffix) { + table = table || '' + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (Array.isArray(set)) { - return this.insert_batch(table, set, ignore, suffix); - } + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } + if (Array.isArray(set)) { + return this.insert_batch(table, set, ignore, suffix); + } - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); } + } - table = table.trim(); + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } - if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } + table = table.trim(); - if (table === '') { - if (this.from_array.length == 0) { - throw new Error('insert(): No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } - return compile_insert(this, ignore, suffix); - }, + if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } - insert_ignore: function(table, set, suffix) { - return this.insert(table, set, true, suffix); - }, + if (table === '') { + if (this.from_array.length == 0) throw new Error('insert(): No tables set to insert into!'); + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + return this._compile_insert(ignore, suffix); + } - insert_batch: function(table,set=null,ignore,suffix) { + insert_ignore(table, set, suffix) { + return this._insert_ignore(table, set, suffix); + } - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; + _insert_ignore(table, set, suffix) { + return this.insert(table, set, true, suffix); + } - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } + insert_batch(table,set=null,ignore,suffix) { + return this._insert_batch(table, set, ignore, suffix); + } - table = table.trim(); + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } - if (!Array.isArray(set)) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - for (let key in set) { - const row = set[key]; - const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (let i in row) { - const v = row[i]; + if (!Array.isArray(set)) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MS SQL!"); - } + for (let key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (let i in row) { + const v = row[i]; + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); } } } + } - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } - const map = []; - const columns = []; + const map = []; + const columns = []; - // Obtain all the column names - for (let key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(protect_identifiers(this,key)); - } + // Obtain all the column names + for (let key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(this._protect_identifiers(key)); } } + } - for (let i = 0; i < set.length; i++) { - (i => { - const row = []; - for (let key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(qb_escape(this,set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + for (let i = 0; i < set.length; i++) { + (i => { + const row = []; + for (let key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(this._qb_escape(set[i][key])); } - map.push('(' + row.join(', ') + ')'); - })(i); - } + } + if (row.length != columns.length) { + throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + } + map.push('(' + row.join(', ') + ')'); + })(i); + } + + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + } - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + 'INTO ' + this.from_array[0] + ' (' + columns.join(', ') + ') VALUES ' + map.join(', ') + suffix; - }, + get(table) { + return this._get(table); + } - get: function(table) { - if (typeof table === 'string' || Array.isArray(table)) { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } + _get(table) { + if (typeof table === 'string' || Array.isArray(table)) { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); } - return compile_select(this); - }, + } + return this._compile_select(this); + } - get_where: function(table=null, where=null) { + get_where(table=null, where=null) { + return this._get_where(table, where); + } - // Check if table is either a string or array - if (typeof table !== 'string' && !Array.isArray(table)) - throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); + _get_where(table=null, where=null) { + // Check if table is either a string or array + if (typeof table !== 'string' && !Array.isArray(table)) + throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - // If table is a string, make sure it's not empty - if (typeof table === 'string' && table.trim().length <= 0) - throw new Error("Invalid table string specified!"); + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); - // If table is array, make sure there are only strings in there and that they are non-empty strings - if (Array.isArray(table)) { - for (let v in table) { - if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { - throw new Error("Invalid table string specified in array of tables!"); - break; - } + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (let v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; } } + } - this.from(table); + this.from(table); - if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - this.where(where); + this.where(where); - return compile_select(this); - }, + return this._compile_select(); + } - count: function(table) { - if (typeof table === 'string') { - this.from(table); - } + count(table) { + return this._count(table); + } - const sql = 'SELECT COUNT(*) AS ' + protect_identifiers(this,'numrows') - + build_from_clause(this) - + build_join_string(this) - + build_where_clause(this); + _count(table) { + if (typeof table === 'string') { + this.from(table); + } + + const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') + + this._build_from_clause() + + this._build_join_string() + + this._build_where_clause(); + + return sql; + } - return sql; - }, + update(table, set, where=null) { + return this._update(table, set, where); + } - update: function(table, set, where=null) { + _update(table, set, where=null) { - table = table || ''; - set = set || null; + table = table || ''; + set = set || null; - // Send to batch_update if the data param is an array - if (Array.isArray(set)) { - let index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + // Send to batch_update if the data param is an array + if (Array.isArray(set)) { + let index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; } } - - // If set is a number, boolean, a non-empty string, or regex, fail - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); } + } - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } + // If set is a number, boolean, a non-empty string, or regex, fail + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); } + } - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } - table = table.trim(); + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } + table = table.trim(); - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); - } + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - return compile_update(this); - }, + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } - update_batch: function(table='', set=null, index=null, where=null) { + return this._compile_update(); + } + update_batch(table='', set=null, index=null, where=null) { + return this._update_batch(table, set, index, where); + } - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } + _update_batch(table='', set=null, index=null, where=null) { + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } - // Check to make sure we have a dataset - if (!Array.isArray(set)) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } + // Check to make sure we have a dataset + if (!Array.isArray(set)) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (let i in set) { - const clean = {}; - const row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - const keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (let j in row) { - clean[protect_identifiers(this, j)] = qb_escape(this, row[j]); - } - this.set_array.push(clean); + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (let i in set) { + const clean = {}; + const row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + const keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (let j in row) { + clean[this._protect_identifiers(j)] = this._qb_escape(row[j]); } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + this.set_array.push(clean); } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); } + } - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } - table = table.trim(); + table = table.trim(); - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - clear_array(this.from_array); - this.from(table); + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } - // Verify there is a table in the from_array - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); + // Verify there is a table in the from_array + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } - table = this.from_array.toString(); + table = this.from_array.toString(); - // Limit to 100 rows per batch - const batches = []; - for (let i = 0, total = this.set_array.length; i < total; i += 100) { - const when_then = {}; - const ids = []; - const where = (this.where_array.length > 0 ? build_where_clause(this) + ' AND ' : ''); - const chunk = this.set_array.slice(i,100); + // Limit to 100 rows per batch + const batches = []; + for (let i = 0, total = this.set_array.length; i < total; i += 100) { + const when_then = {}; + const ids = []; + const where = (this.where_array.length > 0 ? this._build_where_clause() + ' AND ' : ''); + const chunk = this.set_array.slice(i,100); - // Escape the index - index = protect_identifiers(this, index); + // Escape the index + index = this._protect_identifiers(index); - for (let j in chunk) { - ids.push(chunk[j][index]); + for (let j in chunk) { + ids.push(chunk[j][index]); - const keys = Object.keys(chunk[j]); - for (let k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push('WHEN ' + index + ' = ' + ids[j] + ' THEN ' + chunk[j][keys[k]] + ' '); + const keys = Object.keys(chunk[j]); + for (let k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; } + when_then[keys[k]].push(`WHEN ${index} = ${ids[j]} THEN ${chunk[j][keys[k]]} `); } } + } - // Build the actual SQL statement - let sql = 'UPDATE (' + table + ') SET '; - let cases = ''; - - for (let l in when_then) { - cases += l + ' = CASE '; + // Build the actual SQL statement + let sql = `UPDATE (${table}) SET `; + let cases = ''; - for (let m in when_then[l]) { - cases += when_then[l][m]; - } + for (let l in when_then) { + cases += l + ' = CASE '; - cases += 'ELSE ' + l + ' END, '; + for (let m in when_then[l]) { + cases += when_then[l][m]; } - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); + cases += `ELSE ${l} END, `; } - return batches; - }, + sql += cases.substr(0, cases.length - 2); + sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - delete: function(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + // Add query to batch + batches.push(sql); + } - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } + return batches; + } + + delete(table, where) { + return this._delete(table, where); + } - return compile_delete(this); - }, + _delete(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } - get_compiled_select: function(table) { - if (typeof table !== 'undefined') { - track_aliases(this,table); - this.from(table); + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); } else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } + this.where(where); } + } - return compile_select(this); - }, + return this._compile_delete(); + } - get_compiled_delete: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); + get_compiled_select(table) { + if (typeof table !== 'undefined') { + this._track_aliases(table); + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; } + } - return compile_delete(this); - }, + return this._compile_select(); + } - get_compiled_update: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_update(this); - }, + get_compiled_delete(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } - get_compiled_insert: function(table) { - if (typeof table !== 'function') { - track_aliases(this,table); - this.from(table); - } - return compile_insert(this); - }, + return this._compile_delete(); + } + + get_compiled_update(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_update(); + } - compile_select: function(table) { - return this.get_compiled_select(table); - }, + get_compiled_insert(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_insert(); + } - compile_delete: function(table) { - return this.get_compiled_delete(table); - }, + compile_select(table) { + return this.get_compiled_select(table); + } - compile_update: function(table) { - return this.get_compiled_update(table); - }, + compile_delete(table) { + return this.get_compiled_delete(table); + } - compile_insert: function(table) { - return this.get_compiled_insert(table); - }, + compile_update(table) { + return this.get_compiled_update(table); + } - last_query: function() { - return this.last_query_string[0] || ''; - }, + compile_insert(table) { + return this.get_compiled_insert(table); + } - escape: function(val) { - return qb_escape(this, val); - }, + last_query() { + return this.last_query_string[0] || ''; + } - empty_table: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + escape(val) { + return this._qb_escape(val); + } - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } + empty_table(table) { + return this._empty_table(table); + } - return "DELETE FROM " + this.from_array[0]; - }, + _empty_table(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } - truncate: function(table) { - if (typeof table === 'string' && table.trim().length > 0) { - clear_array(this.from_array); - this.from(table); - } + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } + return "DELETE FROM " + this.from_array[0]; + } + + truncate(table) { + return this._truncate(table); + } + + _truncate(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } - return "TRUNCATE " + this.from_array[0]; - }, + return "TRUNCATE " + this.from_array[0]; } -}; +} -exports.QueryBuilder = QueryBuilder; +module.exports = QueryBuilder; diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index 6275d3d..99d709a 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -1,14 +1,16 @@ const Request = require('tedious').Request; +const QueryBuilder = require('./query_builder.js'); // **************************************************************************** // QueryBuilder "Query Execution" methods. -// ----- -// @param Object qb The QueryBuilder object -// @param Object adapter The connection adapter object // **************************************************************************** -const QueryExec = function (qb, conn) { - const exec = (sql, callback) => { - if (Object.prototype.toString.call(conn) == Object.prototype.toString.call({})) { +class QueryExec extends QueryBuilder { + constructor() { + super(); + } + + _exec(sql, cb) { + if (Object.prototype.toString.call(this._connection) == Object.prototype.toString.call({})) { //console.log("Connection: ", conn); const request = new Request(sql, (err, count, results) => { //console.log("Results:" , results); @@ -32,190 +34,189 @@ const QueryExec = function (qb, conn) { } } - callback(err, results); + cb(err, results); }); - if (!conn.connection) { - conn.connect(err => { - if (err) return callback(err, null); - conn.connection.execSql(request); + if (!this._connection) { + this._connection.connect(err => { + if (err) return cb(err, null); + this._connection.execSql(request); }); } else { - conn.connection.execSql(request); + //console.log("connection object2: ", this._connection); + this._connection.execSql(request); } } else { throw new Error("No connection object supplied to the Query Exec Library!"); } - }; - - return { - query: function(sql, callback) { - exec(sql, callback); - }, + } - count: function(table, callback) { - if (typeof table === 'function' && typeof callback !== 'function') { - table = null; - callback = table; - } + query(sql, cb) { + this._exec(sql, cb); + } - const sql = qb.count(table); - qb.reset_query(sql); - exec(sql, (err, row) => { - if (!err) { - //console.dir(row[0].numrows); - callback(err, row[0].numrows); - } - else { - callback(err, row); - } - }); - }, + count(table, cb) { + if (typeof table === 'function' && typeof cb !== 'function') { + table = null; + cb = table; + } - get: function(table, callback,conn) { - // The table parameter is optional, it could be the callback... - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; + const sql = this._count(table); + this.reset_query(sql); + this._exec(sql, (err, row) => { + if (!err) { + //console.dir(row[0].numrows); + cb(err, row[0].numrows); } - else if (typeof table === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your 'get' call!"); + else { + cb(err, row); } + }); + } - const sql = qb.get(table); - qb.reset_query(sql); - exec(sql, callback); - }, + get(table,cb,conn) { + // The table parameter is optional, it could be the cb... + if (typeof table === 'function' && typeof cb !== 'function') { + cb = table; + } + else if (typeof table === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your 'get' call!"); + } - get_where: function(table,where, callback) { - if (typeof table !== 'string' && !Array.isArray(table)) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); - } - if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); - } - const sql = qb.get_where(table,where); - qb.reset_query(sql); - exec(sql, callback); - }, - - insert: function(table,set, callback,ignore,suffix) { - const sql = qb.insert(table,set,ignore,suffix); - qb.reset_query(sql); - exec(sql, callback); - }, - - insert_ignore: function(table,set,on_dupe, callback) { - if (typeof on_dupe === 'function') { - callback = on_dupe; - on_dupe = null; - } - const sql = qb.insert_ignore(table,set,on_dupe); - qb.reset_query(sql); - exec(sql, callback); - }, - - insert_batch: function(table,set, callback) { - const sql = qb.insert_batch(table,set); - qb.reset_query(sql); - exec(sql, callback); - }, - - update: function(table,set,where, callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } + const sql = this._get(table); + this.reset_query(sql); + this._exec(sql,cb); + } - const sql = qb.update(table,set,where); - qb.reset_query(sql); - exec(sql, callback); - }, - - // TODO: Write this complicated-ass function - update_batch: function(table,set,index,where, callback) { - // The where parameter is optional, it could be the callback... - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = null; - } - else if (typeof where === 'undefined' && typeof callback !== 'function') { - throw new Error("No callback function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { - where = null; - } + get_where(table,where,cb) { + if (typeof table !== 'string' && !Array.isArray(table)) { + throw new Error("First parameter of get_where() must be a string or an array of strings."); + } + if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { + throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + } + const sql = this._get_where(table,where); + this.reset_query(sql); + this._exec(sql,cb); + } - const sqls = qb.update_batch(table,set,index,where); - const results = null; - const errors = []; - - // Execute each batch of (at least) 100 - (function next_batch() { - const sql = sqls.shift(); - qb.reset_query(sql); - - exec(sql, (err, res) => { - if (!err) { - if (null === results) { - results = res; - } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; - } - } else { - errors.push(err); - } + insert(table,set,cb,ignore,suffix) { + const sql = this._insert(table,set,ignore,suffix); + this.reset_query(sql); + this._exec(sql,cb); + } + + insert_ignore(table,set,on_dupe,cb) { + if (typeof on_dupe === 'function') { + cb = on_dupe; + on_dupe = null; + } + const sql = this._insert_ignore(table,set,on_dupe); + this.reset_query(sql); + this._exec(sql,cb); + } + + insert_batch(table,set,cb) { + const sql = this._insert_batch(table,set); + this.reset_query(sql); + this._exec(sql,cb); + } + + update(table,set,where,cb) { + // The where parameter is optional, it could be the cb... + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = null; + } + else if (typeof where === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your update call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sql = this._update(table,set,where); + this.reset_query(sql); + this._exec(sql,cb); + } + + // TODO: Write this complicated-ass function + update_batch(table,set,index,where,cb) { + // The where parameter is optional, it could be the cb... + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = null; + } + else if (typeof where === 'undefined' && typeof cb !== 'function') { + throw new Error("No cb function has been provided in your update_batch call!"); + } + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + where = null; + } + + const sqls = this._update_batch(table,set,index,where); + const results = null; + const errors = []; + + // Execute each batch of (at least) 100 + (function next_batch() { + const sql = sqls.shift(); + this.reset_query(sql); - if (sqls.length > 0) { - setTimeout(next_batch,0); + this._exec(sql, (err, res) => { + if (!err) { + if (null === results) { + results = res; } else { - return callback(errors, results); + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; } - }); - })(); - }, + } else { + errors.push(err); + } - delete: function(table, where, callback) { - if (typeof where === 'function' && typeof callback !== 'function') { - callback = where; - where = undefined; - } + if (sqls.length > 0) { + setTimeout(next_batch,0); + } else { + return cb(errors, results); + } + }); + })(); + } - if (typeof table === 'function' && typeof callback !== 'function') { - callback = table; - table = undefined; - where = undefined; - } + delete(table, where, cb) { + if (typeof where === 'function' && typeof cb !== 'function') { + cb = where; + where = undefined; + } - if (typeof callback !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } + if (typeof table === 'function' && typeof cb !== 'function') { + cb = table; + table = undefined; + where = undefined; + } - const sql = qb.delete(table, where); + if (typeof cb !== 'function') { + throw new Error("delete(): No callback function has been provided!"); + } - qb.reset_query(sql); - exec(sql, callback); - }, + const sql = this._delete(table, where); - empty_table: function(table, callback) { - const sql = qb.empty_table(table, callback); - qb.reset_query(sql); - exec(sql, callback); - }, + this.reset_query(sql); + this._exec(sql,cb); + } + + empty_table(table, cb) { + const sql = this._empty_table(table,cb); + this.reset_query(sql); + this._exec(sql,cb); + } - truncate: function(table, callback) { - const sql = qb.truncate(table, callback); - qb.reset_query(sql); - exec(sql, callback); - }, + truncate(table, cb) { + const sql = this._truncate(table,cb); + this.reset_query(sql); + this._exec(sql,cb); } } -exports.QueryExec = QueryExec; +module.exports = QueryExec; diff --git a/drivers/mysql/adapter.js b/drivers/mysql/adapter.js index 94c3504..70b68be 100755 --- a/drivers/mysql/adapter.js +++ b/drivers/mysql/adapter.js @@ -34,30 +34,30 @@ class Adapter extends QueryExec { // it's just good practice to go ahead and do this in case things change. // **************************************************************************** map_connection_settings() { - const nqb_settings = JSON.parse(JSON.stringify(this._connection_settings)); + const settings = JSON.parse(JSON.stringify(this._connection_settings)); this._connection_settings = { - host: nqb_settings.host, - user: nqb_settings.user, - password: nqb_settings.password, + host: settings.host, + user: settings.user, + password: settings.password, }; - if (nqb_settings.hasOwnProperty('database')) { - this._connection_settings.database = nqb_settings.database; - delete nqb_settings.database; + if (settings.hasOwnProperty('database')) { + this._connection_settings.database = settings.database; + delete settings.database; } - if (nqb_settings.hasOwnProperty('port')) { - this._connection_settings.port = nqb_settings.port; - delete nqb_settings.port; + if (settings.hasOwnProperty('port')) { + this._connection_settings.port = settings.port; + delete settings.port; } - + // Remove mapped settings: - delete nqb_settings.host; - delete nqb_settings.user; - delete nqb_settings.password; + delete settings.host; + delete settings.user; + delete settings.password; // Merge any driver-specific settings into connection settings - this._connection_settings = Object.assign(this._connection_settings, nqb_settings); + this._connection_settings = Object.assign(this._connection_settings, settings); } } diff --git a/drivers/mysql/adapters/cluster.js b/drivers/mysql/adapters/cluster.js index 0fba6cf..53dc5cc 100755 --- a/drivers/mysql/adapters/cluster.js +++ b/drivers/mysql/adapters/cluster.js @@ -1,8 +1,8 @@ const Adapter = require('../adapter.js'); class Cluster extends Adapter { - constructor(nqb) { - super(nqb); + constructor(settings) { + super(settings); return {}; } } diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js index bfab119..9e11ec2 100755 --- a/test/mssql/tests-adapter.js +++ b/test/mssql/tests-adapter.js @@ -69,18 +69,28 @@ describe('QueryBuilder() - MS SQL Adapter', () => { }); it('should have all the QueryBuilder methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); - const children = ['where_array','where_in_array','from_array','join_array','select_array','set_array','order_by_array','group_by_array','having_array','limit_to','offset_val','join_clause','last_query_string','distinct_clause','aliased_tables','reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like','not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum','_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set']; - expect(qb).to.include.keys(children); + const children = [ + 'reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like', + 'not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum', + '_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set', + ]; + children.forEach(v => { + expect(qb).to.respondTo(v); + }); }); it('should have all the QueryExec methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; - expect(qb).to.include.keys(children); + children.forEach(v => { + expect(qb).to.respondTo(v); + }); }); it('should have all the miscellaneous methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); const children = ['last_query','escape','empty_table','truncate']; - expect(qb).to.include.keys(children); + children.forEach(v => { + expect(qb).to.respondTo(v); + }); }); it('should establish a single connection given valid connection credentials', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); @@ -252,8 +262,13 @@ describe('QueryBuilder() - MS SQL Adapter', () => { }); it('should create a connection pool object if asked', () => { const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - expect(pool).to.be.instanceof.object; - expect(pool).to.include.keys(['pool','get_connection','disconnect']); + const methods = ['pool','get_connection','disconnect']; + + expect(pool, 'pool should be object').to.be.instanceof(Object); + methods.forEach(v => { + expect(pool, `pool should have method: ${v}`).to.respondTo(v); + }); + pool.pool.should.be.a('function'); pool.get_connection.should.be.a('function'); pool.disconnect.should.be.a('function'); @@ -263,7 +278,7 @@ describe('QueryBuilder() - MS SQL Adapter', () => { const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(qb => { check(done, () => { - expect(qb).to.include.keys(Object.keys(qb2)); + expect(qb2).to.include.keys(Object.keys(qb)); }); }); }); diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js index 686cf96..3a761d9 100755 --- a/test/mysql/tests-adapter.js +++ b/test/mysql/tests-adapter.js @@ -288,7 +288,7 @@ describe('QueryBuilder() - MySQL Adapter', () => { const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); pool.get_connection(qb => { check(done, () => { - expect(qb).to.include.keys(Object.keys(qb2)); + expect(qb2).to.include.keys(Object.keys(qb)); }); }); }); From 611498e51df0f14232f101195b9db299417d6cef Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 14:19:04 -0400 Subject: [PATCH 148/200] Some random mode changes. --- .gitignore | 0 .travis.yml | 0 HISTORY.md | 0 Makefile | 0 README.md | 0 drivers/drivers.json | 0 drivers/mssql/adapter.js | 0 drivers/mssql/adapters/cluster.js | 0 drivers/mssql/adapters/pool.js | 0 drivers/mssql/adapters/single.js | 0 drivers/mssql/query_builder.js | 0 drivers/mssql/query_exec.js | 0 drivers/mysql/adapter.js | 0 drivers/mysql/adapters/cluster.js | 0 drivers/mysql/adapters/pool.js | 0 drivers/mysql/adapters/single.js | 0 drivers/mysql/query_builder.js | 0 drivers/mysql/query_exec.js | 0 drivers/template/connect.js | 0 examples/connection_pool_example.js | 0 examples/connection_single_example.js | 0 index.js | 0 package.json | 0 test/create_mock_dbs.sh | 0 test/mssql/create_mssql_mock.sh | 0 test/mssql/mock_data.sql | 0 test/mssql/tests-adapter.js | 0 test/mysql/create_mysql_mock.sh | 0 test/mysql/mock_data.sql | 0 test/mysql/tests-adapter.js | 0 test/mysql/tests-compilation_methods.js | 0 test/mysql/tests-count.js | 0 test/mysql/tests-delete.js | 0 test/mysql/tests-distinct.js | 0 test/mysql/tests-empty_table.js | 0 test/mysql/tests-from.js | 0 test/mysql/tests-general.js | 0 test/mysql/tests-get.js | 0 test/mysql/tests-groupby.js | 0 test/mysql/tests-having.js | 0 test/mysql/tests-insert.js | 0 test/mysql/tests-insert_batch.js | 0 test/mysql/tests-join.js | 0 test/mysql/tests-like.js | 0 test/mysql/tests-limit.js | 0 test/mysql/tests-multiple-queries.js | 0 test/mysql/tests-offset.js | 0 test/mysql/tests-orderby.js | 0 test/mysql/tests-select.js | 0 test/mysql/tests-set.js | 0 test/mysql/tests-truncate.js | 0 test/mysql/tests-update.js | 0 test/mysql/tests-update_batch.js | 0 test/mysql/tests-where.js | 0 test/mysql/tests-where_in.js | 0 tests.md | 0 56 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 .gitignore mode change 100755 => 100644 .travis.yml mode change 100755 => 100644 HISTORY.md mode change 100755 => 100644 Makefile mode change 100755 => 100644 README.md mode change 100755 => 100644 drivers/drivers.json mode change 100755 => 100644 drivers/mssql/adapter.js mode change 100755 => 100644 drivers/mssql/adapters/cluster.js mode change 100755 => 100644 drivers/mssql/adapters/pool.js mode change 100755 => 100644 drivers/mssql/adapters/single.js mode change 100755 => 100644 drivers/mssql/query_builder.js mode change 100755 => 100644 drivers/mssql/query_exec.js mode change 100755 => 100644 drivers/mysql/adapter.js mode change 100755 => 100644 drivers/mysql/adapters/cluster.js mode change 100755 => 100644 drivers/mysql/adapters/pool.js mode change 100755 => 100644 drivers/mysql/adapters/single.js mode change 100755 => 100644 drivers/mysql/query_builder.js mode change 100755 => 100644 drivers/mysql/query_exec.js mode change 100755 => 100644 drivers/template/connect.js mode change 100755 => 100644 examples/connection_pool_example.js mode change 100755 => 100644 examples/connection_single_example.js mode change 100755 => 100644 index.js mode change 100755 => 100644 package.json mode change 100755 => 100644 test/create_mock_dbs.sh mode change 100755 => 100644 test/mssql/create_mssql_mock.sh mode change 100755 => 100644 test/mssql/mock_data.sql mode change 100755 => 100644 test/mssql/tests-adapter.js mode change 100755 => 100644 test/mysql/create_mysql_mock.sh mode change 100755 => 100644 test/mysql/mock_data.sql mode change 100755 => 100644 test/mysql/tests-adapter.js mode change 100755 => 100644 test/mysql/tests-compilation_methods.js mode change 100755 => 100644 test/mysql/tests-count.js mode change 100755 => 100644 test/mysql/tests-delete.js mode change 100755 => 100644 test/mysql/tests-distinct.js mode change 100755 => 100644 test/mysql/tests-empty_table.js mode change 100755 => 100644 test/mysql/tests-from.js mode change 100755 => 100644 test/mysql/tests-general.js mode change 100755 => 100644 test/mysql/tests-get.js mode change 100755 => 100644 test/mysql/tests-groupby.js mode change 100755 => 100644 test/mysql/tests-having.js mode change 100755 => 100644 test/mysql/tests-insert.js mode change 100755 => 100644 test/mysql/tests-insert_batch.js mode change 100755 => 100644 test/mysql/tests-join.js mode change 100755 => 100644 test/mysql/tests-like.js mode change 100755 => 100644 test/mysql/tests-limit.js mode change 100755 => 100644 test/mysql/tests-multiple-queries.js mode change 100755 => 100644 test/mysql/tests-offset.js mode change 100755 => 100644 test/mysql/tests-orderby.js mode change 100755 => 100644 test/mysql/tests-select.js mode change 100755 => 100644 test/mysql/tests-set.js mode change 100755 => 100644 test/mysql/tests-truncate.js mode change 100755 => 100644 test/mysql/tests-update.js mode change 100755 => 100644 test/mysql/tests-update_batch.js mode change 100755 => 100644 test/mysql/tests-where.js mode change 100755 => 100644 test/mysql/tests-where_in.js mode change 100755 => 100644 tests.md diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/.travis.yml b/.travis.yml old mode 100755 new mode 100644 diff --git a/HISTORY.md b/HISTORY.md old mode 100755 new mode 100644 diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/drivers/drivers.json b/drivers/drivers.json old mode 100755 new mode 100644 diff --git a/drivers/mssql/adapter.js b/drivers/mssql/adapter.js old mode 100755 new mode 100644 diff --git a/drivers/mssql/adapters/cluster.js b/drivers/mssql/adapters/cluster.js old mode 100755 new mode 100644 diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js old mode 100755 new mode 100644 diff --git a/drivers/mssql/adapters/single.js b/drivers/mssql/adapters/single.js old mode 100755 new mode 100644 diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js old mode 100755 new mode 100644 diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/adapter.js b/drivers/mysql/adapter.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/adapters/cluster.js b/drivers/mysql/adapters/cluster.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/adapters/single.js b/drivers/mysql/adapters/single.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js old mode 100755 new mode 100644 diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js old mode 100755 new mode 100644 diff --git a/drivers/template/connect.js b/drivers/template/connect.js old mode 100755 new mode 100644 diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js old mode 100755 new mode 100644 diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js old mode 100755 new mode 100644 diff --git a/index.js b/index.js old mode 100755 new mode 100644 diff --git a/package.json b/package.json old mode 100755 new mode 100644 diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh old mode 100755 new mode 100644 diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh old mode 100755 new mode 100644 diff --git a/test/mssql/mock_data.sql b/test/mssql/mock_data.sql old mode 100755 new mode 100644 diff --git a/test/mssql/tests-adapter.js b/test/mssql/tests-adapter.js old mode 100755 new mode 100644 diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh old mode 100755 new mode 100644 diff --git a/test/mysql/mock_data.sql b/test/mysql/mock_data.sql old mode 100755 new mode 100644 diff --git a/test/mysql/tests-adapter.js b/test/mysql/tests-adapter.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/tests-compilation_methods.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-count.js b/test/mysql/tests-count.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-delete.js b/test/mysql/tests-delete.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-distinct.js b/test/mysql/tests-distinct.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-empty_table.js b/test/mysql/tests-empty_table.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-from.js b/test/mysql/tests-from.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-general.js b/test/mysql/tests-general.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-get.js b/test/mysql/tests-get.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-groupby.js b/test/mysql/tests-groupby.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-having.js b/test/mysql/tests-having.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-insert.js b/test/mysql/tests-insert.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/tests-insert_batch.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-join.js b/test/mysql/tests-join.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-like.js b/test/mysql/tests-like.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-limit.js b/test/mysql/tests-limit.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-multiple-queries.js b/test/mysql/tests-multiple-queries.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-offset.js b/test/mysql/tests-offset.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-orderby.js b/test/mysql/tests-orderby.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-select.js b/test/mysql/tests-select.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-set.js b/test/mysql/tests-set.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-truncate.js b/test/mysql/tests-truncate.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-update.js b/test/mysql/tests-update.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-update_batch.js b/test/mysql/tests-update_batch.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-where.js b/test/mysql/tests-where.js old mode 100755 new mode 100644 diff --git a/test/mysql/tests-where_in.js b/test/mysql/tests-where_in.js old mode 100755 new mode 100644 diff --git a/tests.md b/tests.md old mode 100755 new mode 100644 From 8cf9b6ca0f29428975ff52d11703542258ef8865 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 14:31:36 -0400 Subject: [PATCH 149/200] Re-named MySQL tests so they'd actually run in order. --- Makefile | 2 +- ...ests-general.js => 00-AA-tests-general.js} | 18 +- .../{tests-adapter.js => 00-tests-adapter.js} | 0 ...tests-distinct.js => 01-tests-distinct.js} | 0 .../mysql/{tests-from.js => 01-tests-from.js} | 0 test/mysql/{tests-get.js => 01-tests-get.js} | 0 .../{tests-groupby.js => 01-tests-groupby.js} | 0 .../{tests-having.js => 01-tests-having.js} | 0 .../mysql/{tests-join.js => 01-tests-join.js} | 0 .../mysql/{tests-like.js => 01-tests-like.js} | 378 +++++++++--------- .../{tests-limit.js => 01-tests-limit.js} | 0 .../{tests-offset.js => 01-tests-offset.js} | 0 .../{tests-orderby.js => 01-tests-orderby.js} | 0 .../{tests-select.js => 01-tests-select.js} | 0 test/mysql/{tests-set.js => 01-tests-set.js} | 0 .../{tests-where.js => 01-tests-where.js} | 0 ...tests-where_in.js => 01-tests-where_in.js} | 0 ...ods.js => 02-tests-compilation_methods.js} | 0 .../{tests-count.js => 03-tests-count.js} | 0 .../{tests-delete.js => 03-tests-delete.js} | 0 ...empty_table.js => 03-tests-empty_table.js} | 0 .../{tests-insert.js => 03-tests-insert.js} | 0 ...sert_batch.js => 03-tests-insert_batch.js} | 0 ...tests-truncate.js => 03-tests-truncate.js} | 0 .../{tests-update.js => 03-tests-update.js} | 0 ...date_batch.js => 03-tests-update_batch.js} | 0 ...ueries.js => 04-tests-multiple-queries.js} | 58 +-- 27 files changed, 228 insertions(+), 228 deletions(-) rename test/mysql/{tests-general.js => 00-AA-tests-general.js} (96%) rename test/mysql/{tests-adapter.js => 00-tests-adapter.js} (100%) rename test/mysql/{tests-distinct.js => 01-tests-distinct.js} (100%) rename test/mysql/{tests-from.js => 01-tests-from.js} (100%) rename test/mysql/{tests-get.js => 01-tests-get.js} (100%) rename test/mysql/{tests-groupby.js => 01-tests-groupby.js} (100%) rename test/mysql/{tests-having.js => 01-tests-having.js} (100%) rename test/mysql/{tests-join.js => 01-tests-join.js} (100%) rename test/mysql/{tests-like.js => 01-tests-like.js} (98%) rename test/mysql/{tests-limit.js => 01-tests-limit.js} (100%) rename test/mysql/{tests-offset.js => 01-tests-offset.js} (100%) rename test/mysql/{tests-orderby.js => 01-tests-orderby.js} (100%) rename test/mysql/{tests-select.js => 01-tests-select.js} (100%) rename test/mysql/{tests-set.js => 01-tests-set.js} (100%) rename test/mysql/{tests-where.js => 01-tests-where.js} (100%) rename test/mysql/{tests-where_in.js => 01-tests-where_in.js} (100%) rename test/mysql/{tests-compilation_methods.js => 02-tests-compilation_methods.js} (100%) rename test/mysql/{tests-count.js => 03-tests-count.js} (100%) rename test/mysql/{tests-delete.js => 03-tests-delete.js} (100%) rename test/mysql/{tests-empty_table.js => 03-tests-empty_table.js} (100%) rename test/mysql/{tests-insert.js => 03-tests-insert.js} (100%) rename test/mysql/{tests-insert_batch.js => 03-tests-insert_batch.js} (100%) rename test/mysql/{tests-truncate.js => 03-tests-truncate.js} (100%) rename test/mysql/{tests-update.js => 03-tests-update.js} (100%) rename test/mysql/{tests-update_batch.js => 03-tests-update_batch.js} (100%) rename test/mysql/{tests-multiple-queries.js => 04-tests-multiple-queries.js} (97%) diff --git a/Makefile b/Makefile index 4e994ba..57a0da2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = test/mssql/*.js +TESTS = test/mysql/*.js test/mssql/*.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/test/mysql/tests-general.js b/test/mysql/00-AA-tests-general.js similarity index 96% rename from test/mysql/tests-general.js rename to test/mysql/00-AA-tests-general.js index 7c8ce90..a4fc2f3 100644 --- a/test/mysql/tests-general.js +++ b/test/mysql/00-AA-tests-general.js @@ -1,9 +1,9 @@ -const should = require('chai').should(); - -describe('QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mysql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); -}); +const should = require('chai').should(); + +describe('QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mysql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); diff --git a/test/mysql/tests-adapter.js b/test/mysql/00-tests-adapter.js similarity index 100% rename from test/mysql/tests-adapter.js rename to test/mysql/00-tests-adapter.js diff --git a/test/mysql/tests-distinct.js b/test/mysql/01-tests-distinct.js similarity index 100% rename from test/mysql/tests-distinct.js rename to test/mysql/01-tests-distinct.js diff --git a/test/mysql/tests-from.js b/test/mysql/01-tests-from.js similarity index 100% rename from test/mysql/tests-from.js rename to test/mysql/01-tests-from.js diff --git a/test/mysql/tests-get.js b/test/mysql/01-tests-get.js similarity index 100% rename from test/mysql/tests-get.js rename to test/mysql/01-tests-get.js diff --git a/test/mysql/tests-groupby.js b/test/mysql/01-tests-groupby.js similarity index 100% rename from test/mysql/tests-groupby.js rename to test/mysql/01-tests-groupby.js diff --git a/test/mysql/tests-having.js b/test/mysql/01-tests-having.js similarity index 100% rename from test/mysql/tests-having.js rename to test/mysql/01-tests-having.js diff --git a/test/mysql/tests-join.js b/test/mysql/01-tests-join.js similarity index 100% rename from test/mysql/tests-join.js rename to test/mysql/01-tests-join.js diff --git a/test/mysql/tests-like.js b/test/mysql/01-tests-like.js similarity index 98% rename from test/mysql/tests-like.js rename to test/mysql/01-tests-like.js index c0d698d..d2edaa4 100644 --- a/test/mysql/tests-like.js +++ b/test/mysql/01-tests-like.js @@ -1,189 +1,189 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const QueryBuilder = require('../../drivers/mysql/query_builder.js'); -const qb = new QueryBuilder(); - -describe('like()', () => { - it('should exist', () => { - should.exist(qb.like); - }); - it('should be a function', () => { - qb.like.should.be.a('function'); - }); - it('should require first parameter to be a valid string or object with key value pairs', () => { - expect(() => qb.like(), 'nothing provided').to.throw(Error); - expect(() => qb.like(null), 'null provided').to.throw(Error); - expect(() => qb.like(false), 'false provided').to.throw(Error); - expect(() => qb.like(true), 'true provided').to.throw(Error); - expect(() => qb.like({}), 'empty object provided').to.throw(Error); - expect(() => qb.like(3), 'integer provided').to.throw(Error); - expect(() => qb.like(3.5), 'float provided').to.throw(Error); - expect(() => qb.like([]), 'empty array provided').to.throw(Error); - expect(() => qb.like(''), 'empty string provided').to.throw(Error); - - expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', () => { - expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); - expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', () => { - expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); - expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - - expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); - expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); - expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); - expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { - expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - - expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); - }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'after'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'before'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'left'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'both'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - }); - it('should put AND in between multiple LIKE clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); -}); - -describe('or_like()', () => { - it('should exist', () => { - should.exist(qb.or_like); - }); - it('should be a function', () => { - qb.or_like.should.be.a('function'); - }); - it('should put OR in between multiple OR LIKE clauses', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky'); - qb.or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); - }); -}); - -describe('not_like()', () => { - it('should exist', () => { - should.exist(qb.not_like); - }); - it('should be a function', () => { - qb.not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put AND in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); -}); - -describe('or_not_like()', () => { - it('should exist', () => { - should.exist(qb.or_not_like); - }); - it('should be a function', () => { - qb.or_not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put OR in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); + + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); +}); + +describe('or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + }); +}); + +describe('not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); +}); + +describe('or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); +}); diff --git a/test/mysql/tests-limit.js b/test/mysql/01-tests-limit.js similarity index 100% rename from test/mysql/tests-limit.js rename to test/mysql/01-tests-limit.js diff --git a/test/mysql/tests-offset.js b/test/mysql/01-tests-offset.js similarity index 100% rename from test/mysql/tests-offset.js rename to test/mysql/01-tests-offset.js diff --git a/test/mysql/tests-orderby.js b/test/mysql/01-tests-orderby.js similarity index 100% rename from test/mysql/tests-orderby.js rename to test/mysql/01-tests-orderby.js diff --git a/test/mysql/tests-select.js b/test/mysql/01-tests-select.js similarity index 100% rename from test/mysql/tests-select.js rename to test/mysql/01-tests-select.js diff --git a/test/mysql/tests-set.js b/test/mysql/01-tests-set.js similarity index 100% rename from test/mysql/tests-set.js rename to test/mysql/01-tests-set.js diff --git a/test/mysql/tests-where.js b/test/mysql/01-tests-where.js similarity index 100% rename from test/mysql/tests-where.js rename to test/mysql/01-tests-where.js diff --git a/test/mysql/tests-where_in.js b/test/mysql/01-tests-where_in.js similarity index 100% rename from test/mysql/tests-where_in.js rename to test/mysql/01-tests-where_in.js diff --git a/test/mysql/tests-compilation_methods.js b/test/mysql/02-tests-compilation_methods.js similarity index 100% rename from test/mysql/tests-compilation_methods.js rename to test/mysql/02-tests-compilation_methods.js diff --git a/test/mysql/tests-count.js b/test/mysql/03-tests-count.js similarity index 100% rename from test/mysql/tests-count.js rename to test/mysql/03-tests-count.js diff --git a/test/mysql/tests-delete.js b/test/mysql/03-tests-delete.js similarity index 100% rename from test/mysql/tests-delete.js rename to test/mysql/03-tests-delete.js diff --git a/test/mysql/tests-empty_table.js b/test/mysql/03-tests-empty_table.js similarity index 100% rename from test/mysql/tests-empty_table.js rename to test/mysql/03-tests-empty_table.js diff --git a/test/mysql/tests-insert.js b/test/mysql/03-tests-insert.js similarity index 100% rename from test/mysql/tests-insert.js rename to test/mysql/03-tests-insert.js diff --git a/test/mysql/tests-insert_batch.js b/test/mysql/03-tests-insert_batch.js similarity index 100% rename from test/mysql/tests-insert_batch.js rename to test/mysql/03-tests-insert_batch.js diff --git a/test/mysql/tests-truncate.js b/test/mysql/03-tests-truncate.js similarity index 100% rename from test/mysql/tests-truncate.js rename to test/mysql/03-tests-truncate.js diff --git a/test/mysql/tests-update.js b/test/mysql/03-tests-update.js similarity index 100% rename from test/mysql/tests-update.js rename to test/mysql/03-tests-update.js diff --git a/test/mysql/tests-update_batch.js b/test/mysql/03-tests-update_batch.js similarity index 100% rename from test/mysql/tests-update_batch.js rename to test/mysql/03-tests-update_batch.js diff --git a/test/mysql/tests-multiple-queries.js b/test/mysql/04-tests-multiple-queries.js similarity index 97% rename from test/mysql/tests-multiple-queries.js rename to test/mysql/04-tests-multiple-queries.js index 0fcfb4f..67ac321 100644 --- a/test/mysql/tests-multiple-queries.js +++ b/test/mysql/04-tests-multiple-queries.js @@ -1,29 +1,29 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306, - debug: false, -}; -const QueryBuilder = require('../../index.js'); -const pool = new QueryBuilder(settings, 'mysql', 'pool'); - -describe('Multiple Queries', () => { - it('should not get confused about table after delete records', done => { - pool.get_connection(qb => { - qb.limit(1).delete('cities', (err, result) => { - qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { - qb.release(); - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - done(); - }); - }); - }); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306, + debug: false, +}; +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); + +describe('Multiple Queries', () => { + it('should not get confused about table after delete records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); From 6486bdb92607e607e8ffa849d3b2f5acc40d8d3d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 14:33:44 -0400 Subject: [PATCH 150/200] Renamed MSSQL tests and added general test. --- test/mssql/00-AA-tests-general.js | 9 +++++++++ test/mssql/{tests-adapter.js => 00-tests-adapter.js} | 0 2 files changed, 9 insertions(+) create mode 100644 test/mssql/00-AA-tests-general.js rename test/mssql/{tests-adapter.js => 00-tests-adapter.js} (100%) diff --git a/test/mssql/00-AA-tests-general.js b/test/mssql/00-AA-tests-general.js new file mode 100644 index 0000000..cd9a398 --- /dev/null +++ b/test/mssql/00-AA-tests-general.js @@ -0,0 +1,9 @@ +const should = require('chai').should(); + +describe('QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mssql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); diff --git a/test/mssql/tests-adapter.js b/test/mssql/00-tests-adapter.js similarity index 100% rename from test/mssql/tests-adapter.js rename to test/mssql/00-tests-adapter.js From a153c0e8fb307fbe0a58e684d8b288147034f439 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 14:38:01 -0400 Subject: [PATCH 151/200] Fixed mock db creation script. --- test/create_mock_dbs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index a8a7931..758ef5e 100644 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -./test/mysql/create_mysql_mock.sh -#./test/mysql/create_mssql_mock.sh +./mysql/create_mysql_mock.sh +#./mysql/create_mssql_mock.sh From ac9d427834a5e7c33dcaabd8724e4080f1a0068a Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 16:00:23 -0400 Subject: [PATCH 152/200] Added test for QB distinct. --- test/mssql/01-tests-distinct.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/mssql/01-tests-distinct.js diff --git a/test/mssql/01-tests-distinct.js b/test/mssql/01-tests-distinct.js new file mode 100644 index 0000000..423ea9f --- /dev/null +++ b/test/mssql/01-tests-distinct.js @@ -0,0 +1,18 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('distinct()', () => { + it('should exist', () => { + should.exist(qb.distinct); + }); + it('should be a function', () => { + qb.distinct.should.be.a('function'); + }); + it('should override the default distinct_clause with the "DISTINCT " keyword', () => { + qb.reset_query(); + qb.distinct(); + qb.distinct_clause.should.eql(['DISTINCT ']); + }); +}); From 74001aef23c843577ebcf8532a70b3f523785cdf Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 21 Jun 2018 17:05:22 -0400 Subject: [PATCH 153/200] Trying to make the full mock database creation script functional for Travis CI and non-Travis CI environments. --- test/mysql/create_mysql_mock.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 73f56c2..0501c13 100644 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -1,3 +1,5 @@ #!/usr/bin/env bash -mysql -e 'create database mock_db;' -mysql -utravis mock_db < ./test/mysql/mock_data.sql \ No newline at end of file +mysql -uroot -e "CREATE DATABSE IF NOT EXISTS mock_db;" +mysql -uroot -e "CREATE USER IF NOT EXISTS 'travis'@'localhost';" +mysql -uroot -e "GRANT ALL ON `mock_db`.* TO 'travis'@'localhost';" +mysql -utravis mock_db < ./test/mysql/mock_data.sql From 9015d8a29659aee9969c1791e1d54ed1c0ac6d3c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Jul 2018 17:03:34 -0400 Subject: [PATCH 154/200] Modified some tests? --- test/mysql/00-AA-tests-general.js | 18 +- test/mysql/01-tests-like.js | 378 ++++++++++++------------ test/mysql/04-tests-multiple-queries.js | 58 ++-- 3 files changed, 227 insertions(+), 227 deletions(-) mode change 100644 => 100755 test/mysql/00-AA-tests-general.js mode change 100644 => 100755 test/mysql/01-tests-like.js mode change 100644 => 100755 test/mysql/04-tests-multiple-queries.js diff --git a/test/mysql/00-AA-tests-general.js b/test/mysql/00-AA-tests-general.js old mode 100644 new mode 100755 index a4fc2f3..7c8ce90 --- a/test/mysql/00-AA-tests-general.js +++ b/test/mysql/00-AA-tests-general.js @@ -1,9 +1,9 @@ -const should = require('chai').should(); - -describe('QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mysql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); -}); +const should = require('chai').should(); + +describe('QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mysql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); diff --git a/test/mysql/01-tests-like.js b/test/mysql/01-tests-like.js old mode 100644 new mode 100755 index d2edaa4..c0d698d --- a/test/mysql/01-tests-like.js +++ b/test/mysql/01-tests-like.js @@ -1,189 +1,189 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const QueryBuilder = require('../../drivers/mysql/query_builder.js'); -const qb = new QueryBuilder(); - -describe('like()', () => { - it('should exist', () => { - should.exist(qb.like); - }); - it('should be a function', () => { - qb.like.should.be.a('function'); - }); - it('should require first parameter to be a valid string or object with key value pairs', () => { - expect(() => qb.like(), 'nothing provided').to.throw(Error); - expect(() => qb.like(null), 'null provided').to.throw(Error); - expect(() => qb.like(false), 'false provided').to.throw(Error); - expect(() => qb.like(true), 'true provided').to.throw(Error); - expect(() => qb.like({}), 'empty object provided').to.throw(Error); - expect(() => qb.like(3), 'integer provided').to.throw(Error); - expect(() => qb.like(3.5), 'float provided').to.throw(Error); - expect(() => qb.like([]), 'empty array provided').to.throw(Error); - expect(() => qb.like(''), 'empty string provided').to.throw(Error); - - expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', () => { - expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); - expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', () => { - expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); - expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - - expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); - expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); - expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); - expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { - expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - - expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); - }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'after'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'before'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'left'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'both'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - }); - it('should put AND in between multiple LIKE clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); -}); - -describe('or_like()', () => { - it('should exist', () => { - should.exist(qb.or_like); - }); - it('should be a function', () => { - qb.or_like.should.be.a('function'); - }); - it('should put OR in between multiple OR LIKE clauses', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky'); - qb.or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); - }); -}); - -describe('not_like()', () => { - it('should exist', () => { - should.exist(qb.not_like); - }); - it('should be a function', () => { - qb.not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put AND in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); -}); - -describe('or_not_like()', () => { - it('should exist', () => { - should.exist(qb.or_not_like); - }); - it('should be a function', () => { - qb.or_not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put OR in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); + + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); +}); + +describe('or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + }); +}); + +describe('not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); +}); + +describe('or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); +}); diff --git a/test/mysql/04-tests-multiple-queries.js b/test/mysql/04-tests-multiple-queries.js old mode 100644 new mode 100755 index 67ac321..0fcfb4f --- a/test/mysql/04-tests-multiple-queries.js +++ b/test/mysql/04-tests-multiple-queries.js @@ -1,29 +1,29 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306, - debug: false, -}; -const QueryBuilder = require('../../index.js'); -const pool = new QueryBuilder(settings, 'mysql', 'pool'); - -describe('Multiple Queries', () => { - it('should not get confused about table after delete records', done => { - pool.get_connection(qb => { - qb.limit(1).delete('cities', (err, result) => { - qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { - qb.release(); - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - done(); - }); - }); - }); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306, + debug: false, +}; +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); + +describe('Multiple Queries', () => { + it('should not get confused about table after delete records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); From 8f875660c737a65691b874605558faa7a0aa25d8 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 6 Jul 2018 17:05:17 -0400 Subject: [PATCH 155/200] These permissions changes are kinda making me crazy... --- .gitignore | 0 .travis.yml | 0 HISTORY.md | 0 Makefile | 0 README.md | 0 drivers/drivers.json | 0 drivers/mssql/adapter.js | 0 drivers/mssql/adapters/cluster.js | 0 drivers/mssql/adapters/pool.js | 0 drivers/mssql/adapters/single.js | 0 drivers/mssql/query_builder.js | 0 drivers/mssql/query_exec.js | 0 drivers/mysql/adapter.js | 0 drivers/mysql/adapters/cluster.js | 0 drivers/mysql/adapters/pool.js | 0 drivers/mysql/adapters/single.js | 0 drivers/mysql/query_builder.js | 0 drivers/mysql/query_exec.js | 0 drivers/template/connect.js | 0 examples/connection_pool_example.js | 0 examples/connection_single_example.js | 0 index.js | 0 package.json | 0 test/create_mock_dbs.sh | 0 test/mssql/00-AA-tests-general.js | 18 +++++++++--------- test/mssql/00-tests-adapter.js | 0 test/mssql/01-tests-distinct.js | 0 test/mssql/create_mssql_mock.sh | 0 test/mssql/mock_data.sql | 0 test/mysql/00-tests-adapter.js | 0 test/mysql/01-tests-distinct.js | 0 test/mysql/01-tests-from.js | 0 test/mysql/01-tests-get.js | 0 test/mysql/01-tests-groupby.js | 0 test/mysql/01-tests-having.js | 0 test/mysql/01-tests-join.js | 0 test/mysql/01-tests-limit.js | 0 test/mysql/01-tests-offset.js | 0 test/mysql/01-tests-orderby.js | 0 test/mysql/01-tests-select.js | 0 test/mysql/01-tests-set.js | 0 test/mysql/01-tests-where.js | 0 test/mysql/01-tests-where_in.js | 0 test/mysql/02-tests-compilation_methods.js | 0 test/mysql/03-tests-count.js | 0 test/mysql/03-tests-delete.js | 0 test/mysql/03-tests-empty_table.js | 0 test/mysql/03-tests-insert.js | 0 test/mysql/03-tests-insert_batch.js | 0 test/mysql/03-tests-truncate.js | 0 test/mysql/03-tests-update.js | 0 test/mysql/03-tests-update_batch.js | 0 test/mysql/create_mysql_mock.sh | 0 test/mysql/mock_data.sql | 0 tests.md | 0 55 files changed, 9 insertions(+), 9 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .travis.yml mode change 100644 => 100755 HISTORY.md mode change 100644 => 100755 Makefile mode change 100644 => 100755 README.md mode change 100644 => 100755 drivers/drivers.json mode change 100644 => 100755 drivers/mssql/adapter.js mode change 100644 => 100755 drivers/mssql/adapters/cluster.js mode change 100644 => 100755 drivers/mssql/adapters/pool.js mode change 100644 => 100755 drivers/mssql/adapters/single.js mode change 100644 => 100755 drivers/mssql/query_builder.js mode change 100644 => 100755 drivers/mssql/query_exec.js mode change 100644 => 100755 drivers/mysql/adapter.js mode change 100644 => 100755 drivers/mysql/adapters/cluster.js mode change 100644 => 100755 drivers/mysql/adapters/pool.js mode change 100644 => 100755 drivers/mysql/adapters/single.js mode change 100644 => 100755 drivers/mysql/query_builder.js mode change 100644 => 100755 drivers/mysql/query_exec.js mode change 100644 => 100755 drivers/template/connect.js mode change 100644 => 100755 examples/connection_pool_example.js mode change 100644 => 100755 examples/connection_single_example.js mode change 100644 => 100755 index.js mode change 100644 => 100755 package.json mode change 100644 => 100755 test/create_mock_dbs.sh mode change 100644 => 100755 test/mssql/00-AA-tests-general.js mode change 100644 => 100755 test/mssql/00-tests-adapter.js mode change 100644 => 100755 test/mssql/01-tests-distinct.js mode change 100644 => 100755 test/mssql/create_mssql_mock.sh mode change 100644 => 100755 test/mssql/mock_data.sql mode change 100644 => 100755 test/mysql/00-tests-adapter.js mode change 100644 => 100755 test/mysql/01-tests-distinct.js mode change 100644 => 100755 test/mysql/01-tests-from.js mode change 100644 => 100755 test/mysql/01-tests-get.js mode change 100644 => 100755 test/mysql/01-tests-groupby.js mode change 100644 => 100755 test/mysql/01-tests-having.js mode change 100644 => 100755 test/mysql/01-tests-join.js mode change 100644 => 100755 test/mysql/01-tests-limit.js mode change 100644 => 100755 test/mysql/01-tests-offset.js mode change 100644 => 100755 test/mysql/01-tests-orderby.js mode change 100644 => 100755 test/mysql/01-tests-select.js mode change 100644 => 100755 test/mysql/01-tests-set.js mode change 100644 => 100755 test/mysql/01-tests-where.js mode change 100644 => 100755 test/mysql/01-tests-where_in.js mode change 100644 => 100755 test/mysql/02-tests-compilation_methods.js mode change 100644 => 100755 test/mysql/03-tests-count.js mode change 100644 => 100755 test/mysql/03-tests-delete.js mode change 100644 => 100755 test/mysql/03-tests-empty_table.js mode change 100644 => 100755 test/mysql/03-tests-insert.js mode change 100644 => 100755 test/mysql/03-tests-insert_batch.js mode change 100644 => 100755 test/mysql/03-tests-truncate.js mode change 100644 => 100755 test/mysql/03-tests-update.js mode change 100644 => 100755 test/mysql/03-tests-update_batch.js mode change 100644 => 100755 test/mysql/create_mysql_mock.sh mode change 100644 => 100755 test/mysql/mock_data.sql mode change 100644 => 100755 tests.md diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 diff --git a/HISTORY.md b/HISTORY.md old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/drivers/drivers.json b/drivers/drivers.json old mode 100644 new mode 100755 diff --git a/drivers/mssql/adapter.js b/drivers/mssql/adapter.js old mode 100644 new mode 100755 diff --git a/drivers/mssql/adapters/cluster.js b/drivers/mssql/adapters/cluster.js old mode 100644 new mode 100755 diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js old mode 100644 new mode 100755 diff --git a/drivers/mssql/adapters/single.js b/drivers/mssql/adapters/single.js old mode 100644 new mode 100755 diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js old mode 100644 new mode 100755 diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/adapter.js b/drivers/mysql/adapter.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/adapters/cluster.js b/drivers/mysql/adapters/cluster.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/adapters/single.js b/drivers/mysql/adapters/single.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js old mode 100644 new mode 100755 diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js old mode 100644 new mode 100755 diff --git a/drivers/template/connect.js b/drivers/template/connect.js old mode 100644 new mode 100755 diff --git a/examples/connection_pool_example.js b/examples/connection_pool_example.js old mode 100644 new mode 100755 diff --git a/examples/connection_single_example.js b/examples/connection_single_example.js old mode 100644 new mode 100755 diff --git a/index.js b/index.js old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh old mode 100644 new mode 100755 diff --git a/test/mssql/00-AA-tests-general.js b/test/mssql/00-AA-tests-general.js old mode 100644 new mode 100755 index cd9a398..84de76b --- a/test/mssql/00-AA-tests-general.js +++ b/test/mssql/00-AA-tests-general.js @@ -1,9 +1,9 @@ -const should = require('chai').should(); - -describe('QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mssql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); -}); +const should = require('chai').should(); + +describe('QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mssql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); diff --git a/test/mssql/00-tests-adapter.js b/test/mssql/00-tests-adapter.js old mode 100644 new mode 100755 diff --git a/test/mssql/01-tests-distinct.js b/test/mssql/01-tests-distinct.js old mode 100644 new mode 100755 diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh old mode 100644 new mode 100755 diff --git a/test/mssql/mock_data.sql b/test/mssql/mock_data.sql old mode 100644 new mode 100755 diff --git a/test/mysql/00-tests-adapter.js b/test/mysql/00-tests-adapter.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-distinct.js b/test/mysql/01-tests-distinct.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-from.js b/test/mysql/01-tests-from.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-get.js b/test/mysql/01-tests-get.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-groupby.js b/test/mysql/01-tests-groupby.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-having.js b/test/mysql/01-tests-having.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-join.js b/test/mysql/01-tests-join.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-limit.js b/test/mysql/01-tests-limit.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-offset.js b/test/mysql/01-tests-offset.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-orderby.js b/test/mysql/01-tests-orderby.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-select.js b/test/mysql/01-tests-select.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-set.js b/test/mysql/01-tests-set.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-where.js b/test/mysql/01-tests-where.js old mode 100644 new mode 100755 diff --git a/test/mysql/01-tests-where_in.js b/test/mysql/01-tests-where_in.js old mode 100644 new mode 100755 diff --git a/test/mysql/02-tests-compilation_methods.js b/test/mysql/02-tests-compilation_methods.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-count.js b/test/mysql/03-tests-count.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-delete.js b/test/mysql/03-tests-delete.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-empty_table.js b/test/mysql/03-tests-empty_table.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-insert.js b/test/mysql/03-tests-insert.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-insert_batch.js b/test/mysql/03-tests-insert_batch.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-truncate.js b/test/mysql/03-tests-truncate.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-update.js b/test/mysql/03-tests-update.js old mode 100644 new mode 100755 diff --git a/test/mysql/03-tests-update_batch.js b/test/mysql/03-tests-update_batch.js old mode 100644 new mode 100755 diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh old mode 100644 new mode 100755 diff --git a/test/mysql/mock_data.sql b/test/mysql/mock_data.sql old mode 100644 new mode 100755 diff --git a/tests.md b/tests.md old mode 100644 new mode 100755 From d393c5faa53b8edb66c6b65084ba0513a3d6ff11 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 10 Jul 2018 13:47:39 -0400 Subject: [PATCH 156/200] Seperated out the common methods and properties of the query builder libraries into a generic QB lib that the driver-specific QBs extend. --- README.md | 96 +- drivers/mssql/query_builder.js | 1548 +++----------------------------- drivers/mssql/query_exec.js | 15 +- drivers/mysql/query_builder.js | 1543 +++---------------------------- drivers/query_builder.js | 1331 +++++++++++++++++++++++++++ test/mssql/01-tests-from.js | 123 +++ 6 files changed, 1739 insertions(+), 2917 deletions(-) create mode 100755 drivers/query_builder.js create mode 100755 test/mssql/01-tests-from.js diff --git a/README.md b/README.md index 445b7cf..bf73e53 100755 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ const qb = new require('node-querybuilder')({ ``` ## Choosing the Database Type -This part is super simple. Just pass which one you'd like to use as the second paramter to the constructor (`mysql` is the default): +This part is super simple. Just pass which one you'd like to use as the second parameter to the constructor (`mysql` is the default): **_Example:_** @@ -228,31 +228,31 @@ Chainable methods can be called as many times as you'd like in any order you lik API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo :------------------------------------ | :---------- | :------: | :------: | :------: | :------: | :------: | :------: -[select()](#select) | SELECT | ✓ | | | | | -[distinct()](#distinct) | DISTINCT | ✓ | | | | | -[select_min()](#min) | MIN | ✓ | | | | | -[select_max()](#max) | MAX | ✓ | | | | | -[select_avg()](#avg) | AVG | ✓ | | | | | -[select_sum()](#sum) | SUM | ✓ | | | | | -[from()](#from) | FROM | ✓ | | | | | -[join()](#join) | JOIN | ✓ | | | | | -[where()](#where) | WHERE | ✓ | | | | | -[where_in()](#where_in) | IN | ✓ | | | | | -[where_not_in()](#where_not_in) | WHERE | ✓ | | | | | -[or_where()](#or_where) | WHERE | ✓ | | | | | -[or_where_in()](#or_where_in) | WHERE | ✓ | | | | | -[or_where_not_in()](#or_where_not_in) | WHERE | ✓ | | | | | -[like()](#like) | LIKE | ✓ | | | | | -[or_like()](#or_like) | LIKE | ✓ | | | | | -[or_not_like()](#or_not_like) | LIKE | ✓ | | | | | -[not_like()](#not_like) | LIKE | ✓ | | | | | -[group_by()](#group-by) | GROUP BY | ✓ | | | | | -[having()](#having) | HAVING | ✓ | | | | | -[or_having()](#or_having) | HAVING | ✓ | | | | | -[order_by()](#order-by) | ORDER BY | ✓ | | | | | -[limit()](#limit) | LIMIT | ✓ | | | | | -[offset()](#offset) | OFFSET | ✓ | | | | | -[set()](#set) | SET | ✓ | | | | | +[select()](#select) | SELECT | ✓ | ✓ | | | | +[distinct()](#distinct) | DISTINCT | ✓ | ✓ | | | | +[select_min()](#min) | MIN | ✓ | ✓ | | | | +[select_max()](#max) | MAX | ✓ | ✓ | | | | +[select_avg()](#avg) | AVG | ✓ | ✓ | | | | +[select_sum()](#sum) | SUM | ✓ | ✓ | | | | +[from()](#from) | FROM | ✓ | ✓ | | | | +[join()](#join) | JOIN | ✓ | ✓ | | | | +[where()](#where) | WHERE | ✓ | ✓ | | | | +[where_in()](#where_in) | IN | ✓ | ✓ | | | | +[where_not_in()](#where_not_in) | WHERE | ✓ | ✓ | | | | +[or_where()](#or_where) | WHERE | ✓ | ✓ | | | | +[or_where_in()](#or_where_in) | WHERE | ✓ | ✓ | | | | +[or_where_not_in()](#or_where_not_in) | WHERE | ✓ | ✓ | | | | +[like()](#like) | LIKE | ✓ | ✓ | | | | +[or_like()](#or_like) | LIKE | ✓ | ✓ | | | | +[or_not_like()](#or_not_like) | LIKE | ✓ | ✓ | | | | +[not_like()](#not_like) | LIKE | ✓ | ✓ | | | | +[group_by()](#group-by) | GROUP BY | ✓ | ✓ | | | | +[having()](#having) | HAVING | ✓ | ✓ | | | | +[or_having()](#or_having) | HAVING | ✓ | ✓ | | | | +[order_by()](#order-by) | ORDER BY | ✓ | ✓ | | | | +[limit()](#limit) | LIMIT | ✓ | ✓ | | | | +[offset()](#offset) | OFFSET | ✓ | ✓ | | | | +[set()](#set) | SET | ✓ | ✓ | | | | -------------------------------------------------------------------------------- @@ -921,7 +921,7 @@ This SQL command is used to order the resultset by a field or fields in descendi Parameter | Type | Default | Description :-------- | :----------- | :------- | :---------------------------------------------------------------------- fields | String/Array | Required | Field name or an array of field names, possibly with directions as well -direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. +direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'newid'/'random'/'rand()': Random. This is a very flexible method, offering a wide variety of ways you can call it. Variations include: - Pass the field name and omit the direction @@ -940,6 +940,14 @@ Pass the field name and omit the direction qb.order_by('galaxy_name').get('galaxies', callback); ``` +Random sort + +```javascript +// (MySQL) SELECT * FROM `galaxies` ORDER BY RAND() ASC +// (MSSQL) SELECT * FROM `galaxies` ORDER BY NEWID() ASC +qb.order_by('random').get('galaxies', callback); +``` + Pass the field name and the direction as the first and second parameters, respectively ```javascript @@ -947,7 +955,7 @@ Pass the field name and the direction as the first and second parameters, respec qb.order_by('galaxy_name', 'desc').get('galaxies', callback); ``` -Pass an array of fields to first paramter, direction to second parameter +Pass an array of fields to first parameter, direction to second parameter ```javascript // SELECT * FROM `galaxies` ORDER BY `galaxy_name` DESC, `galaxy_size` DESC @@ -1052,20 +1060,20 @@ qb.set({birthday: birthday, anniversary: '2010-05-15'}).update('users', callback ## Execution Methods -API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo -:-------------------------------- | :------------ | :------: | :---: | :----: | :----: | :------: | :---: -[query()](#query) | N/A | ✓ | | | | | -[get()](#get) | N/A | ✓ | | | | | -[get_where()](#get_where) | N/A | ✓ | | | | | -[count()](#count) | COUNT | ✓ | | | | | -[update()](#update) | UPDATE | ✓ | | | | | -[update_batch()](#update_batch) | N/A | ✓ | | | | | -[insert()](#insert) | INSERT | ✓ | | | | | -[insert_batch()](#insert_batch) | N/A | ✓ | | | | | -[insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | -[delete()](#delete) | DELETE | ✓ | | | | | -[truncate()](#truncate) | TRUNCATE | ✓ | | | | | -[empty_table()](#empty_table) | DELETE | ✓ | | | | | +API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo +:-------------------------------- | :------------ | :------: | :------: | :----: | :----: | :------: | :---: +[query()](#query) | N/A | ✓ | ✓ | | | | +[get()](#get) | N/A | ✓ | ✓ | | | | +[get_where()](#get_where) | N/A | ✓ | ✓ | | | | +[count()](#count) | COUNT | ✓ | ✓ | | | | +[update()](#update) | UPDATE | ✓ | ✓ | | | | +[update_batch()](#update_batch) | N/A | ✓ | ✓ | | | | +[insert()](#insert) | INSERT | ✓ | ✓ | | | | +[insert_batch()](#insert_batch) | N/A | ✓ | ✓ | | | | +[insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | +[delete()](#delete) | DELETE | ✓ | ✓ | | | | +[truncate()](#truncate) | TRUNCATE | ✓ | ✓ | | | | +[empty_table()](#empty_table) | DELETE | ✓ | ✓ | | | | ### What are "Execution Methods"?? Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. Both (if provided) will be supplied to the callback function. @@ -1534,7 +1542,7 @@ data | Object | undefined | An array of objects containing the data you w on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). callback | Function | Required | What to do when the driver has responded. -This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd paramter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). With the third parameter, you can create an 'upsert' of sorts. Without the third parameter, it's essentially just "ignoring" errors, or, rather, converting them to simple warnings. +This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd parameter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). With the third parameter, you can create an 'upsert' of sorts. Without the third parameter, it's essentially just "ignoring" errors, or, rather, converting them to simple warnings. **Type of Response Sent to Callback** @@ -1654,7 +1662,7 @@ Parameter | Type | Default | Description table | String | Required | The table/collection you'd like to truncate. callback | Function | Required | What to do when the driver has responded. -For drivers that support it (MySQL), this method will utilize the `TRUNCATE` directive to empty a table of all it's data. The main difference between the `truncate()` method and the `empty_table()` method is that, when available, and when possible, truncating a table will reset your AUTO_INCREMENT counter back to zero. If you simply delete every row from a table, the next item inserted will just continue with the next highest ID from the deleted records. +For drivers that support it (MySQL, MSSQL), this method will utilize the `TRUNCATE` directive to empty a table of all it's data. The main difference between the `truncate()` method and the `empty_table()` method is that, when available, and when possible, truncating a table will reset your AUTO_INCREMENT counter back to zero. If you simply delete every row from a table, the next item inserted will just continue with the next highest ID from the deleted records. For drivers that don't support the truncate method, this will simply act as a wrapper to the [.empty_table()](#empty_table) method. diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index 9e4f212..526a63d 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -1,174 +1,14 @@ -class QueryBuilder { - constructor() { - this.where_array = []; - this.where_in_array = []; - this.from_array = []; - this.join_array = []; - this.select_array = []; - this.set_array = []; // has to be array to work as reference - this.order_by_array = []; - this.group_by_array = []; - this.having_array = []; - this.limit_to = []; // has to be array to work as reference - this.offset_val = []; // has to be array to work as reference - this.join_clause = []; - this.last_query_string = []; // has to be array to work as reference - this.distinct_clause = []; // has to be array to work as reference - this.aliased_tables = []; - } - - // ------------------------------ GENERIC METHODS ------------------------------// - _array_values(item) { - const keys = Object.keys(item); - const length = keys.length; - const values = Array(length); - for (let i = 0; i < length; i++) { - values[i] = item[keys[i]]; - } - return values; - } - - _prepare_for_limit_and_offset(item, type = 'limit') { - type = type.toLowerCase(); - - if (!/^(string|number)$/.test(typeof item)) { - throw new Error("Only integers or integers in the form of a string are allowed"); - } - - if (typeof item === 'string') { - item = item.trim(); - if (!/^\d+$/.test(item)) { - throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); - } - // Force to an integer - item = parseInt(item); - } - - // Make sure the number is a good one - if (typeof item === 'number') { - // Check for NaN and Infinity - if (item !== +item || item === Infinity) { - throw new Error("You have not provided a valid number to " + type + " by!"); - } - - // Make sure it's positive - if (item < 0) { - throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); - } - - // Only allow integers - if (item % 1 !== 0) { - throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); - } - } - else { - throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); - } - - return item; - } - - _extract_having_parts(key, key_array) { - let m; - key = key.trim().replace(/\s+/g,' '); - const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - const sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - const num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - const bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; - - if (m = str_condition.exec(key)) { - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = sstr_condition.exec(key)) { - //console.log("Key has sstring value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = num_condition.exec(key)) { - //console.log("Key has numeric value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = bool_condition.exec(key)) { - //console.log("Key has boolean value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else { - throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); - } - - return key_array; - } +const GenericQueryBuilder = require('../query_builder.js'); - // Simply setting all properties to [] causes reference issues in the parent class. - _clear_array(a, debug) { - if (debug === true) { - console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (let key in a) { - if (a.hasOwnProperty(key)) { - delete a[key]; - } - } - } - else if (Array.isArray(a)) { - while (a.length > 0) { - a.pop(); - } - } - if (debug === true) { - console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } +class QueryBuilder extends GenericQueryBuilder { + constructor() { + super(); + this.rand_word = 'NEWID()'; } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - _track_aliases(table) { - if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (let i in table) { - const t = table[i]; - this._track_aliases(t); - } - return; - } - - // Does the string contain a comma? If so, we need to separate - // the string into discreet statements - if (table.indexOf(',') !== -1) { - return this._track_aliases(table.split(',')); - } - - // if a table alias is used we can recognize it by a space - if (table.indexOf(' ') !== -1) { - // if the alias is written with the AS keyword, remove it - table = table.replace(/\s+AS\s+/gi, ' '); - - // Grab the alias - const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); - - // Store the alias, if it doesn't already exist - if (this.aliased_tables.indexOf(alias) == -1) { - this.aliased_tables.push(alias); - } - } - } - - _create_aliases_from_table(item) { - if (item.indexOf('.') !== -1) { - return item.split('.').reverse()[0]; - } - - return item; - } - _escape_identifiers(item = '*') { - if (item === '*') { - return item; - } + if (item === '*') return item; if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (let i in item) { @@ -176,12 +16,12 @@ class QueryBuilder { } return item; } - else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { + else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item.indexOf('(') !== -1) { return item; } let str; - if (item.indexOf('.' + '*') !== -1) { + if (item.indexOf('.*') !== -1) { str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); } else { @@ -285,16 +125,6 @@ class QueryBuilder { return item + alias; }; - _has_operator(str) { - if (typeof str === 'string' && str.length > 0) { - const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); - if (!match) { - return false; - } - } - return true; - }; - _qb_escape(str) { const mysql = require('mysql'); @@ -309,72 +139,62 @@ class QueryBuilder { return str; }; - // ---------------------------- SQL BUILD TOOLS ----------------------------// - _build_where_clause() { - let sql = ''; - if (this.where_array.length > 0) { - sql += " WHERE "; - } - sql += this.where_array.join(" "); - return sql; - }; + _build_limit_clause(sql, limit, offset) { + if (!limit) return sql; - _build_from_clause() { - let sql = ''; - if (this.from_array.length > 0) { - sql += " FROM "; - } else { - throw new Error("You have not provided any tables, views, or store procedures for this query!!"); - } - sql += this.from_array.join(', '); - return sql; - }; + sql += ' '; - _build_join_string() { - let sql = ''; - sql += this.join_array.join(' '); - if (sql.length > 0) sql = ' ' + sql; - return sql; + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - _build_group_by_clause() { - if (this.group_by_array.length <= 0) return ''; + _compile_delete() { + if (this.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } - let sql = ' GROUP BY '; - sql += this.group_by_array.join(', '); - return sql; - }; + this.from_array = this.from_array.slice(0,1); - _build_having_clause() { - if (this.having_array.length <= 0) return ''; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - let sql = ' HAVING '; - sql += this.having_array.join(' '); - return sql; + const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - _build_order_by_clause() { - if (this.order_by_array.length <= 0) return ''; - - let sql = ' ORDER BY '; - sql += this.order_by_array.join(', '); + _compile_insert(ignore, suffix='') { + const keys = []; + const values = []; - return sql; - }; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; - _build_limit_clause(sql, limit, offset) { - if (!limit) return sql; + keys.push(key); + values.push(val); + } - sql += ' '; + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; + if (this.from_array.length === 1) { + const table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + + return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; }; _compile_select() { @@ -400,21 +220,6 @@ class QueryBuilder { return sql; }; - _compile_delete() { - if (this.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - this.from_array = this.from_array.slice(0,1); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - _compile_update() { const valstr = []; for (let i in this.set_array) { @@ -443,837 +248,91 @@ class QueryBuilder { return this._build_limit_clause(sql, limit_to, offset_val); }; - _compile_insert(ignore, suffix='') { - const keys = []; - const values = []; - - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (this.from_array.length === 1) { - const table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; - }; - - reset_query(new_last_query,debug) { - this._clear_array(this.where_array, debug); - this._clear_array(this.where_in_array); - this._clear_array(this.from_array); - this._clear_array(this.join_array); - this._clear_array(this.select_array); - this._clear_array(this.set_array); - this._clear_array(this.order_by_array); - this._clear_array(this.group_by_array); - this._clear_array(this.having_array); - this._clear_array(this.limit_to); - this._clear_array(this.offset_val); - this._clear_array(this.join_clause); - this._clear_array(this.distinct_clause); - this._clear_array(this.aliased_tables); - - this._clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - } - - where(key, value = null, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); + join(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); } + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); escape = (typeof escape === 'boolean' ? escape : true); - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - } + const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - or_where(key, value=null, escape) { - escape = (typeof escape === 'boolean' ? escape : true); + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); + direction += ' '; } - return this._where(key, value, 'OR ', escape); - } - _where(key, value=null, type='AND ', escape) { - escape = (typeof escape === 'boolean' ? escape : true); + this._track_aliases(table); - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } + // Split multiple conditions + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; + if (escape === true && m) { + while (k = regex.exec(relation)) { + matches.push(k); } - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { - const filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - const self = this; - const parse_statement = (statement, joiner) => { - const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || /AND/i.test(joiner)) { - self.where(key, value, true); - } else { - self.or_where(key, value, true); - } - } - }; - parse_statement(filters.shift(), null); - while (filters.length > 0) { - const joiner = filters.shift(); - const statement = filters.shift(); - parse_statement(statement, joiner); - } - return this; - } + let new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); + new_relation += matches[j][0]; } - const key_array = {}; - key_array[key] = value; - key = key_array; + relation = ' ON ' + new_relation; } - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + // Split apart the condition and protect the identifiers + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); + } + else if (!this._has_operator(relation)) { + relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; + } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } + else { + relation = ' '; } - for (let k in key) { - let v = key[k]; - - if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { - return this._where_in(k, v, false, type, escape); - } - - const prefix = (this.where_array.length == 0 ? '' : type); + // Do we want to escape the table name? + if (escape === true) { + table = this._protect_identifiers(table,true); + } - if (v === null && !this._has_operator(k)) { - k += ' IS NULL'; - } + const join = direction + 'JOIN ' + table + relation; - if (v !== null) { - if (escape === true) { - k = this._protect_identifiers(k,escape); - v = ' ' + this._qb_escape(v); - } + this.join_array.push(join); + return this; + } - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + this._qb_escape(v); - } + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; - if (!this._has_operator(k)) { - k += ' ='; - } - } - else { - k = this._protect_identifiers(k,escape); - } + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); - if (v) { - this.where_array.push(prefix+k+v); - } - else { - this.where_array.push(prefix+k); - } - } - - return this; - } - - where_in(key, values, escape) { - return this._where_in(key, values, false, 'AND ', escape); - } - - or_where_in(key, values, escape) { - return this._where_in(key, values, false, 'OR ', escape); - } - - where_not_in(key, values, escape) { - return this._where_in(key, values, true, 'AND ', escape); - } - - or_where_not_in(key, values, escape) { - return this._where_in(key, values, true, 'OR ', escape); - } - - _where_in(key='', values=[], not, type='AND ', escape) { - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } - - // Values must be an array... - if (!Array.isArray(values)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } - - for (let i in values) { - this.where_in_array.push(this._qb_escape(values[i])); - } - - const prefix = (this.where_array.length == 0 ? '' : type); - const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); - - // reset the array for multiple calls - this._clear_array(this.where_in_array); - return this; - } - - like(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - } - - not_like(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - } - - or_like(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - } - - or_not_like(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - } - - _like(field, match, type='AND ', side='both', not='') { - match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); - - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if (!/^(string|object)$/.test(typeof field)) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } - - if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } - - const field_array = {}; - field_array[field] = match; - field = field_array; - } - - for (let k in field) { - let like_statement; - const v = field[k]; - k = this._protect_identifiers(k.trim()); - - // Make sure value is only string, number, or boolean - if (!/^(string|number|boolean)$/.test(typeof v)) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } - - this._where(like_statement, null, type, false); - } - - return this; - } - - from(from_param) { - if (!Array.isArray(from_param)) { - from_param = [from_param]; - } - for (let i in from_param) { - let val = from_param[i]; - - if (typeof val !== 'string' || val.trim() === '') continue; - - if (val.indexOf(',') !== -1) { - const objects = val.split(','); - for (let j in objects) { - const v = objects[j].trim(); - - this._track_aliases(v); - - this.from_array.push(this._protect_identifiers(v, true)); - } - } - else { - val = val.trim(); - - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - this._track_aliases(val); - - this.from_array.push(this._protect_identifiers(val, true)); - } - } - - return this; - } - - join(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - this._track_aliases(table); - - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while (k = regex.exec(relation)) { - matches.push(k); - } - - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); - } - else if (!this._has_operator(relation)) { - relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = this._protect_identifiers(table,true); - } - - const join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - } - - select(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Array.isArray(select)) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } - } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); - } - - if (typeof escape !== 'boolean') escape = true; - - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - // Prevent it from trying to parse select statements with functions and if statements - if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - - // Identify individual statements within select string - let m, open_paren_index, inner_parenthesis; - const reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } - } - } - } - else { - select = [select]; - } - } - } - - for (let i in select) { - const val = select[i].trim(); - - if (val !== '') { - this.select_array.push(this._protect_identifiers(val, escape)); - } - } - return this; - } - - select_min(select,alias) { - return this._min_max_avg_sum(select, alias, 'MIN'); - } - - select_max(select,alias) { - return this._min_max_avg_sum(select, alias, 'MAX'); - } - - select_avg(select,alias) { - return this._min_max_avg_sum(select, alias, 'AVG'); - } - - select_sum(select,alias) { - return this._min_max_avg_sum(select, alias, 'SUM'); - } - - _min_max_avg_sum(select='', alias='', type='MAX') { - - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } - - type = type.toUpperCase(); - - if (['MAX', 'MIN', 'AVG', 'SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } - - if (alias == '') { - alias = this._create_aliases_from_table(select.trim()); - } - - const sql = `${type}(${this._protect_identifiers(select.trim())}) AS ${alias}`; - - this.select_array.push(sql); - - return this; - } - - distinct(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - - if (do_distinct) { - this._clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - this._clear_array(this.distinct_clause); - } - return this; - } - - group_by(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (!Array.isArray(by)) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } - - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - - for (let key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } - - const val = by[key].trim(); - - if (val !== '') { - this.group_by_array.push(this._protect_identifiers(val)); - } - } - return this; - } - - having(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - } - - or_having(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - } - - _having(key, value, type='AND ', escape) { - - let m; - let key_array = {}; - const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - const key_is_array = Array.isArray(key); - - if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); - } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; - } - else { - key = this._extract_having_parts(key, key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (let i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = this._extract_having_parts(key[i], key_array); - } - } - key = key_array; - } - } - } - else { - throw new Error(`Improper value supplied as the second parameter (${typeof value}) of the having() method.`); - } - - - for (let k in key) { - let v = key[k]; - const prefix = (this.having_array.length == 0 ? '' : type); - - if (escape === true) { - k = this._protect_identifiers(k); - } - - if (v === null) { - k += ' IS'; - } - else if (!this._has_operator(k)) { - k += ' ='; - } - - if (v != '') { - v = ' ' + this._qb_escape(v); - } - - this.having_array.push(prefix + k + v); - } - - return this; - } - - order_by(orderby, direction) { - let m; - const rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { - this.order_by_array.push(rand_word); - return this; - } - - // normalize orderby to be an array of items - if (!Array.isArray(orderby)) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } - } - - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } - - for (let i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); - - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: this._protect_identifiers(m[1].trim()), direction: m[2].trim().toUpperCase()}; - } else { - if (/^(ASC|DESC)$/i.test(direction) || direction === '') { - orderby[i] = {field: this._protect_identifiers(orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } - } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); - } - - return this; - } - - limit(limit, offset) { - this._clear_array(this.limit_to); - this.limit_to.push(this._prepare_for_limit_and_offset(limit, 'limit')); - - if (offset !== undefined) { - return this.offset(offset); - } - - return this; - } - - offset(offset) { - this._clear_array(this.offset_val); - this.offset_val.push(this._prepare_for_limit_and_offset(offset, 'offset')); - return this; - } - - set(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) throw new Error("set(): Invalid field name provided!"); - - if (typeof value === 'undefined') - throw new Error("set(): First param was string but no value (second param) provided to set!"); - - const key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } - - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } - } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); - } - - - // Add each key:value pair to the set_array - for (let i in key) { - let v = key[i]; - if (typeof v === 'undefined') continue; - - if (v instanceof Date) v = v.toString(); - - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - - // Escape the key to be DRY - const escaped_key = this._protect_identifiers(i,escape); - - // Build a temporary object with escaped key and val - const temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = this._qb_escape(v); - } - - // Determine if this key has already been set - let found_index = null; - for (let j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } - } - - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } - } - - //console.dir(this.set_array); - - return this; - } - - insert(table, set, ignore, suffix) { - return this._insert(table, set, ignore, suffix); - } - - _insert(table, set, ignore, suffix) { - table = table || '' - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } - - if (Array.isArray(set)) { - return this.insert_batch(table, set, ignore, suffix); - } - - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table === '') { - if (this.from_array.length == 0) throw new Error('insert(): No tables set to insert into!'); - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - return this._compile_insert(ignore, suffix); - } - - insert_ignore(table, set, suffix) { - return this._insert_ignore(table, set, suffix); - } - - _insert_ignore(table, set, suffix) { - return this.insert(table, set, true, suffix); - } - - insert_batch(table,set=null,ignore,suffix) { - return this._insert_batch(table, set, ignore, suffix); - } - - _insert_batch(table,set=null,ignore,suffix) { - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); - table = table.trim(); - - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } if (table == '') { @@ -1334,7 +393,7 @@ class QueryBuilder { } } if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); } map.push('(' + row.join(', ') + ')'); })(i); @@ -1344,59 +403,6 @@ class QueryBuilder { return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; } - get(table) { - return this._get(table); - } - - _get(table) { - if (typeof table === 'string' || Array.isArray(table)) { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } - } - return this._compile_select(this); - } - - get_where(table=null, where=null) { - return this._get_where(table, where); - } - - _get_where(table=null, where=null) { - // Check if table is either a string or array - if (typeof table !== 'string' && !Array.isArray(table)) - throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - - // If table is a string, make sure it's not empty - if (typeof table === 'string' && table.trim().length <= 0) - throw new Error("Invalid table string specified!"); - - // If table is array, make sure there are only strings in there and that they are non-empty strings - if (Array.isArray(table)) { - for (let v in table) { - if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { - throw new Error("Invalid table string specified in array of tables!"); - break; - } - } - } - - this.from(table); - - if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - - this.where(where); - - return this._compile_select(); - } - - count(table) { - return this._count(table); - } - _count(table) { if (typeof table === 'string') { this.from(table); @@ -1409,333 +415,7 @@ class QueryBuilder { return sql; } - - update(table, set, where=null) { - return this._update(table, set, where); - } - - _update(table, set, where=null) { - - table = table || ''; - set = set || null; - - // Send to batch_update if the data param is an array - if (Array.isArray(set)) { - let index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); - } - } - - // If set is a number, boolean, a non-empty string, or regex, fail - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); - } - - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); - } - - return this._compile_update(); - } - - update_batch(table='', set=null, index=null, where=null) { - return this._update_batch(table, set, index, where); - } - - _update_batch(table='', set=null, index=null, where=null) { - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } - - // Check to make sure we have a dataset - if (!Array.isArray(set)) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } - - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } - - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (let i in set) { - const clean = {}; - const row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - const keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (let j in row) { - clean[this._protect_identifiers(j)] = this._qb_escape(row[j]); - } - this.set_array.push(clean); - } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } - - // Verify there is a table in the from_array - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); - } - - table = this.from_array.toString(); - - - // Limit to 100 rows per batch - const batches = []; - for (let i = 0, total = this.set_array.length; i < total; i += 100) { - const when_then = {}; - const ids = []; - const where = (this.where_array.length > 0 ? this._build_where_clause() + ' AND ' : ''); - const chunk = this.set_array.slice(i,100); - - // Escape the index - index = this._protect_identifiers(index); - - for (let j in chunk) { - ids.push(chunk[j][index]); - - const keys = Object.keys(chunk[j]); - for (let k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push(`WHEN ${index} = ${ids[j]} THEN ${chunk[j][keys[k]]} `); - } - } - } - - // Build the actual SQL statement - let sql = `UPDATE (${table}) SET `; - let cases = ''; - - for (let l in when_then) { - cases += l + ' = CASE '; - - for (let m in when_then[l]) { - cases += when_then[l][m]; - } - - cases += `ELSE ${l} END, `; - } - - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); - } - - return batches; - } - - delete(table, where) { - return this._delete(table, where); - } - - _delete(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } - - return this._compile_delete(); - } - - get_compiled_select(table) { - if (typeof table !== 'undefined') { - this._track_aliases(table); - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } - } - - return this._compile_select(); - } - - get_compiled_delete(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - - return this._compile_delete(); - } - - get_compiled_update(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - return this._compile_update(); - } - - get_compiled_insert(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - return this._compile_insert(); - } - - compile_select(table) { - return this.get_compiled_select(table); - } - - compile_delete(table) { - return this.get_compiled_delete(table); - } - - compile_update(table) { - return this.get_compiled_update(table); - } - - compile_insert(table) { - return this.get_compiled_insert(table); - } - - last_query() { - return this.last_query_string[0] || ''; - } - - escape(val) { - return this._qb_escape(val); - } - - empty_table(table) { - return this._empty_table(table); - } - - _empty_table(table) { - if (typeof table === 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } - - return "DELETE FROM " + this.from_array[0]; - } - - truncate(table) { - return this._truncate(table); - } - - _truncate(table) { - if (typeof table === 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } - - return "TRUNCATE " + this.from_array[0]; - } } + module.exports = QueryBuilder; diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index 99d709a..e610b1b 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -107,13 +107,14 @@ class QueryExec extends QueryBuilder { } insert_ignore(table,set,on_dupe,cb) { - if (typeof on_dupe === 'function') { - cb = on_dupe; - on_dupe = null; - } - const sql = this._insert_ignore(table,set,on_dupe); - this.reset_query(sql); - this._exec(sql,cb); + throw new Error("This feature is currently unsupported in the MSSQL driver."); + // if (typeof on_dupe === 'function') { + // cb = on_dupe; + // on_dupe = null; + // } + // const sql = this._insert_ignore(table,set,on_dupe); + // this.reset_query(sql); + // this._exec(sql,cb); } insert_batch(table,set,cb) { diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 9e4f212..b4a4729 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,174 +1,14 @@ -class QueryBuilder { - constructor() { - this.where_array = []; - this.where_in_array = []; - this.from_array = []; - this.join_array = []; - this.select_array = []; - this.set_array = []; // has to be array to work as reference - this.order_by_array = []; - this.group_by_array = []; - this.having_array = []; - this.limit_to = []; // has to be array to work as reference - this.offset_val = []; // has to be array to work as reference - this.join_clause = []; - this.last_query_string = []; // has to be array to work as reference - this.distinct_clause = []; // has to be array to work as reference - this.aliased_tables = []; - } - - // ------------------------------ GENERIC METHODS ------------------------------// - _array_values(item) { - const keys = Object.keys(item); - const length = keys.length; - const values = Array(length); - for (let i = 0; i < length; i++) { - values[i] = item[keys[i]]; - } - return values; - } - - _prepare_for_limit_and_offset(item, type = 'limit') { - type = type.toLowerCase(); - - if (!/^(string|number)$/.test(typeof item)) { - throw new Error("Only integers or integers in the form of a string are allowed"); - } - - if (typeof item === 'string') { - item = item.trim(); - if (!/^\d+$/.test(item)) { - throw new Error("The string you provided to " + type + " by contains non-integer values--this isn't allowed."); - } - // Force to an integer - item = parseInt(item); - } - - // Make sure the number is a good one - if (typeof item === 'number') { - // Check for NaN and Infinity - if (item !== +item || item === Infinity) { - throw new Error("You have not provided a valid number to " + type + " by!"); - } - - // Make sure it's positive - if (item < 0) { - throw new Error("Only positive integeres are allowed when " + (type == 'offset' ? 'OFFSET' : 'LIMIT') + "ing SQL results!"); - } - - // Only allow integers - if (item % 1 !== 0) { - throw new Error("You cannot " + type + " a SQL result set with a floating point value!"); - } - } - else { - throw new Error("There was an unrecoverable error while parsing the value provdided in your " + type + " statement."); - } - - return item; - } - - _extract_having_parts(key, key_array) { - let m; - key = key.trim().replace(/\s+/g,' '); - const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; //"// had to do this for syntax highlighting - const sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; //'// had to do this for syntax highlighting - const num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; - const bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; - - if (m = str_condition.exec(key)) { - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = sstr_condition.exec(key)) { - //console.log("Key has sstring value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = num_condition.exec(key)) { - //console.log("Key has numeric value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else if (m = bool_condition.exec(key)) { - //console.log("Key has boolean value"); - key_array[m[1]] = m[3]; - key = key_array; - } - else { - throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); - } - - return key_array; - } +const GenericQueryBuilder = require('../query_builder.js'); - // Simply setting all properties to [] causes reference issues in the parent class. - _clear_array(a, debug) { - if (debug === true) { - console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } - if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { - for (let key in a) { - if (a.hasOwnProperty(key)) { - delete a[key]; - } - } - } - else if (Array.isArray(a)) { - while (a.length > 0) { - a.pop(); - } - } - if (debug === true) { - console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); - console.dir(a); - } +class QueryBuilder extends GenericQueryBuilder { + constructor() { + super(); + this.rand_word = 'RAND()'; } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - _track_aliases(table) { - if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { - for (let i in table) { - const t = table[i]; - this._track_aliases(t); - } - return; - } - - // Does the string contain a comma? If so, we need to separate - // the string into discreet statements - if (table.indexOf(',') !== -1) { - return this._track_aliases(table.split(',')); - } - - // if a table alias is used we can recognize it by a space - if (table.indexOf(' ') !== -1) { - // if the alias is written with the AS keyword, remove it - table = table.replace(/\s+AS\s+/gi, ' '); - - // Grab the alias - const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,''); - - // Store the alias, if it doesn't already exist - if (this.aliased_tables.indexOf(alias) == -1) { - this.aliased_tables.push(alias); - } - } - } - - _create_aliases_from_table(item) { - if (item.indexOf('.') !== -1) { - return item.split('.').reverse()[0]; - } - - return item; - } - _escape_identifiers(item = '*') { - if (item === '*') { - return item; - } + if (item === '*') return item; if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { for (let i in item) { @@ -285,16 +125,6 @@ class QueryBuilder { return item + alias; }; - _has_operator(str) { - if (typeof str === 'string' && str.length > 0) { - const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); - if (!match) { - return false; - } - } - return true; - }; - _qb_escape(str) { const mysql = require('mysql'); @@ -309,72 +139,62 @@ class QueryBuilder { return str; }; - // ---------------------------- SQL BUILD TOOLS ----------------------------// - _build_where_clause() { - let sql = ''; - if (this.where_array.length > 0) { - sql += " WHERE "; - } - sql += this.where_array.join(" "); - return sql; - }; + _build_limit_clause(sql, limit, offset) { + if (!limit) return sql; - _build_from_clause() { - let sql = ''; - if (this.from_array.length > 0) { - sql += " FROM "; - } else { - throw new Error("You have not provided any tables, views, or store procedures for this query!!"); - } - sql += this.from_array.join(', '); - return sql; - }; + sql += ' '; - _build_join_string() { - let sql = ''; - sql += this.join_array.join(' '); - if (sql.length > 0) sql = ' ' + sql; - return sql; + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; - _build_group_by_clause() { - if (this.group_by_array.length <= 0) return ''; + _compile_delete() { + if (this.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } - let sql = ' GROUP BY '; - sql += this.group_by_array.join(', '); - return sql; - }; + this.from_array = this.from_array.slice(0,1); - _build_having_clause() { - if (this.having_array.length <= 0) return ''; + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; - let sql = ' HAVING '; - sql += this.having_array.join(' '); - return sql; + const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); + return this._build_limit_clause(sql, limit_to, offset_val); }; - _build_order_by_clause() { - if (this.order_by_array.length <= 0) return ''; - - let sql = ' ORDER BY '; - sql += this.order_by_array.join(', '); + _compile_insert(ignore, suffix='') { + const keys = []; + const values = []; - return sql; - }; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; - _build_limit_clause(sql, limit, offset) { - if (!limit) return sql; + keys.push(key); + values.push(val); + } - sql += ' '; + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; + if (this.from_array.length === 1) { + const table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + + return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; }; _compile_select() { @@ -400,21 +220,6 @@ class QueryBuilder { return sql; }; - _compile_delete() { - if (this.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - this.from_array = this.from_array.slice(0,1); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - _compile_update() { const valstr = []; for (let i in this.set_array) { @@ -443,837 +248,91 @@ class QueryBuilder { return this._build_limit_clause(sql, limit_to, offset_val); }; - _compile_insert(ignore, suffix='') { - const keys = []; - const values = []; - - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (this.from_array.length === 1) { - const table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; - }; - - reset_query(new_last_query,debug) { - this._clear_array(this.where_array, debug); - this._clear_array(this.where_in_array); - this._clear_array(this.from_array); - this._clear_array(this.join_array); - this._clear_array(this.select_array); - this._clear_array(this.set_array); - this._clear_array(this.order_by_array); - this._clear_array(this.group_by_array); - this._clear_array(this.having_array); - this._clear_array(this.limit_to); - this._clear_array(this.offset_val); - this._clear_array(this.join_clause); - this._clear_array(this.distinct_clause); - this._clear_array(this.aliased_tables); - - this._clear_array(this.last_query_string); - if (typeof new_last_query === 'string') { - this.last_query_string.push(new_last_query); - } - } - - where(key, value = null, escape) { - if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); + join(table, relation, direction, escape) { + if (typeof table !== 'string' || table.trim().length == 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); } + relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); escape = (typeof escape === 'boolean' ? escape : true); - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'AND '); - } - return this._where(key, value, 'AND ', escape); - } + const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - or_where(key, value=null, escape) { - escape = (typeof escape === 'boolean' ? escape : true); + if (direction != '') { + direction = direction.toUpperCase().trim(); + if (valid_directions.indexOf(direction) === -1) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (relation === '') { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { - return this._where_in(key, value, false, 'OR '); + direction += ' '; } - return this._where(key, value, 'OR ', escape); - } - _where(key, value=null, type='AND ', escape) { - escape = (typeof escape === 'boolean' ? escape : true); + this._track_aliases(table); - // Must be an object or a string - if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { - // If it's not an object, it must be a string - if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); - } else { - // If it is a string, it can't be an empty one - if (key.length == 0) { - throw new Error("where(): No field name or query provided!"); - } + // Split multiple conditions + const regex = /\sAND\s|\sOR\s/ig; + const m = relation.match(regex); + const matches = []; + let k, temp, temp_match, match; + if (escape === true && m) { + while (k = regex.exec(relation)) { + matches.push(k); } - // If it's a actual where clause string (with no paranthesis), - // not just a field name, split it into individual parts to escape it properly - if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { - const filters = key.split(/\s+(AND|OR)\s+/i); - if (filters.length > 1) { - const self = this; - const parse_statement = (statement, joiner) => { - const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); - if (parsed.length >= 4) { - const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); - const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); - if (joiner === null || /AND/i.test(joiner)) { - self.where(key, value, true); - } else { - self.or_where(key, value, true); - } - } - }; - parse_statement(filters.shift(), null); - while (filters.length > 0) { - const joiner = filters.shift(); - const statement = filters.shift(); - parse_statement(statement, joiner); - } - return this; - } + let new_relation = ''; + matches.push(['']); + matches[(matches.length - 1)].index = relation.length; + for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { + temp = relation.substr(s, matches[j].index - s); + temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); + new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); + new_relation += matches[j][0]; } - const key_array = {}; - key_array[key] = value; - key = key_array; + relation = ' ON ' + new_relation; } - if (Object.keys(key).length == 0) { - throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + // Split apart the condition and protect the identifiers + else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { + match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) + relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); + } + else if (!this._has_operator(relation)) { + relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; + } + else if (relation && escape === false) { + relation = ' ON ' + relation; + } + else { + relation = ' '; } - for (let k in key) { - let v = key[k]; - - if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { - return this._where_in(k, v, false, type, escape); - } - - const prefix = (this.where_array.length == 0 ? '' : type); + // Do we want to escape the table name? + if (escape === true) { + table = this._protect_identifiers(table,true); + } - if (v === null && !this._has_operator(k)) { - k += ' IS NULL'; - } + const join = direction + 'JOIN ' + table + relation; - if (v !== null) { - if (escape === true) { - k = this._protect_identifiers(k,escape); - v = ' ' + this._qb_escape(v); - } + this.join_array.push(join); + return this; + } - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - v = ' ' + this._qb_escape(v); - } + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; - if (!this._has_operator(k)) { - k += ' ='; - } - } - else { - k = this._protect_identifiers(k,escape); - } + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); - if (v) { - this.where_array.push(prefix+k+v); - } - else { - this.where_array.push(prefix+k); - } - } - - return this; - } - - where_in(key, values, escape) { - return this._where_in(key, values, false, 'AND ', escape); - } - - or_where_in(key, values, escape) { - return this._where_in(key, values, false, 'OR ', escape); - } - - where_not_in(key, values, escape) { - return this._where_in(key, values, true, 'AND ', escape); - } - - or_where_not_in(key, values, escape) { - return this._where_in(key, values, true, 'OR ', escape); - } - - _where_in(key='', values=[], not, type='AND ', escape) { - not = (not ? ' NOT' : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); - } - - // Values must be an array... - if (!Array.isArray(values)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } - } - - for (let i in values) { - this.where_in_array.push(this._qb_escape(values[i])); - } - - const prefix = (this.where_array.length == 0 ? '' : type); - const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; - this.where_array.push(where_in); - - // reset the array for multiple calls - this._clear_array(this.where_in_array); - return this; - } - - like(field, match, side) { - return this._like(field, match, 'AND ', side, ''); - } - - not_like(field, match, side) { - return this._like(field, match, 'AND ', side, ' NOT'); - } - - or_like(field, match, side) { - return this._like(field, match, 'OR ', side, ''); - } - - or_not_like(field, match, side) { - return this._like(field, match, 'OR ', side, ' NOT'); - } - - _like(field, match, type='AND ', side='both', not='') { - match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); - - if (typeof field === 'string' && field.length == 0) { - throw new Error("like(): The field you provided is empty."); - } - else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { - throw new Error("like(): The object you provided is empty."); - } - else if (!/^(string|object)$/.test(typeof field)) { - throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); - } - - if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { - if (match === null) { - throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); - } - - const field_array = {}; - field_array[field] = match; - field = field_array; - } - - for (let k in field) { - let like_statement; - const v = field[k]; - k = this._protect_identifiers(k.trim()); - - // Make sure value is only string, number, or boolean - if (!/^(string|number|boolean)$/.test(typeof v)) { - throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - // If number, don't allow Infinity or NaN - else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { - throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); - } - - if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; - } - else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; - } - else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; - } - else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; - } - else { - throw new Error("like(): Invalid direction provided!"); - } - - this._where(like_statement, null, type, false); - } - - return this; - } - - from(from_param) { - if (!Array.isArray(from_param)) { - from_param = [from_param]; - } - for (let i in from_param) { - let val = from_param[i]; - - if (typeof val !== 'string' || val.trim() === '') continue; - - if (val.indexOf(',') !== -1) { - const objects = val.split(','); - for (let j in objects) { - const v = objects[j].trim(); - - this._track_aliases(v); - - this.from_array.push(this._protect_identifiers(v, true)); - } - } - else { - val = val.trim(); - - // Extract any aliases that might exist. We use this information - // in the protect_identifiers function to know whether to add a table prefix - this._track_aliases(val); - - this.from_array.push(this._protect_identifiers(val, true)); - } - } - - return this; - } - - join(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - this._track_aliases(table); - - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while (k = regex.exec(relation)) { - matches.push(k); - } - - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); - } - else if (!this._has_operator(relation)) { - relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = this._protect_identifiers(table,true); - } - - const join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - } - - select(select,escape) { - // First param must be a non-empty string or array - if (typeof select === 'string') { - select = select.trim(); - if (select.length == 0) { - throw new Error("Your select string is empty!"); - } - } - else if (Array.isArray(select)) { - if (select.length == 0) { - throw new Error("Your select array is empty!"); - } - } - else { - throw new Error("Select method requires a string or array to be passed in the first parameter!"); - } - - if (typeof escape !== 'boolean') escape = true; - - // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) - if (typeof select === 'string') { - if (select.indexOf(')') === -1) { - select = select.split(','); - } else { - if (escape === true) { - // Prevent it from trying to parse select statements with functions and if statements - if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - - // Identify individual statements within select string - let m, open_paren_index, inner_parenthesis; - const reg = /\)/g; - while ((m = reg.exec(select) !== null)) { - open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); - if (open_paren_index !== -1) { - inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); - if (inner.parenthesis.indexOf(',') !== -1) { - throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); - break; - } - } - } - } - else { - select = [select]; - } - } - } - - for (let i in select) { - const val = select[i].trim(); - - if (val !== '') { - this.select_array.push(this._protect_identifiers(val, escape)); - } - } - return this; - } - - select_min(select,alias) { - return this._min_max_avg_sum(select, alias, 'MIN'); - } - - select_max(select,alias) { - return this._min_max_avg_sum(select, alias, 'MAX'); - } - - select_avg(select,alias) { - return this._min_max_avg_sum(select, alias, 'AVG'); - } - - select_sum(select,alias) { - return this._min_max_avg_sum(select, alias, 'SUM'); - } - - _min_max_avg_sum(select='', alias='', type='MAX') { - - if (typeof select !== 'string' || select === '') { - throw Error("Invalid query!"); - return this; - } - - type = type.toUpperCase(); - - if (['MAX', 'MIN', 'AVG', 'SUM'].indexOf(type) === -1) { - throw Error("Invalid function type!"); - return this; - } - - if (alias == '') { - alias = this._create_aliases_from_table(select.trim()); - } - - const sql = `${type}(${this._protect_identifiers(select.trim())}) AS ${alias}`; - - this.select_array.push(sql); - - return this; - } - - distinct(do_distinct) { - do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); - - if (do_distinct) { - this._clear_array(this.distinct_clause); - this.distinct_clause.push('DISTINCT '); - } - else { - this._clear_array(this.distinct_clause); - } - return this; - } - - group_by(by) { - if (typeof by === 'string') { - by = by.trim(); - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - by = by.split(','); - } - - if (!Array.isArray(by)) { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); - } - - if (by.length <= 0) { - throw new Error("You haven't provided any fields to group by!"); - } - - for (let key in by) { - if (typeof by[key] !== 'string') { - throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); - } - - const val = by[key].trim(); - - if (val !== '') { - this.group_by_array.push(this._protect_identifiers(val)); - } - } - return this; - } - - having(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'AND ', escape); - } - - or_having(key, value, escape) { - escape = (typeof escape !== 'boolean' ? true : escape); - return this._having(key, value, 'OR ', escape); - } - - _having(key, value, type='AND ', escape) { - - let m; - let key_array = {}; - const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); - const key_is_array = Array.isArray(key); - - if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... - if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... - throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); - } - key_array[key] = value; - key = key_array; - } - else if (typeof value === 'undefined' || value === null) { - if (key_is_object === false) { - if (typeof key === 'string') { - if (value === null) { - key_array[key] = null; - key = key_array; - } - else { - key = this._extract_having_parts(key, key_array); - } - } - else if (key_is_array === true) { - //console.log("Key is NOT a string"); - for (let i in key) { - if (typeof key[i] !== 'string') { - throw new Error("having(): You've provided an unparseable format to the having() method.."); - } - else { - key_array = this._extract_having_parts(key[i], key_array); - } - } - key = key_array; - } - } - } - else { - throw new Error(`Improper value supplied as the second parameter (${typeof value}) of the having() method.`); - } - - - for (let k in key) { - let v = key[k]; - const prefix = (this.having_array.length == 0 ? '' : type); - - if (escape === true) { - k = this._protect_identifiers(k); - } - - if (v === null) { - k += ' IS'; - } - else if (!this._has_operator(k)) { - k += ' ='; - } - - if (v != '') { - v = ' ' + this._qb_escape(v); - } - - this.having_array.push(prefix + k + v); - } - - return this; - } - - order_by(orderby, direction) { - let m; - const rand_word = 'RAND()'; - direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); - - // Don't need to do anything below if the direction provided is random - if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { - this.order_by_array.push(rand_word); - return this; - } - - // normalize orderby to be an array of items - if (!Array.isArray(orderby)) { - if (typeof orderby === 'string') { - orderby = orderby.trim(); - if (orderby.length == 0) { - throw new Error("You haven't provided any fields to order by!!"); - } - orderby = orderby.split(','); - } else if (!orderby && (/(random|RAND|RAND\(\))/i.test(direction))) { - this.order_by_array.push(rand_word); - return this; - } - else { - throw new Error("No field provided to order by!"); - } - } - - if (orderby.length === 0) { - throw new Error("You haven't provided any fields to order by!"); - } - - for (let i in orderby) { - orderby[i] = orderby[i].replace(/\s+/g, ' '); - - if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\))/i)) { - if (m[2].trim() === 'RAND()') { - this.order_by_array.push(rand_word); - return this; - } - orderby[i] = {field: this._protect_identifiers(m[1].trim()), direction: m[2].trim().toUpperCase()}; - } else { - if (/^(ASC|DESC)$/i.test(direction) || direction === '') { - orderby[i] = {field: this._protect_identifiers(orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; - } else { - throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); - } - } - - this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); - } - - return this; - } - - limit(limit, offset) { - this._clear_array(this.limit_to); - this.limit_to.push(this._prepare_for_limit_and_offset(limit, 'limit')); - - if (offset !== undefined) { - return this.offset(offset); - } - - return this; - } - - offset(offset) { - this._clear_array(this.offset_val); - this.offset_val.push(this._prepare_for_limit_and_offset(offset, 'offset')); - return this; - } - - set(key, value, escape) { - escape = (typeof escape === 'boolean' ? escape : true); - - if (typeof key === 'string') { - // Convert key and value params to {key: value} - key = key.trim(); - if (key.length == 0) throw new Error("set(): Invalid field name provided!"); - - if (typeof value === 'undefined') - throw new Error("set(): First param was string but no value (second param) provided to set!"); - - const key_array = {}; - key_array[key] = value; - key = key_array; - } - else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { - if (Object.keys(key).length === 0) { - throw new Error("set(): The object you provided is empty."); - } - - if (typeof value !== 'undefined' & value !== null) { - throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); - } - } - else { - throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); - } - - - // Add each key:value pair to the set_array - for (let i in key) { - let v = key[i]; - if (typeof v === 'undefined') continue; - - if (v instanceof Date) v = v.toString(); - - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - - // Escape the key to be DRY - const escaped_key = this._protect_identifiers(i,escape); - - // Build a temporary object with escaped key and val - const temp = {}; - if (escape === false) { - temp[escaped_key] = v; - } else { - temp[escaped_key] = this._qb_escape(v); - } - - // Determine if this key has already been set - let found_index = null; - for (let j in this.set_array) { - if (this.set_array[j].hasOwnProperty(escaped_key)) { - found_index = j; - break; - } - } - - // Update value if key already set or add if not found - if (found_index !== null) { - this.set_array[found_index] = temp; - } else { - this.set_array.push(temp); - } - } - - //console.dir(this.set_array); - - return this; - } - - insert(table, set, ignore, suffix) { - return this._insert(table, set, ignore, suffix); - } - - _insert(table, set, ignore, suffix) { - table = table || '' - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("insert(): Invalid data provided to insert into database!"); - } - - if (Array.isArray(set)) { - return this.insert_batch(table, set, ignore, suffix); - } - - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } - } - - if (typeof table !== 'string') { - throw new Error("insert(): Table parameter must be a string!"); - } - - table = table.trim(); - - if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table === '') { - if (this.from_array.length == 0) throw new Error('insert(): No tables set to insert into!'); - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - return this._compile_insert(ignore, suffix); - } - - insert_ignore(table, set, suffix) { - return this._insert_ignore(table, set, suffix); - } - - _insert_ignore(table, set, suffix) { - return this.insert(table, set, true, suffix); - } - - insert_batch(table,set=null,ignore,suffix) { - return this._insert_batch(table, set, ignore, suffix); - } - - _insert_batch(table,set=null,ignore,suffix) { - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); - table = table.trim(); - - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); } if (table == '') { @@ -1334,7 +393,7 @@ class QueryBuilder { } } if (row.length != columns.length) { - throw new Error('insert_batch(): Cannot use batch insert into ' + table + ' - fields must match on all rows (' + row.join(',') + ' vs ' + columns.join(',') + ').'); + throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); } map.push('(' + row.join(', ') + ')'); })(i); @@ -1344,59 +403,6 @@ class QueryBuilder { return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; } - get(table) { - return this._get(table); - } - - _get(table) { - if (typeof table === 'string' || Array.isArray(table)) { - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to select from!'); - } - } - return this._compile_select(this); - } - - get_where(table=null, where=null) { - return this._get_where(table, where); - } - - _get_where(table=null, where=null) { - // Check if table is either a string or array - if (typeof table !== 'string' && !Array.isArray(table)) - throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); - - // If table is a string, make sure it's not empty - if (typeof table === 'string' && table.trim().length <= 0) - throw new Error("Invalid table string specified!"); - - // If table is array, make sure there are only strings in there and that they are non-empty strings - if (Array.isArray(table)) { - for (let v in table) { - if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { - throw new Error("Invalid table string specified in array of tables!"); - break; - } - } - } - - this.from(table); - - if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) - throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); - - this.where(where); - - return this._compile_select(); - } - - count(table) { - return this._count(table); - } - _count(table) { if (typeof table === 'string') { this.from(table); @@ -1409,333 +415,6 @@ class QueryBuilder { return sql; } - - update(table, set, where=null) { - return this._update(table, set, where); - } - - _update(table, set, where=null) { - - table = table || ''; - set = set || null; - - // Send to batch_update if the data param is an array - if (Array.isArray(set)) { - let index = null; - if (set.length > 0) { - if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { - index = Object.keys(set[0])[0]; - } - } - if (index) { - return this.update_batch(table, set, index, where); - } else { - throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); - } - } - - // If set is a number, boolean, a non-empty string, or regex, fail - if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { - throw new Error("update(): Invalid data provided to update database!"); - } - - // If data object was provided, set it - if (set !== null) { - if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { - this.set(set); - } else { - throw new Error("update(): Empty data object provided. This is not allowed."); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('update(): No tables set to update!'); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where !== null) { - this.where(where); - } - - return this._compile_update(); - } - - update_batch(table='', set=null, index=null, where=null) { - return this._update_batch(table, set, index, where); - } - - _update_batch(table='', set=null, index=null, where=null) { - // Make sure an index has been provided! - if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { - throw new Error("update_batch(): Invalid index provided to generate batch update query!"); - } - - // Check to make sure we have a dataset - if (!Array.isArray(set)) { - throw new Error("update_batch(): Array of object expected and non-array received."); - } - - // Make sure our dataset isn't emtpy - if (set.length === 0) { - throw new Error("update_batch(): You must supply some data to batch update the table with."); - } - - // Make sure each item in the dataset has the specified index and then add data to set_array - //console.dir(set); - for (let i in set) { - const clean = {}; - const row = set[i]; - if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { - const keys = Object.keys(row); - if (keys.indexOf(index) !== -1) { - for (let j in row) { - clean[this._protect_identifiers(j)] = this._qb_escape(row[j]); - } - this.set_array.push(clean); - } - } else { - throw new Error("update_batch(): You have supplied an invalid object to batch update!"); - } - } - - // Fail if, at this point, nothing has been set - if (this.set_array.length == 0) { - throw new Error("update_batch(): You must provide some data to batch update!"); - } - - // NOTE: If falsy table provided, table will have been converted to an empty string... - if (typeof table !== 'string') { - throw new Error("update(): Table parameter must be a string!"); - } - - table = table.trim(); - - // Table name must be in a legitimate format - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("update(): You have not set any tables to update!"); - } - - // If table not supplied, it must have been supplied already - if (table == '') { - if (this.from_array.length == 0) { - throw new Error('No tables set to insert into!'); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - // Set where array if a where statement was provided - if (where != null) { - this.where(where); - } - - // Verify there is a table in the from_array - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build batch UPDATE query with!"); - } - throw new Error("You have provided too many tables to build batch UPDATE query with!"); - } - - table = this.from_array.toString(); - - - // Limit to 100 rows per batch - const batches = []; - for (let i = 0, total = this.set_array.length; i < total; i += 100) { - const when_then = {}; - const ids = []; - const where = (this.where_array.length > 0 ? this._build_where_clause() + ' AND ' : ''); - const chunk = this.set_array.slice(i,100); - - // Escape the index - index = this._protect_identifiers(index); - - for (let j in chunk) { - ids.push(chunk[j][index]); - - const keys = Object.keys(chunk[j]); - for (let k in keys) { - if (keys[k] != index) { - if (!when_then.hasOwnProperty(keys[k])) { - when_then[keys[k]] = []; - } - when_then[keys[k]].push(`WHEN ${index} = ${ids[j]} THEN ${chunk[j][keys[k]]} `); - } - } - } - - // Build the actual SQL statement - let sql = `UPDATE (${table}) SET `; - let cases = ''; - - for (let l in when_then) { - cases += l + ' = CASE '; - - for (let m in when_then[l]) { - cases += when_then[l][m]; - } - - cases += `ELSE ${l} END, `; - } - - sql += cases.substr(0, cases.length - 2); - sql += ' WHERE ' + where + index + ' IN (' + ids.join(',') + ')'; - - // Add query to batch - batches.push(sql); - } - - return batches; - } - - delete(table, where) { - return this._delete(table, where); - } - - _delete(table, where) { - if (typeof table == 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); - } - else { - this.where(where); - } - } - - return this._compile_delete(); - } - - get_compiled_select(table) { - if (typeof table !== 'undefined') { - this._track_aliases(table); - this.from(table); - } - else { - if (this.from_array.length == 0) { - throw new Error('You have not specified any tables to build a select statement with!'); - return this; - } - } - - return this._compile_select(); - } - - get_compiled_delete(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - - return this._compile_delete(); - } - - get_compiled_update(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - return this._compile_update(); - } - - get_compiled_insert(table) { - if (typeof table !== 'function') { - this._track_aliases(table); - this.from(table); - } - return this._compile_insert(); - } - - compile_select(table) { - return this.get_compiled_select(table); - } - - compile_delete(table) { - return this.get_compiled_delete(table); - } - - compile_update(table) { - return this.get_compiled_update(table); - } - - compile_insert(table) { - return this.get_compiled_insert(table); - } - - last_query() { - return this.last_query_string[0] || ''; - } - - escape(val) { - return this._qb_escape(val); - } - - empty_table(table) { - return this._empty_table(table); - } - - _empty_table(table) { - if (typeof table === 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('empty_table(): You have not specified a table to empty!'); - return ''; - } - - return "DELETE FROM " + this.from_array[0]; - } - - truncate(table) { - return this._truncate(table); - } - - _truncate(table) { - if (typeof table === 'string' && table.trim().length > 0) { - this._clear_array(this.from_array); - this.from(table); - } - - if (this.from_array.length === 0) { - throw new Error('truncate(): You have not specified a table to truncate!'); - return ''; - } - - return "TRUNCATE " + this.from_array[0]; - } } module.exports = QueryBuilder; diff --git a/drivers/query_builder.js b/drivers/query_builder.js new file mode 100755 index 0000000..653f54b --- /dev/null +++ b/drivers/query_builder.js @@ -0,0 +1,1331 @@ +class GenericQueryBuilder { + constructor() { + this.where_array = []; + this.where_in_array = []; + this.from_array = []; + this.join_array = []; + this.select_array = []; + this.set_array = []; // has to be array to work as reference + this.order_by_array = []; + this.group_by_array = []; + this.having_array = []; + this.limit_to = []; // has to be array to work as reference + this.offset_val = []; // has to be array to work as reference + this.join_clause = []; + this.last_query_string = []; // has to be array to work as reference + this.distinct_clause = []; // has to be array to work as reference + this.aliased_tables = []; + } + + // ------------------------------ GENERIC METHODS ------------------------------// + _array_values(item) { + const keys = Object.keys(item); + const length = keys.length; + const values = Array(length); + for (let i = 0; i < length; i++) { + values[i] = item[keys[i]]; + } + return values; + } + + // Simply setting all properties to [] causes reference issues in the parent class. + _clear_array(a, debug) { + if (debug === true) { + console.log("DEBUG before (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + if (Object.prototype.toString.call(a) === Object.prototype.toString.call({})) { + for (let key in a) { + if (a.hasOwnProperty(key)) { + delete a[key]; + } + } + } + else if (Array.isArray(a)) { + while (a.length > 0) { + a.pop(); + } + } + if (debug === true) { + console.log("DEBUG after (" + Object.prototype.toString.call(a) + "):"); + console.dir(a); + } + } + + _extract_having_parts(key, key_array) { + let m; + key = key.trim().replace(/\s+/g,' '); + const str_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s"([^"]+)"$/; + const sstr_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s'([^']+)'$/; + const num_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((?=.)([+-]?([0-9]*)(\.([0-9]+))?))$/; + const bool_condition = /^([^\s]+\s(<=|>=|<>|>|<|!=|=))+\s((true|false)+)$/; + + if (m = str_condition.exec(key)) { + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = sstr_condition.exec(key)) { + //console.log("Key has sstring value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = num_condition.exec(key)) { + //console.log("Key has numeric value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else if (m = bool_condition.exec(key)) { + //console.log("Key has boolean value"); + key_array[m[1]] = m[3]; + key = key_array; + } + else { + throw new Error("An invalid condition was supplied (" + key + ") in your having statement!"); + } + + return key_array; + } + + _prepare_for_limit_and_offset(item, type = 'limit') { + type = type.toLowerCase(); + + if (!/^(string|number)$/.test(typeof item)) { + throw new Error("Only integers or integers in the form of a string are allowed"); + } + + if (typeof item === 'string') { + item = item.trim(); + if (!/^\d+$/.test(item)) { + throw new Error(`The string you provided to ${type} by contains non-integer values--this isn't allowed.`); + } + // Force to an integer + item = parseInt(item); + } + + // Make sure the number is a good one + if (typeof item === 'number') { + // Check for NaN and Infinity + if (item !== +item || item === Infinity) { + throw new Error(`You have not provided a valid number to ${type} by!`); + } + + // Make sure it's positive + if (item < 0) { + throw new Error(`Only positive integers are allowed when ${(type == 'offset' ? 'offset' : 'limit')}ing SQL results!`); + } + + // Only allow integers + if (item % 1 !== 0) { + throw new Error(`You cannot ${type} a SQL resultset with a floating point value!`); + } + } + else { + throw new Error(`There was an unrecoverable error while parsing the value provided in your ${type} statement.`); + } + + return item; + } + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + _track_aliases(table) { + if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { + for (let i in table) { + const t = table[i]; + this._track_aliases(t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (table.indexOf(',') !== -1) { + return this._track_aliases(table.split(',')); + } + + // if a table alias is used we can recognize it by a space + if (table.indexOf(' ') !== -1) { + // if the alias is written with the AS keyword, remove it + table = table.replace(/\s+AS\s+/gi, ' '); + + // Grab the alias + const alias = table.slice(table.lastIndexOf(' ')).trim().replace(/`/g,'').replace(/\[([^\]]+)\]/, "$1"); + + // Store the alias, if it doesn't already exist + if (this.aliased_tables.indexOf(alias) == -1) { + this.aliased_tables.push(alias); + } + } + } + + _create_aliases_from_table(item) { + if (item.indexOf('.') !== -1) { + return item.split('.').reverse()[0]; + } + + return item; + } + + _has_operator(str) { + if (typeof str === 'string' && str.length > 0) { + const match = /(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sEXISTS|\sBETWEEN|\sLIKE|\sCASE|\sTHEN|\sWHEN|\sIN\s*\(|\s)/i.test(str.trim()); + if (!match) { + return false; + } + } + return true; + }; + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + _build_where_clause() { + let sql = ''; + if (this.where_array.length > 0) { + sql += " WHERE "; + } + sql += this.where_array.join(" "); + return sql; + }; + + _build_from_clause() { + let sql = ''; + if (this.from_array.length > 0) { + sql += " FROM "; + } else { + throw new Error("You have not provided any tables, views, or store procedures for this query!!"); + } + sql += this.from_array.join(', '); + return sql; + }; + + _build_join_string() { + let sql = ''; + sql += this.join_array.join(' '); + if (sql.length > 0) sql = ' ' + sql; + return sql; + }; + + _build_group_by_clause() { + if (this.group_by_array.length <= 0) return ''; + + let sql = ' GROUP BY '; + sql += this.group_by_array.join(', '); + return sql; + }; + + _build_having_clause() { + if (this.having_array.length <= 0) return ''; + + let sql = ' HAVING '; + sql += this.having_array.join(' '); + return sql; + }; + + _build_order_by_clause() { + if (this.order_by_array.length <= 0) return ''; + + let sql = ' ORDER BY '; + sql += this.order_by_array.join(', '); + + return sql; + }; + + reset_query(new_last_query,debug) { + this._clear_array(this.where_array, debug); + this._clear_array(this.where_in_array); + this._clear_array(this.from_array); + this._clear_array(this.join_array); + this._clear_array(this.select_array); + this._clear_array(this.set_array); + this._clear_array(this.order_by_array); + this._clear_array(this.group_by_array); + this._clear_array(this.having_array); + this._clear_array(this.limit_to); + this._clear_array(this.offset_val); + this._clear_array(this.join_clause); + this._clear_array(this.distinct_clause); + this._clear_array(this.aliased_tables); + + this._clear_array(this.last_query_string); + if (typeof new_last_query === 'string') { + this.last_query_string.push(new_last_query); + } + } + + where(key, value = null, escape) { + if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { + escape = (typeof escape === 'boolean' ? escape : value); + } + + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'AND '); + } + return this._where(key, value, 'AND ', escape); + } + + or_where(key, value=null, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + return this._where_in(key, value, false, 'OR '); + } + return this._where(key, value, 'OR ', escape); + } + + _where(key, value=null, type='AND ', escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + // Must be an object or a string + if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { + // If it's not an object, it must be a string + if (typeof key !== 'string') { + throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + } else { + // If it is a string, it can't be an empty one + if (key.length == 0) { + throw new Error("where(): No field name or query provided!"); + } + } + + // If it's a actual where clause string (with no paranthesis), + // not just a field name, split it into individual parts to escape it properly + if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { + const filters = key.split(/\s+(AND|OR)\s+/i); + if (filters.length > 1) { + const self = this; + const parse_statement = (statement, joiner) => { + const parsed = statement.match(/^([^<>=!]+)(<=|>=|<>|>|<|!=|=)(.*)$/); + if (parsed.length >= 4) { + const key = parsed[1].trim() + (parsed[2].trim() !== '=' ? ' ' + parsed[2].trim() : ''); + const value = parsed[3].trim().replace(/^((?:'|"){1})(.*)/, "$2").replace(/'$/,''); + if (joiner === null || /AND/i.test(joiner)) { + self.where(key, value, true); + } else { + self.or_where(key, value, true); + } + } + }; + parse_statement(filters.shift(), null); + while (filters.length > 0) { + const joiner = filters.shift(); + const statement = filters.shift(); + parse_statement(statement, joiner); + } + return this; + } + } + + const key_array = {}; + key_array[key] = value; + key = key_array; + } + + if (Object.keys(key).length == 0) { + throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); + } + + for (let k in key) { + let v = key[k]; + + if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { + return this._where_in(k, v, false, type, escape); + } + + const prefix = (this.where_array.length == 0 ? '' : type); + + if (v === null && !this._has_operator(k)) { + k += ' IS NULL'; + } + + if (v !== null) { + if (escape === true) { + k = this._protect_identifiers(k,escape); + v = ' ' + this._qb_escape(v); + } + + if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + v = ' ' + this._qb_escape(v); + } + + if (!this._has_operator(k)) { + k += ' ='; + } + } + else { + k = this._protect_identifiers(k,escape); + } + + if (v) { + this.where_array.push(prefix+k+v); + } + else { + this.where_array.push(prefix+k); + } + } + + return this; + } + + where_in(key, values, escape) { + return this._where_in(key, values, false, 'AND ', escape); + } + + or_where_in(key, values, escape) { + return this._where_in(key, values, false, 'OR ', escape); + } + + where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'AND ', escape); + } + + or_where_not_in(key, values, escape) { + return this._where_in(key, values, true, 'OR ', escape); + } + + _where_in(key='', values=[], not, type='AND ', escape) { + not = (not ? ' NOT' : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key !== 'string' || (typeof key === 'string' && key.length == 0)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); + } + + // Values must be an array... + if (!Array.isArray(values)) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); + } + else { + if (values.length == 0) { + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); + } + } + + for (let i in values) { + this.where_in_array.push(this._qb_escape(values[i])); + } + + const prefix = (this.where_array.length == 0 ? '' : type); + const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + this.where_array.push(where_in); + + // reset the array for multiple calls + this._clear_array(this.where_in_array); + return this; + } + + like(field, match, side) { + return this._like(field, match, 'AND ', side, ''); + } + + not_like(field, match, side) { + return this._like(field, match, 'AND ', side, ' NOT'); + } + + or_like(field, match, side) { + return this._like(field, match, 'OR ', side, ''); + } + + or_not_like(field, match, side) { + return this._like(field, match, 'OR ', side, ' NOT'); + } + + _like(field, match, type='AND ', side='both', not='') { + match = (/^(string|number|boolean)$/.test(typeof match) ? match : null); + + if (typeof field === 'string' && field.length == 0) { + throw new Error("like(): The field you provided is empty."); + } + else if (typeof field === 'object' && (field.length == 0 || Object.keys(field).length === 0)) { + throw new Error("like(): The object you provided is empty."); + } + else if (!/^(string|object)$/.test(typeof field)) { + throw new Error("like(): You have provided an invalid value as the first parameter. Only valid strings and objects are allowed."); + } + + if (Object.prototype.toString.call(field) !== Object.prototype.toString.call({})) { + if (match === null) { + throw new Error("like(): Since your first parameter is a string, your second param must a valid number, boolean, or string."); + } + + const field_array = {}; + field_array[field] = match; + field = field_array; + } + + for (let k in field) { + let like_statement; + const v = field[k]; + k = this._protect_identifiers(k.trim()); + + // Make sure value is only string, number, or boolean + if (!/^(string|number|boolean)$/.test(typeof v)) { + throw new Error("like(): You have provided an invalid value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + // If number, don't allow Infinity or NaN + else if (typeof v === 'number' && (v === Infinity || (v !== +v))) { + throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); + } + + if (side === 'none') { + like_statement = k + not + ' LIKE ' + "'" + v + "'"; + } + else if (side === 'before' || side === 'left') { + like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + } + else if (side === 'after' || side === 'right') { + like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + } + else if (side === 'both') { + like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + } + else { + throw new Error("like(): Invalid direction provided!"); + } + + this._where(like_statement, null, type, false); + } + + return this; + } + + from(from_param) { + if (!Array.isArray(from_param)) { + from_param = [from_param]; + } + for (let i in from_param) { + let val = from_param[i]; + + if (typeof val !== 'string' || val.trim() === '') continue; + + if (val.indexOf(',') !== -1) { + const objects = val.split(','); + for (let j in objects) { + const v = objects[j].trim(); + + this._track_aliases(v); + + this.from_array.push(this._protect_identifiers(v, true)); + } + } + else { + val = val.trim(); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers function to know whether to add a table prefix + this._track_aliases(val); + + this.from_array.push(this._protect_identifiers(val, true)); + } + } + + return this; + } + + select(select,escape) { + // First param must be a non-empty string or array + if (typeof select === 'string') { + select = select.trim(); + if (select.length == 0) { + throw new Error("Your select string is empty!"); + } + } + else if (Array.isArray(select)) { + if (select.length == 0) { + throw new Error("Your select array is empty!"); + } + } + else { + throw new Error("Select method requires a string or array to be passed in the first parameter!"); + } + + if (typeof escape !== 'boolean') escape = true; + + // Split statements out into individual ones by comma (unless there is a function or subquery with commas in it) + if (typeof select === 'string') { + if (select.indexOf(')') === -1) { + select = select.split(','); + } else { + if (escape === true) { + // Prevent it from trying to parse select statements with functions and if statements + if (/\w\s?\(/.test(select)) throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + + // Identify individual statements within select string + let m, open_paren_index, inner_parenthesis; + const reg = /\)/g; + while ((m = reg.exec(select) !== null)) { + open_paren_index = m.input.substring(0, m.index).lastIndexOf('('); + if (open_paren_index !== -1) { + inner_parenthesis = m.input.substring((open_paren_index + 1), m.index); + if (inner.parenthesis.indexOf(',') !== -1) { + throw new Error("Select statements with subqueries or functions cannot be escaped! Please escape manually and pass FALSE as the second paramter to the select method."); + break; + } + } + } + } + else { + select = [select]; + } + } + } + + for (let i in select) { + const val = select[i].trim(); + + if (val !== '') { + this.select_array.push(this._protect_identifiers(val, escape)); + } + } + return this; + } + + select_min(select,alias) { + return this._min_max_avg_sum(select, alias, 'MIN'); + } + + select_max(select,alias) { + return this._min_max_avg_sum(select, alias, 'MAX'); + } + + select_avg(select,alias) { + return this._min_max_avg_sum(select, alias, 'AVG'); + } + + select_sum(select,alias) { + return this._min_max_avg_sum(select, alias, 'SUM'); + } + + _min_max_avg_sum(select='', alias='', type='MAX') { + + if (typeof select !== 'string' || select === '') { + throw Error("Invalid query!"); + return this; + } + + type = type.toUpperCase(); + + if (['MAX', 'MIN', 'AVG', 'SUM'].indexOf(type) === -1) { + throw Error("Invalid function type!"); + return this; + } + + if (alias == '') { + alias = this._create_aliases_from_table(select.trim()); + } + + const sql = `${type}(${this._protect_identifiers(select.trim())}) AS ${alias}`; + + this.select_array.push(sql); + + return this; + } + + distinct(do_distinct) { + do_distinct = (typeof do_distinct !== 'boolean' ? true : do_distinct); + + if (do_distinct) { + this._clear_array(this.distinct_clause); + this.distinct_clause.push('DISTINCT '); + } + else { + this._clear_array(this.distinct_clause); + } + return this; + } + + group_by(by) { + if (typeof by === 'string') { + by = by.trim(); + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + by = by.split(','); + } + + if (!Array.isArray(by)) { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed."); + } + + if (by.length <= 0) { + throw new Error("You haven't provided any fields to group by!"); + } + + for (let key in by) { + if (typeof by[key] !== 'string') { + throw new Error("You have provided an invalid value to the group_by() method. Only strings and arrays of strings are allowed!"); + } + + const val = by[key].trim(); + + if (val !== '') { + this.group_by_array.push(this._protect_identifiers(val)); + } + } + return this; + } + + having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'AND ', escape); + } + + or_having(key, value, escape) { + escape = (typeof escape !== 'boolean' ? true : escape); + return this._having(key, value, 'OR ', escape); + } + + _having(key, value, type='AND ', escape) { + + let m; + let key_array = {}; + const key_is_object = Object.prototype.toString.call(key) === Object.prototype.toString.call({}); + const key_is_array = Array.isArray(key); + + if (/^(string|number|boolean)$/.test(typeof value)) { // if the value is a string, number, or boolean... + if (typeof key !== 'string' || /^\W+$/i.test(key)) { // if the key is not a string... + throw new Error("having(): The value you provided when calling having() will be ignored since the first parameter is not a single field provided in string form."); + } + key_array[key] = value; + key = key_array; + } + else if (typeof value === 'undefined' || value === null) { + if (key_is_object === false) { + if (typeof key === 'string') { + if (value === null) { + key_array[key] = null; + key = key_array; + } + else { + key = this._extract_having_parts(key, key_array); + } + } + else if (key_is_array === true) { + //console.log("Key is NOT a string"); + for (let i in key) { + if (typeof key[i] !== 'string') { + throw new Error("having(): You've provided an unparseable format to the having() method.."); + } + else { + key_array = this._extract_having_parts(key[i], key_array); + } + } + key = key_array; + } + } + } + else { + throw new Error(`Improper value supplied as the second parameter (${typeof value}) of the having() method.`); + } + + + for (let k in key) { + let v = key[k]; + const prefix = (this.having_array.length == 0 ? '' : type); + + if (escape === true) { + k = this._protect_identifiers(k); + } + + if (v === null) { + k += ' IS'; + } + else if (!this._has_operator(k)) { + k += ' ='; + } + + if (v != '') { + v = ' ' + this._qb_escape(v); + } + + this.having_array.push(prefix + k + v); + } + + return this; + } + + order_by(orderby, direction) { + let m; + direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); + + // Don't need to do anything below if the direction provided is random + if ((direction === 'random' || direction === 'rand' || direction === 'rand()')) { + this.order_by_array.push(this.rand_word); + return this; + } + + // Normalize orderby to be an array of items + if (!Array.isArray(orderby)) { + if (typeof orderby === 'string') { + orderby = orderby.trim(); + if (orderby.length == 0) { + throw new Error("You haven't provided any fields to order by!!"); + } + orderby = orderby.split(','); + } else if (!orderby && (/(newid|random|RAND|RAND\(\))/i.test(direction))) { + this.order_by_array.push(this.rand_word); + return this; + } + else { + throw new Error("No field provided to order by!"); + } + } + + if (orderby.length === 0) { + throw new Error("You haven't provided any fields to order by!"); + } + + for (let i in orderby) { + orderby[i] = orderby[i].replace(/\s+/g, ' '); + + if (m = orderby[i].match(/([^\s]+)\s+(ASC|DESC|RAND\(\)|NEWID\(\))/i)) { + if (m[2].trim() === this.rand_word) { + this.order_by_array.push(this.rand_word); + return this; + } + orderby[i] = {field: this._protect_identifiers(m[1].trim()), direction: m[2].trim().toUpperCase()}; + } else { + if (/^(ASC|DESC)$/i.test(direction) || direction === '') { + orderby[i] = {field: this._protect_identifiers(orderby[i].trim()), direction: (direction !== '' ? direction.toUpperCase() : 'ASC')}; + } else { + throw new Error("Invalid direction provided in order_by method! Only 'ASC', 'DESC', and 'RAND' are allowed!"); + } + } + + this.order_by_array.push(orderby[i].field + ' ' + orderby[i].direction); + } + + return this; + } + + limit(limit, offset) { + this._clear_array(this.limit_to); + this.limit_to.push(this._prepare_for_limit_and_offset(limit, 'limit')); + + if (offset !== undefined) { + return this.offset(offset); + } + + return this; + } + + offset(offset) { + this._clear_array(this.offset_val); + this.offset_val.push(this._prepare_for_limit_and_offset(offset, 'offset')); + return this; + } + + set(key, value, escape) { + escape = (typeof escape === 'boolean' ? escape : true); + + if (typeof key === 'string') { + // Convert key and value params to {key: value} + key = key.trim(); + if (key.length == 0) throw new Error("set(): Invalid field name provided!"); + + if (typeof value === 'undefined') + throw new Error("set(): First param was string but no value (second param) provided to set!"); + + const key_array = {}; + key_array[key] = value; + key = key_array; + } + else if (Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (Object.keys(key).length === 0) { + throw new Error("set(): The object you provided is empty."); + } + + if (typeof value !== 'undefined' & value !== null) { + throw new Error("set(): The value you provided in the second parameter will be ignored since you passed an object as the first parameter."); + } + } + else { + throw new Error("set(): First parameter must be a non-empty string or non-empty object! " + typeof key + " provided."); + } + + + // Add each key:value pair to the set_array + for (let i in key) { + let v = key[i]; + if (typeof v === 'undefined') continue; + + if (v instanceof Date) v = v.toString(); + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided! (provided: " + v + " (type: " + (typeof v) + ")"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + + // Escape the key to be DRY + const escaped_key = this._protect_identifiers(i, escape); + + // Build a temporary object with escaped key and val + const temp = {}; + if (escape === false) { + temp[escaped_key] = v; + } else { + temp[escaped_key] = this._qb_escape(v); + } + + // Determine if this key has already been set + let found_index = null; + for (let j in this.set_array) { + if (this.set_array[j].hasOwnProperty(escaped_key)) { + found_index = j; + break; + } + } + + // Update value if key already set or add if not found + if (found_index !== null) { + this.set_array[found_index] = temp; + } else { + this.set_array.push(temp); + } + } + + //console.dir(this.set_array); + + return this; + } + + + // ---------------------------- SQL EXEC TOOLS ----------------------------// + insert(table, set, ignore, suffix) { + return this._insert(table, set, ignore, suffix); + } + + _insert(table, set, ignore, suffix) { + table = table || '' + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("insert(): Invalid data provided to insert into database!"); + } + + if (Array.isArray(set)) { + return this.insert_batch(table, set, ignore, suffix); + } + + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } + } + + if (typeof table !== 'string') { + throw new Error("insert(): Table parameter must be a string!"); + } + + table = table.trim(); + + if (table !== '' && !(/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/).test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table === '') { + if (this.from_array.length == 0) throw new Error('insert(): No tables set to insert into!'); + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + return this._compile_insert(ignore, suffix); + } + + insert_ignore(table, set, suffix) { + return this._insert_ignore(table, set, suffix); + } + + _insert_ignore(table, set, suffix) { + return this.insert(table, set, true, suffix); + } + + insert_batch(table,set=null,ignore,suffix) { + return this._insert_batch(table, set, ignore, suffix); + } + + get(table) { + return this._get(table); + } + + _get(table) { + if (typeof table === 'string' || Array.isArray(table)) { + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to select from!'); + } + } + return this._compile_select(this); + } + + get_where(table=null, where=null) { + return this._get_where(table, where); + } + + _get_where(table=null, where=null) { + // Check if table is either a string or array + if (typeof table !== 'string' && !Array.isArray(table)) + throw new Error('You must specify a table or array of tables in the first parameter of get_where()'); + + // If table is a string, make sure it's not empty + if (typeof table === 'string' && table.trim().length <= 0) + throw new Error("Invalid table string specified!"); + + // If table is array, make sure there are only strings in there and that they are non-empty strings + if (Array.isArray(table)) { + for (let v in table) { + if (typeof v !== 'string' || (typeof v === 'string' && v.trim().length <= 0)) { + throw new Error("Invalid table string specified in array of tables!"); + break; + } + } + } + + this.from(table); + + if (where === null || typeof where !== 'object' || Object.keys(where).length === 0) + throw new Error('You must supply an object of field:value pairs in the second parameter of get_where()'); + + this.where(where); + + return this._compile_select(); + } + + count(table) { + return this._count(table); + } + + update(table, set, where=null) { + return this._update(table, set, where); + } + + _update(table, set, where=null) { + + table = table || ''; + set = set || null; + + // Send to batch_update if the data param is an array + if (Array.isArray(set)) { + let index = null; + if (set.length > 0) { + if (Object.prototype.toString.call(set[0]) === Object.prototype.toString.call({})) { + index = Object.keys(set[0])[0]; + } + } + if (index) { + return this.update_batch(table, set, index, where); + } else { + throw new Error("update(): update_batch attempted but could not ascertain a valid index to use from the dataset provided."); + } + } + + // If set is a number, boolean, a non-empty string, or regex, fail + if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { + throw new Error("update(): Invalid data provided to update database!"); + } + + // If data object was provided, set it + if (set !== null) { + if (Object.prototype.toString.call(set) === Object.prototype.toString.call({}) && Object.keys(set).length > 0) { + this.set(set); + } else { + throw new Error("update(): Empty data object provided. This is not allowed."); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update(): You must set some field value pairs to update using the set method or via an object passed to the second parameter of the update method!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('update(): No tables set to update!'); + } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where !== null) { + this.where(where); + } + + return this._compile_update(); + } + + update_batch(table='', set=null, index=null, where=null) { + return this._update_batch(table, set, index, where); + } + + delete(table, where) { + return this._delete(table, where); + } + + _delete(table, where) { + if (typeof table == 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } + + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { + if (Object.keys(where).length == 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + } + else { + this.where(where); + } + } + + return this._compile_delete(); + } + + get_compiled_select(table) { + if (typeof table !== 'undefined') { + this._track_aliases(table); + this.from(table); + } + else { + if (this.from_array.length == 0) { + throw new Error('You have not specified any tables to build a select statement with!'); + return this; + } + } + + return this._compile_select(); + } + + get_compiled_delete(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + + return this._compile_delete(); + } + + get_compiled_update(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_update(); + } + + get_compiled_insert(table) { + if (typeof table !== 'function') { + this._track_aliases(table); + this.from(table); + } + return this._compile_insert(); + } + + compile_select(table) { + return this.get_compiled_select(table); + } + + compile_delete(table) { + return this.get_compiled_delete(table); + } + + compile_update(table) { + return this.get_compiled_update(table); + } + + compile_insert(table) { + return this.get_compiled_insert(table); + } + + last_query() { + return this.last_query_string[0] || ''; + } + + escape(val) { + return this._qb_escape(val); + } + + empty_table(table) { + return this._empty_table(table); + } + + _empty_table(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('empty_table(): You have not specified a table to empty!'); + return ''; + } + + return "DELETE FROM " + this.from_array[0]; + } + + truncate(table) { + return this._truncate(table); + } + + _truncate(table) { + if (typeof table === 'string' && table.trim().length > 0) { + this._clear_array(this.from_array); + this.from(table); + } + + if (this.from_array.length === 0) { + throw new Error('truncate(): You have not specified a table to truncate!'); + return ''; + } + + return "TRUNCATE " + this.from_array[0]; + } + + _update_batch(table='', set=null, index=null, where=null) { + // Make sure an index has been provided! + if (typeof index !== 'string' || (typeof index === 'string' && index.length === 0)) { + throw new Error("update_batch(): Invalid index provided to generate batch update query!"); + } + + // Check to make sure we have a dataset + if (!Array.isArray(set)) { + throw new Error("update_batch(): Array of object expected and non-array received."); + } + + // Make sure our dataset isn't emtpy + if (set.length === 0) { + throw new Error("update_batch(): You must supply some data to batch update the table with."); + } + + // Make sure each item in the dataset has the specified index and then add data to set_array + //console.dir(set); + for (let i in set) { + const clean = {}; + const row = set[i]; + if (Object.prototype.toString.call(row) === Object.prototype.toString.call({}) && Object.keys(row).length > 0) { + const keys = Object.keys(row); + if (keys.indexOf(index) !== -1) { + for (let j in row) { + clean[this._protect_identifiers(j)] = this._qb_escape(row[j]); + } + this.set_array.push(clean); + } + } else { + throw new Error("update_batch(): You have supplied an invalid object to batch update!"); + } + } + + // Fail if, at this point, nothing has been set + if (this.set_array.length == 0) { + throw new Error("update_batch(): You must provide some data to batch update!"); + } + + // NOTE: If falsy table provided, table will have been converted to an empty string... + if (typeof table !== 'string') { + throw new Error("update(): Table parameter must be a string!"); + } + + table = table.trim(); + + // Table name must be in a legitimate format + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("update(): You have not set any tables to update!"); + } + + // If table not supplied, it must have been supplied already + if (table == '') { + if (this.from_array.length == 0) { + throw new Error('No tables set to insert into!'); + } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + + // Set where array if a where statement was provided + if (where != null) { + this.where(where); + } + + // Verify there is a table in the from_array + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build batch UPDATE query with!"); + } + throw new Error("You have provided too many tables to build batch UPDATE query with!"); + } + + table = this.from_array.toString(); + + + // Limit to 100 rows per batch + const batches = []; + for (let i = 0, total = this.set_array.length; i < total; i += 100) { + const when_then = {}; + const ids = []; + const where = (this.where_array.length > 0 ? this._build_where_clause() + ' AND ' : ''); + const chunk = this.set_array.slice(i,100); + + // Escape the index + index = this._protect_identifiers(index); + + for (let j in chunk) { + ids.push(chunk[j][index]); + + const keys = Object.keys(chunk[j]); + for (let k in keys) { + if (keys[k] != index) { + if (!when_then.hasOwnProperty(keys[k])) { + when_then[keys[k]] = []; + } + when_then[keys[k]].push(`WHEN ${index} = ${ids[j]} THEN ${chunk[j][keys[k]]} `); + } + } + } + + // Build the actual SQL statement + let sql = `UPDATE (${table}) SET `; + let cases = ''; + + for (let l in when_then) { + cases += l + ' = CASE '; + + for (let m in when_then[l]) { + cases += when_then[l][m]; + } + + cases += `ELSE ${l} END, `; + } + + sql += cases.substr(0, cases.length - 2); + sql += ` WHERE ${where + index} IN (${ids.join(',')})`; + + // Add query to batch + batches.push(sql); + } + + return batches; + } +} + +module.exports = GenericQueryBuilder; diff --git a/test/mssql/01-tests-from.js b/test/mssql/01-tests-from.js new file mode 100755 index 0000000..bd9cdf6 --- /dev/null +++ b/test/mssql/01-tests-from.js @@ -0,0 +1,123 @@ +const should = require('chai').should(); +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('from()', () => { + it('should exist', () => { + should.exist(qb.from); + }); + it('should be a function', () => { + qb.from.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('from_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.from_array.should.be.empty; + }); + it('should add an item to an array and escape it properly', () => { + qb.from('universe'); + qb.from_array.should.eql(['[universe]']); + }) + it('should accept a comma-delimited string of items and trim and escape each properly', () => { + qb.reset_query(); + qb.from('universe,galaxy , star_system, planet'); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should have an empty array after resetting', () => { + qb.reset_query(); + qb.from_array.should.be.empty; + }); + it('should be allowed to be called multiple times to add multiple items to the from array', () => { + qb.reset_query(); + qb.from('universe').from('galaxy').from('star_system').from('planet'); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should accept an array of items and add them individually to the from array', () => { + qb.reset_query(); + qb.from(['universe','galaxy','star_system','planet']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should not double-escape an item', () => { + qb.reset_query(); + qb.from('`do`'); + qb.from_array.should.eql(['`do`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe`','`galaxy`','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe`','galaxy','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should allow for aliases and it should escape them properly', () => { + qb.reset_query(); + qb.from('universe u'); + qb.from_array.should.eql(['`universe` `u`']); + }); + it('should allow for the word AS to be used to alias an item', () => { + qb.reset_query(); + qb.from('universe as u'); + qb.from_array.should.eql(['`universe` as `u`']); + }); + it('should allow for an array of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u', 'galaxy g']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { + qb.reset_query(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe` u', 'galaxy `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should add aliases to alias-tracking array', () => { + qb.reset_query(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.aliased_tables.should.eql(['u','g']); + }); + it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u, galaxy g']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for namespacing in field name (host.db.table)', () => { + qb.reset_query(); + qb.from('star_system.planet'); + qb.from_array.should.eql(['`star_system`.`planet`']); + + qb.reset_query(); + qb.from('galaxy.star_system.planet'); + qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system planet'); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system as planet'); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); + }); + it('should accept but ignore empty strings and empty strings within arrays', () => { + qb.reset_query(); + qb.from(''); + qb.from_array.should.be.empty; + + qb.reset_query(); + qb.from(['','']); + qb.from_array.should.be.empty; + + qb.reset_query(); + qb.from(['','foobar']); + qb.from_array.should.eql(['`foobar`']); + }); +}); From 0e28ac910db706a9c3475ed0b64ef58d3532d15d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 11 Jul 2018 18:16:46 -0400 Subject: [PATCH 157/200] Hopefully getting the escaping to work properly. Slowly but surely. --- README.md | 7 +- drivers/mssql/query_builder.js | 682 +++++++++++++-------------------- drivers/mysql/query_builder.js | 78 +--- drivers/query_builder.js | 227 ++++++++++- 4 files changed, 495 insertions(+), 499 deletions(-) diff --git a/README.md b/README.md index bf73e53..2ec038d 100755 --- a/README.md +++ b/README.md @@ -496,7 +496,7 @@ qb.from('groups g').select('u.id, u.name, u, description, g.name as group_name') ### JOIN #### .join(table, relation[,direction]) -This SQL command is used query multiple tables related and connected by keys and get a single resultset. +This SQL command is used query multiple tables related and connected by keys and get a single result set. Parameter | Type | Default | Description :-------- | :------ | :------- | :-------------------------------------------------- @@ -515,6 +515,11 @@ escape | Boolean | true | TRUE: Escape table name and conditions; FALSE: The table/view and the relationship of it to the main table/view (see: `.from()`) must be specified. The specific type of join defaults to "left" if none is specified (although it is recommended to always supply this value for readability). Multiple function calls can be made if you need several joins in one query. Aliases can (and should) be provided and they will be escaped properly. +**Warning about complex relationship clauses** +This library currently does not support complex/nested ON clauses passed to the `relation` (second) parameter. You can supply multiple statements as long as they are not nested within parentheses. If you need to use a complex relationship clause, please make sure to escape those parts manually and pass `false` to the `escape` (fourth) parameter. See examples below for more details. + +If anyone would like to add this capability, please submit a pull request! + **Examples** If no direction is specified, "left" will be used: diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index 526a63d..c22a118 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -1,421 +1,261 @@ -const GenericQueryBuilder = require('../query_builder.js'); - -class QueryBuilder extends GenericQueryBuilder { - constructor() { - super(); - this.rand_word = 'NEWID()'; - } - - // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - _escape_identifiers(item = '*') { - if (item === '*') return item; - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (let i in item) { - item[i] = this._escape_identifiers(item[i]); - } - return item; - } - else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item.indexOf('(') !== -1) { - return item; - } - - let str; - if (item.indexOf('.*') !== -1) { - str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); - } - else { - str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); - } - - // remove duplicates if the user already included the escape - return str.replace(/[`]+/g,'`'); - }; - - _protect_identifiers(item, protect_identifiers) { - if (item === '') return item; - - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - const escaped_array = {}; - - for (let k in item) { - const v = item[k]; - escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v); - } - return escaped_array; - } - - // Make sure item is a string... - if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); - - // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/g, ' '); - - // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - const has_alias = item.lastIndexOf(')'); - let alias; - if (has_alias >= 0) { - alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = this._escape_identifiers(alias); - if (alias != '') - alias = ' AS ' + alias; - item = item.substr(0, has_alias + 1); - } else { - alias = ''; - } - - return item + alias; - } - - let alias = ''; - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (/\sAS\s/ig.test(item)) { - const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - alias = (protect_identifiers ? item.substr(alias_index, 4) + this._escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0, alias_index); - } - else if (item.indexOf(' ') !== -1) { - const alias_index = item.indexOf(' '); - - alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - const parts = item.split('.'); - const first_seg = parts[0].trim().replace(/`/g,''); - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (this.aliased_tables.indexOf(first_seg) !== -1) { - if (protect_identifiers === true) { - for (let key in parts) { - const val = parts[key]; - if (val !== '*') { - parts[key] = this._escape_identifiers(val); - } - } - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - } - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - }; - - _qb_escape(str) { - const mysql = require('mysql'); - - if (typeof str === 'boolean') { - str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { - str *= 1; - } else { - str = mysql.escape(str); - } - - return str; - }; - - // ---------------------------- SQL BUILD TOOLS ----------------------------// - _build_limit_clause(sql, limit, offset) { - if (!limit) return sql; - - sql += ' '; - - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; - } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; - }; - - _compile_delete() { - if (this.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - this.from_array = this.from_array.slice(0,1); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - - _compile_insert(ignore, suffix='') { - const keys = []; - const values = []; - - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (this.from_array.length === 1) { - const table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; - }; - - _compile_select() { - const distinct_clause = this.distinct_clause[0] || ''; - let sql = 'SELECT ' + distinct_clause; - if (this.select_array.length === 0) { - sql += '*'; - } else { - sql += this.select_array.join(', '); - } - - sql += this._build_from_clause() - + this._build_join_string() - + this._build_where_clause() - + this._build_group_by_clause() - + this._build_having_clause() - + this._build_order_by_clause(); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - sql = this._build_limit_clause(sql, limit_to, offset_val); - return sql; - }; - - _compile_update() { - const valstr = []; - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - valstr.push(key + ' = ' + val); - } - - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build UPDATE query with!"); - return ''; - } - throw new Error("You have provided too many tables to build UPDATE query with!"); - return ''; - } - - const table = this.from_array.toString(); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - let sql = `UPDATE (${table}) SET ${valstr.join(', ')}`; - sql += this._build_where_clause(); - sql += this._build_order_by_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - - join(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - this._track_aliases(table); - - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while (k = regex.exec(relation)) { - matches.push(k); - } - - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); - } - else if (!this._has_operator(relation)) { - relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = this._protect_identifiers(table,true); - } - - const join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - } - - _insert_batch(table,set=null,ignore,suffix) { - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); - table = table.trim(); - - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - if (!Array.isArray(set)) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); - } - - for (let key in set) { - const row = set[key]; - const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (let i in row) { - const v = row[i]; - - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - } - } - } - - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } - - const map = []; - const columns = []; - - // Obtain all the column names - for (let key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(this._protect_identifiers(key)); - } - } - } - - for (let i = 0; i < set.length; i++) { - (i => { - const row = []; - for (let key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(this._qb_escape(set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); - } - map.push('(' + row.join(', ') + ')'); - })(i); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; - } - - _count(table) { - if (typeof table === 'string') { - this.from(table); - } - - const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') - + this._build_from_clause() - + this._build_join_string() - + this._build_where_clause(); - - return sql; - } -} - - -module.exports = QueryBuilder; +const GenericQueryBuilder = require('../query_builder.js'); + +class QueryBuilder extends GenericQueryBuilder { + constructor() { + super(); + this.rand_word = 'NEWID()'; + this.escape_char = ['[',']']; + this.condition_rgx = /([\[\]\w\."\s-]+)(\s*[^\"\[`'\w-]+\s*)(.+)/i; + this.multi_condition_rgx = /\s(OR|AND)\s/; + } + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + + _qb_escape(str) { + const mysql = require('mysql'); + + if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { + str *= 1; + } else { + str = mysql.escape(str); + } + + return str; + } + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + _build_limit_clause() { + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; + + // If there's no limit provided or there IS an offset + // provided, skip building limit clause + if (!limit_to || offset_val) return ''; + + return `TOP ${limit_to}`; + } + + _build_offset_clause() { + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; + + let offset_clause = ''; + + // No offset provided + if (!offset_val) return offset_clause; + + // There has to be an order by clause. This is a default. + if (this.order_by_array.length === 0) { + offset_clause += ' ORDER BY (SELECT NULL)'; + } + + offset_clause += ` OFFSET ${offset_val} ROWS`; + if (limit_to) offset += ` FETCH NEXT ${limit_to} ROWS ONLY`; + } + + // ---------------------------- SQL EXEC TOOLS ----------------------------// + _compile_delete() { + if (this.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } + + this.from_array = this.from_array.slice(0,1); + + const sql = `DELETE ${this._build_limit_clause()} ` + + this._build_from_clause() + + this._build_where_clause(); + + return sql; + } + + _compile_insert(ignore=false, suffix='') { + const keys = []; + const values = []; + + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + const verb = 'INSERT '; + + if (this.from_array.length === 1) { + const table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; + } + + return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})`; + } + + _compile_select() { + const distinct_clause = this.distinct_clause[0] || ''; + let sql = `SELECT ${distinct_clause}`; + + const limit_clause = this._build_limit_clause(); + if (limit_clause) sql += `${limit_clause} `; + + if (this.select_array.length === 0) { + sql += '*'; + } else { + sql += this.select_array.join(', '); + } + + sql += this._build_from_clause() + + this._build_join_string() + + this._build_where_clause() + + this._build_group_by_clause() + + this._build_having_clause() + + this._build_order_by_clause() + + this._build_offset_clause(); + + return sql; + } + + _compile_update() { + const valstr = this.set_array.map(v => { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; + return `${key} = ${val}`; + }).join(', '); + + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + const table = this.from_array.toString(); + + let sql = 'UPDATE '; + + if (this.order_by_array.length > 0) { + sql += `${table} SET ${valstr}${this._build_where_clause()}`; + } else { + const limit_clause = this._build_limit_clause(); + const from_clause = (limit_clause ? 'FROM *' : `${limit_clause} FROM *`); + sql += `[t] SET ${valstr} FROM (SELECT ${from_clause})` + + this._build_where_clause() + + this._build_order_by_clause() + + `) [t]`; + } + + return sql; + } + + insert(table='', set='', ignore=false, suffix='') { + if (ignore) throw new Error("insert(): INSERT IGNORE is currently unsupported on the MSSQL driver."); + if (suffix) throw new Error("insert(): 'on_dupe' string (4th parameter) is currently unsupported on the MSSQL driver."); + super._insert(table, set, false, suffix); + } + + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); + + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); + } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + + if (!Array.isArray(set)) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (let key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (let i in row) { + const v = row[i]; + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } + } + } + + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } + + const map = []; + const columns = []; + + // Obtain all the column names + for (let key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(this._protect_identifiers(key)); + } + } + } + + for (let i = 0; i < set.length; i++) { + (i => { + const row = []; + for (let key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(this._qb_escape(set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); + } + map.push('(' + row.join(', ') + ')'); + })(i); + } + + const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); + return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + } + + _count(table) { + if (typeof table === 'string') { + this.from(table); + } + + const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') + + this._build_from_clause() + + this._build_join_string() + + this._build_where_clause(); + + return sql; + } +} + +module.exports = QueryBuilder; diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index b4a4729..5637ee9 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -4,6 +4,9 @@ class QueryBuilder extends GenericQueryBuilder { constructor() { super(); this.rand_word = 'RAND()'; + this.escape_char = '`'; + this.condition_rgx = /([\[\]\w\."\s-]+)(\s*[^\"\[`'\w-]+\s*)(.+)/i; + this.multi_condition_rgx = /\sAND\s|\sOR\s/ig; } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // @@ -154,6 +157,7 @@ class QueryBuilder extends GenericQueryBuilder { return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; }; + // ---------------------------- SQL EXEC TOOLS ----------------------------// _compile_delete() { if (this.from_array.length === 0) { throw new Error('You have not specified any tables to delete from!'); @@ -248,80 +252,6 @@ class QueryBuilder extends GenericQueryBuilder { return this._build_limit_clause(sql, limit_to, offset_val); }; - join(table, relation, direction, escape) { - if (typeof table !== 'string' || table.trim().length == 0) { - throw new Error("You must provide a table, view, or stored procedure to join to!"); - } - - relation = (typeof relation === 'string' && relation.trim().length != 0 ? relation.trim() : ''); - direction = (typeof direction === 'string' && direction.trim().length != 0 ? direction.trim() : ''); - escape = (typeof escape === 'boolean' ? escape : true); - - const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; - - if (direction != '') { - direction = direction.toUpperCase().trim(); - if (valid_directions.indexOf(direction) === -1) { - throw new Error("Invalid join direction provided as third parameter."); - } - if (relation === '') { - throw new Error("You must provide a valid condition to join on when providing a join direction."); - } - - direction += ' '; - } - - this._track_aliases(table); - - // Split multiple conditions - const regex = /\sAND\s|\sOR\s/ig; - const m = relation.match(regex); - const matches = []; - let k, temp, temp_match, match; - if (escape === true && m) { - while (k = regex.exec(relation)) { - matches.push(k); - } - - let new_relation = ''; - matches.push(['']); - matches[(matches.length - 1)].index = relation.length; - for (let j = 0, c = matches.length, s = 0; j < c; s = matches[j].index + matches[j][0].length, j++) { - temp = relation.substr(s, matches[j].index - s); - temp_match = temp.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i); - new_relation += (temp_match ? this._protect_identifiers(temp_match[1], escape) + temp_match[2] + this._protect_identifiers(temp_match[3], escape) : temp); - new_relation += matches[j][0]; - } - - relation = ' ON ' + new_relation; - } - - // Split apart the condition and protect the identifiers - else if (escape === true && /([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i.test(relation)) { - match = relation.match(/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i) - relation = ' ON ' + this._protect_identifiers(match[1], true) + match[2] + this._protect_identifiers(match[3], true); - } - else if (!this._has_operator(relation)) { - relation = ' USING (' + (escape ? this._escape_identifiers(relation) : relation) + ')'; - } - else if (relation && escape === false) { - relation = ' ON ' + relation; - } - else { - relation = ' '; - } - - // Do we want to escape the table name? - if (escape === true) { - table = this._protect_identifiers(table,true); - } - - const join = direction + 'JOIN ' + table + relation; - - this.join_array.push(join); - return this; - } - _insert_batch(table,set=null,ignore,suffix) { const orig_table = table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 653f54b..181cc8f 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -1,5 +1,7 @@ class GenericQueryBuilder { constructor() { + this.reserved_identifiers = ['*']; + this.where_array = []; this.where_in_array = []; this.from_array = []; @@ -127,6 +129,167 @@ class GenericQueryBuilder { } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + /** + * Escape the SQL Identifiers + * + * This function escapes column and table names + * + * @param mixed item Identifier to escape + * @param bool split Whether to split identifiers when a dot is encountered + * @return mixed + */ + _escape_identifiers(item = '*', split=true) { + if (item === '*') return item; + + // If object is supplied, escape the value of each key + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + for (let i in item) { + item[i] = this._escape_identifiers(item[i]); + } + return item; + } + + // Avoid breaking functions and literal values inside queries + else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item.indexOf('(') !== -1) { + return item; + } + + let str; + let escape_chars = []; + + if (Array.isArray(this.escape_char)) { + escape_chars = this.escape_char; + } else { + escape_chars[0] = escape_chars[1] = this.escape_char; + } + + this.reserved_identifiers.forEach(v => { + if (item.indexOf(v) === -1) { + return item.replace(RegExp(`${escape_chars[0]}?([^${escape_chars[1]}\.]+)${escape_chars[1]}?\.`, 'i'), `${escape_chars[0]}$1${escape_chars[1]}.`); + } + }); + + const dot = (split !== FALSE ? '\.' : ''); + return item.replace(RegExp(`${escape_chars[0]}?([^${escape_chars[1]}${dot}]+)${escape_chars[1]}?(\.)?`, 'i'), `${escape_chars[0]}$1${escape_chars[1]}$2`); + } + + /** + * Protect Identifiers + * + * Takes a column or table name (optionally with an alias) and inserts + * the table prefix onto it. Some logic is necessary in order to deal with + * column names that include the path. Consider a query like this: + * + * SELECT hostname.database.table.column AS c FROM hostname.database.table + * + * Or a query with aliasing: + * + * SELECT m.member_id, m.member_name FROM members AS m + * + * Since the column name can include up to four segments (host, DB, table, column) + * or also have an alias prefix, we need to do a bit of work to figure this out and + * insert the table prefix (if it exists) in the proper position, and escape only + * the correct identifiers. + * + * @param string item + * @param bool prefix_single + * @param mixed protect_identifiers + * @param bool field_exists + * @return string + */ + _protect_identifiers(item, prefix_single=false, protect_identifiers = null, field_exists = true) { + if (item === '') return item; + + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + const escaped_array = {}; + + for (let k in item) { + const v = item[k]; + escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v, prefix_single, protect_identifiers, field_exists); + } + return escaped_array; + } + + // Make sure item is a string... + if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); + + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + const match = /[\(\)\']{1}/.exec(str) + if (match && match.index !== item.length) return item; + // const parantheses_regex = /^([^\(]+)\(([^\)]+)\)\s+(?:as\s*)?([^\s]+)(?:\s*(.*))?$/; + // if (parantheses_regex.test(item)) { + // return item.replace(parantheses_regex, `$1($2) AS ${this._escape_identifiers("$3")} $4`); + // } + + // Convert tabs or multiple spaces into single spaces + item = item.trim().replace(/\s+/g, ' '); + + let alias = ''; + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (/\sAS\s/i.test(item)) { + const offset = item.indexOf(item.match(/\sAS\s/i)[0]); + alias = (protect_identifiers ? item.substr(offset, 4) + this._escape_identifiers(item.substr(offset + 4), false) : item.substr(offset)); + item = item.substr(0, alias_index); + } + else if (item.indexOf(' ') !== -1) { + const alias_index = item.indexOf(' '); + + alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1), false) : item.substr(alias_index)); + item = item.substr(0,alias_index); + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + const parts = item.split('.'); + let first_seg = parts[0].trim(); + + if (Array.isArray(this.escape_char)) { + const rgx_esc = (str) => str.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); + let rgx = `^${rgx_esc(start)}([^${rgx_esc(end)}]+)${rgx_esc(end)}$`; + first_seg = first_seg.replace(new RegExp(rgx), "$1"); + } else { + first_seg = first_seg.replace(/`/g,''); + } + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (this.aliased_tables.indexOf(first_seg) !== -1) { + if (protect_identifiers === true) { + parts = parts.map((v,i) => { + if (!this.reserved_identifiers.includes(v)) { + return this._escape_identifiers(v); + return v; + } + }); + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = this._escape_identifiers(item); + } + + return item + alias; + } + + if (protect_identifiers === true) { + item = this._escape_identifiers(item); + } + + return item + alias; + } + _track_aliases(table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { for (let i in table) { @@ -743,6 +906,65 @@ class GenericQueryBuilder { return this; } + join(table='', relation='', direction='', escape=true) { + if (typeof table !== 'string' || table.trim().length === 0) { + throw new Error("You must provide a table, view, or stored procedure to join to!"); + } + + relation = (typeof relation === 'string' && relation.trim().length !== 0 ? relation.trim() : ''); + direction = (typeof direction === 'string' && direction.trim().length !== 0 ? direction.trim() : ''); + escape = (typeof escape === 'boolean' ? escape : true); + + const valid_directions = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER']; + + if (direction) { + direction = direction.toUpperCase().trim(); + if (!valid_directions.includes(direction)) { + throw new Error("Invalid join direction provided as third parameter."); + } + if (!relation) { + throw new Error("You must provide a valid condition to join on when providing a join direction."); + } + } + + // Keep track of the table alias (if one is provided) + this._track_aliases(table); + + // How to split a condition (foo=bar) into its consituent parts + + // Find all the conditions and protect their identifiers + if (escape === true && this.multi_condition_rgx.test(relation)) { + const new_relation = relation.split(this.multi_condition_rgx).map((v,i) => { + if (i % 2 !== 0) return v; + return v.replace(this.condition_rgx, `${this._protect_identifiers("$1", escape)} $2 ${this._protect_identifiers("$3", escape)}`); + }).join(' '); + + relation = `ON ${new_relation}`; + } + + // Split apart the condition and protect the identifiers + else if (escape === true && statement_regex.test(relation)) { + relation = 'ON ' + v.replace(this.condition_rgx, `${this._protect_identifiers("$1", escape)} $2 ${this._protect_identifiers("$3", escape)}`); + } + else if (!this._has_operator(relation)) { + relation = `USING (${(escape ? this._escape_identifiers(relation) : relation)})`; + } + else if (relation && escape === false) { + relation = `ON ${relation}`; + } + else { + relation = ''; + } + + // Do we want to escape the table name? + if (escape === true) { + table = this._protect_identifiers(table,true); + } + + this.join_array.push(`${direction} JOIN ${table} ${relation}`); + return this; + } + order_by(orderby, direction) { let m; direction = (typeof direction === 'string' ? direction.toLowerCase().trim() : ''); @@ -892,12 +1114,11 @@ class GenericQueryBuilder { // ---------------------------- SQL EXEC TOOLS ----------------------------// - insert(table, set, ignore, suffix) { + insert(table='', set='', ignore=false, suffix='') { return this._insert(table, set, ignore, suffix); } - _insert(table, set, ignore, suffix) { - table = table || '' + _insert(table='', set='', ignore=false, suffix='') { ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); From 9df3758c1076fcf371785bff878e81fb50e94797 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Tue, 17 Jul 2018 19:48:21 -0400 Subject: [PATCH 158/200] Trying to finish up the unit tests for the MSSQL driver. --- README.md | 6 +- drivers/mssql/query_builder.js | 99 ++-- drivers/mysql/query_builder.js | 599 +++++++++------------ drivers/query_builder.js | 317 ++++++----- package.json | 6 +- test/mssql/01-tests-distinct.js | 22 +- test/mssql/01-tests-from.js | 228 ++++---- test/mssql/01-tests-get.js | 202 +++++++ test/mssql/01-tests-groupby.js | 52 ++ test/mssql/01-tests-having.js | 112 ++++ test/mssql/01-tests-join.js | 124 +++++ test/mssql/01-tests-like.js | 189 +++++++ test/mssql/01-tests-limit.js | 87 +++ test/mssql/01-tests-offset.js | 60 +++ test/mssql/01-tests-orderby.js | 131 +++++ test/mssql/01-tests-select.js | 207 +++++++ test/mssql/01-tests-set.js | 104 ++++ test/mssql/01-tests-where.js | 174 ++++++ test/mssql/01-tests-where_in.js | 177 ++++++ test/mssql/02-tests-compilation_methods.js | 70 +++ test/mssql/03-tests-count.js | 56 ++ test/mssql/03-tests-delete.js | 90 ++++ test/mssql/03-tests-empty_table.js | 51 ++ test/mssql/03-tests-insert_batch.js | 104 ++++ test/mssql/03-tests-update.js | 126 +++++ test/mysql/01-tests-from.js | 228 ++++---- test/mysql/01-tests-get.js | 364 ++++++------- test/mysql/01-tests-groupby.js | 86 +-- test/mysql/01-tests-join.js | 218 ++++---- test/mysql/01-tests-where.js | 308 +++++------ test/mysql/03-tests-count.js | 2 +- 31 files changed, 3319 insertions(+), 1280 deletions(-) create mode 100755 test/mssql/01-tests-get.js create mode 100755 test/mssql/01-tests-groupby.js create mode 100755 test/mssql/01-tests-having.js create mode 100755 test/mssql/01-tests-join.js create mode 100755 test/mssql/01-tests-like.js create mode 100755 test/mssql/01-tests-limit.js create mode 100755 test/mssql/01-tests-offset.js create mode 100755 test/mssql/01-tests-orderby.js create mode 100755 test/mssql/01-tests-select.js create mode 100755 test/mssql/01-tests-set.js create mode 100755 test/mssql/01-tests-where.js create mode 100755 test/mssql/01-tests-where_in.js create mode 100755 test/mssql/02-tests-compilation_methods.js create mode 100755 test/mssql/03-tests-count.js create mode 100755 test/mssql/03-tests-delete.js create mode 100755 test/mssql/03-tests-empty_table.js create mode 100755 test/mssql/03-tests-insert_batch.js create mode 100755 test/mssql/03-tests-update.js diff --git a/README.md b/README.md index 2ec038d..9e81943 100755 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ qb.select('name', 'position') Driver | Default | Ready | single | pool | cluster | Additional Connection Options :----------------------------------------- | :------- | :------ | :----- | :--- | :------ | :---------------------------------------------------------------------------------------- [mysql](//www.npmjs.com/package/mysql) | ✓ | Yes | Yes | Yes | Pending | [node-mysql connection options](https://github.com/felixge/node-mysql#connection-options) -[mssql](//www.npmjs.com/package/mssql) | | Yes | Yes | Yes | ??? | [node-mssql connection options](https://github.com/tediousjs/node-mssql#general-same-for-all-drivers) +[mssql](//www.npmjs.com/package/tedious) | | Yes | Yes | Yes | ??? | [tedious connection options](http://tediousjs.github.io/tedious/api-connection.html) [sqlite3](//www.npmjs.com/package/sqlite3) | | No | Yes | ??? | ??? | [oracle](//www.npmjs.com/package/oracle) | | No | Yes | ??? | ??? | [postgres](//www.npmjs.com/package/pg) | | No | Yes | Yes | ??? | @@ -161,7 +161,7 @@ This part is super simple. Just pass which one you'd like to use as the second p **_Example:_** ```javascript -const qb = new require('node-querybuilder')(settings, 'postgres'); +const qb = new require('node-querybuilder')(settings, 'mssql'); ``` ## Choosing the Connection Type @@ -926,7 +926,7 @@ This SQL command is used to order the resultset by a field or fields in descendi Parameter | Type | Default | Description :-------- | :----------- | :------- | :---------------------------------------------------------------------- fields | String/Array | Required | Field name or an array of field names, possibly with directions as well -direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'newid'/'random'/'rand()': Random. +direction | String | 'asc' | 'asc': Ascending; 'desc': Descending; 'rand'/'random'/'rand()': Random. This is a very flexible method, offering a wide variety of ways you can call it. Variations include: - Pass the field name and omit the direction diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index c22a118..b4fb0a9 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -1,4 +1,5 @@ const GenericQueryBuilder = require('../query_builder.js'); +const tsqlstring = require('tsqlstring'); class QueryBuilder extends GenericQueryBuilder { constructor() { @@ -6,7 +7,7 @@ class QueryBuilder extends GenericQueryBuilder { this.rand_word = 'NEWID()'; this.escape_char = ['[',']']; this.condition_rgx = /([\[\]\w\."\s-]+)(\s*[^\"\[`'\w-]+\s*)(.+)/i; - this.multi_condition_rgx = /\s(OR|AND)\s/; + this.multi_condition_rgx = /\s(OR|AND)\s/i; } // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // @@ -19,7 +20,7 @@ class QueryBuilder extends GenericQueryBuilder { } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { str *= 1; } else { - str = mysql.escape(str); + str = tsqlstring.escape(str); } return str; @@ -52,7 +53,8 @@ class QueryBuilder extends GenericQueryBuilder { } offset_clause += ` OFFSET ${offset_val} ROWS`; - if (limit_to) offset += ` FETCH NEXT ${limit_to} ROWS ONLY`; + if (limit_to) offset_clause += ` FETCH NEXT ${limit_to} ROWS ONLY`; + return offset_clause.trim(); } // ---------------------------- SQL EXEC TOOLS ----------------------------// @@ -62,13 +64,24 @@ class QueryBuilder extends GenericQueryBuilder { return ''; } - this.from_array = this.from_array.slice(0,1); + // Make sure we're only deleting from one table + this.from_array = this.from_array.slice(0, 1); - const sql = `DELETE ${this._build_limit_clause()} ` - + this._build_from_clause() - + this._build_where_clause(); + const limit_clause = this._build_limit_clause(); + const offset_clause = this._build_offset_clause(); + const from_clause = this._build_from_clause().trim(); + const where_clause = this._build_where_clause().trim(); + + // Do not allow offset deletes (don't know how to do this yet) + // TODO: implement this? + if (offset_clause) throw new Error("Offset deletes are currently not supported by QueryBuilder for the `mssql` driver."); - return sql; + let sql = 'DELETE'; + sql += (limit_clause ? ` ${limit_clause}` : ''); + sql += ` ${from_clause}`; + sql += (where_clause ? ` ${where_clause}` : ''); + + return sql.trim(); } _compile_insert(ignore=false, suffix='') { @@ -101,7 +114,15 @@ class QueryBuilder extends GenericQueryBuilder { _compile_select() { const distinct_clause = this.distinct_clause[0] || ''; - let sql = `SELECT ${distinct_clause}`; + const from_clause = this._build_from_clause().trim(); + const join_string = this._build_join_string().trim(); + const where_clause = this._build_where_clause().trim(); + const group_by_clause = this._build_group_by_clause().trim(); + const having_clause = this._build_having_clause().trim(); + const order_by_clause = this._build_order_by_clause().trim(); + const offset_clause = this._build_offset_clause().trim(); + + let sql = (`SELECT ${distinct_clause}`).trim() + ' '; const limit_clause = this._build_limit_clause(); if (limit_clause) sql += `${limit_clause} `; @@ -112,21 +133,21 @@ class QueryBuilder extends GenericQueryBuilder { sql += this.select_array.join(', '); } - sql += this._build_from_clause() - + this._build_join_string() - + this._build_where_clause() - + this._build_group_by_clause() - + this._build_having_clause() - + this._build_order_by_clause() - + this._build_offset_clause(); + sql = `${sql} ${from_clause}`; + sql += (join_string ? ` ${join_string}` : ''); + sql += (where_clause ? ` ${where_clause}` : ''); + sql += (group_by_clause ? ` ${group_by_clause}` : ''); + sql += (having_clause ? ` ${having_clause}` : ''); + sql += (order_by_clause ? ` ${order_by_clause}` : ''); + sql += (offset_clause ? ` ${offset_clause}` : ''); - return sql; + return sql.trim(); } _compile_update() { - const valstr = this.set_array.map(v => { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; + const valstr = this.set_array.map((v,i) => { + const key = Object.keys(v)[0]; + const val = v[key]; return `${key} = ${val}`; }).join(', '); @@ -139,22 +160,27 @@ class QueryBuilder extends GenericQueryBuilder { return ''; } + const limit_clause = this._build_limit_clause().trim(); + const order_by_clause = this._build_order_by_clause().trim(); + const where_clause = this._build_where_clause().trim(); + const table = this.from_array.toString(); let sql = 'UPDATE '; - if (this.order_by_array.length > 0) { - sql += `${table} SET ${valstr}${this._build_where_clause()}`; + if (!limit_clause) { + sql += `${table} SET ${valstr} ${where_clause}`; } else { - const limit_clause = this._build_limit_clause(); - const from_clause = (limit_clause ? 'FROM *' : `${limit_clause} FROM *`); - sql += `[t] SET ${valstr} FROM (SELECT ${from_clause})` - + this._build_where_clause() - + this._build_order_by_clause() - + `) [t]`; + const from_clause = (limit_clause ? `` : `${limit_clause} * FROM ${table}`); + sql += `[t] SET ${valstr} FROM (SELECT`; + sql += (limit_clause ? ` ${limit_clause}` : ''); + sql += ` * FROM ${table}`; + sql += (where_clause ? ` ${where_clause}` : ''); + sql += (order_by_clause ? ` ${order_by_clause}` : ''); + sql += `) [t]`; } - return sql; + return sql.trim(); } insert(table='', set='', ignore=false, suffix='') { @@ -163,7 +189,7 @@ class QueryBuilder extends GenericQueryBuilder { super._insert(table, set, false, suffix); } - _insert_batch(table,set=null,ignore,suffix) { + _insert_batch(table, set=null, ignore=false, suffix='') { const orig_table = table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); @@ -249,12 +275,15 @@ class QueryBuilder extends GenericQueryBuilder { this.from(table); } - const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') - + this._build_from_clause() - + this._build_join_string() - + this._build_where_clause(); + const from_clause = this._build_from_clause().trim(); + const join_string = this._build_join_string().trim(); + const where_clause = this._build_where_clause().trim(); + + let sql = `SELECT COUNT(*) AS ${this._protect_identifiers('numrows')} ${from_clause}`; + sql += (join_string ? ` ${join_string}` : ''); + sql += (where_clause ? ` ${where_clause}` : ''); - return sql; + return sql.trim(); } } diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index 5637ee9..c3b5891 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -1,350 +1,249 @@ -const GenericQueryBuilder = require('../query_builder.js'); - -class QueryBuilder extends GenericQueryBuilder { - constructor() { - super(); - this.rand_word = 'RAND()'; - this.escape_char = '`'; - this.condition_rgx = /([\[\]\w\."\s-]+)(\s*[^\"\[`'\w-]+\s*)(.+)/i; - this.multi_condition_rgx = /\sAND\s|\sOR\s/ig; - } - - // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // - _escape_identifiers(item = '*') { - if (item === '*') return item; - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - for (let i in item) { - item[i] = this._escape_identifiers(item[i]); - } - return item; - } - else if ((typeof item === 'string' && /^\d+$/.test(item)) || item[0] === "'" || item[0] === '"' || item.indexOf('(') !== -1) { - return item; - } - - let str; - if (item.indexOf('.' + '*') !== -1) { - str = item.replace(/`?([^`\.]+)`?\./ig, "`$1`."); - } - else { - str = item.replace(/`?([^`\.]+)`?(\.)?/ig, "`$1`$2`"); - } - - // remove duplicates if the user already included the escape - return str.replace(/[`]+/g,'`'); - }; - - _protect_identifiers(item, protect_identifiers) { - if (item === '') return item; - - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - const escaped_array = {}; - - for (let k in item) { - const v = item[k]; - escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v); - } - return escaped_array; - } - - // Make sure item is a string... - if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); - - // Convert tabs or multiple spaces into single spaces - item = item.replace(/\s+/g, ' '); - - // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - if (item.indexOf('(') !== -1 || item.indexOf("'") !== -1) { - const has_alias = item.lastIndexOf(')'); - let alias; - if (has_alias >= 0) { - alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); - alias = this._escape_identifiers(alias); - if (alias != '') - alias = ' AS ' + alias; - item = item.substr(0, has_alias + 1); - } else { - alias = ''; - } - - return item + alias; - } - - let alias = ''; - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (/\sAS\s/ig.test(item)) { - const alias_index = item.indexOf(item.match(/\sAS\s/ig)[0]); - alias = (protect_identifiers ? item.substr(alias_index, 4) + this._escape_identifiers(item.slice(alias_index + 4)) : item.substr(alias_index)); - item = item.substr(0, alias_index); - } - else if (item.indexOf(' ') !== -1) { - const alias_index = item.indexOf(' '); - - alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - const parts = item.split('.'); - const first_seg = parts[0].trim().replace(/`/g,''); - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (this.aliased_tables.indexOf(first_seg) !== -1) { - if (protect_identifiers === true) { - for (let key in parts) { - const val = parts[key]; - if (val !== '*') { - parts[key] = this._escape_identifiers(val); - } - } - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - } - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - }; - - _qb_escape(str) { - const mysql = require('mysql'); - - if (typeof str === 'boolean') { - str = (str === false ? 0 : 1); - } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { - str *= 1; - } else { - str = mysql.escape(str); - } - - return str; - }; - - // ---------------------------- SQL BUILD TOOLS ----------------------------// - _build_limit_clause(sql, limit, offset) { - if (!limit) return sql; - - sql += ' '; - - if (typeof offset !== 'number' || offset === 0) { - offset = ''; - } - else { - offset += ', '; - } - return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; - }; - - // ---------------------------- SQL EXEC TOOLS ----------------------------// - _compile_delete() { - if (this.from_array.length === 0) { - throw new Error('You have not specified any tables to delete from!'); - return ''; - } - - this.from_array = this.from_array.slice(0,1); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - const sql = 'DELETE' + this._build_from_clause() + this._build_where_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - - _compile_insert(ignore, suffix='') { - const keys = []; - const values = []; - - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - - keys.push(key); - values.push(val); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - - if (this.from_array.length === 1) { - const table = this.from_array.toString(); - } else { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build INSERT querty with!"); - return ''; - } - throw new Error("You have provided too many tables to build INSERT query with!"); - return ''; - } - - return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; - }; - - _compile_select() { - const distinct_clause = this.distinct_clause[0] || ''; - let sql = 'SELECT ' + distinct_clause; - if (this.select_array.length === 0) { - sql += '*'; - } else { - sql += this.select_array.join(', '); - } - - sql += this._build_from_clause() - + this._build_join_string() - + this._build_where_clause() - + this._build_group_by_clause() - + this._build_having_clause() - + this._build_order_by_clause(); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - sql = this._build_limit_clause(sql, limit_to, offset_val); - return sql; - }; - - _compile_update() { - const valstr = []; - for (let i in this.set_array) { - const key = Object.keys(this.set_array[i])[0]; - const val = this.set_array[i][key]; - valstr.push(key + ' = ' + val); - } - - if (this.from_array.length !== 1) { - if (this.from_array.length === 0) { - throw new Error("You haven't provided any tables to build UPDATE query with!"); - return ''; - } - throw new Error("You have provided too many tables to build UPDATE query with!"); - return ''; - } - - const table = this.from_array.toString(); - - const limit_to = this.limit_to[0] || false; - const offset_val = this.offset_val[0] || false; - - let sql = `UPDATE (${table}) SET ${valstr.join(', ')}`; - sql += this._build_where_clause(); - sql += this._build_order_by_clause(); - return this._build_limit_clause(sql, limit_to, offset_val); - }; - - _insert_batch(table,set=null,ignore,suffix) { - const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - - if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); - table = table.trim(); - - if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); - } - - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } - table = this.from_array[0]; - } else { - this._clear_array(this.from_array); - this.from(table); - } - - if (!Array.isArray(set)) { - throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); - } - - for (let key in set) { - const row = set[key]; - const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); - if (!is_object || (is_object && Object.keys(row).length === 0)) { - throw new Error('insert_batch(): An invalid item was found in the data array!'); - } else { - for (let i in row) { - const v = row[i]; - - if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided!"); - } - else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); - } - } - } - } - - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } - - const map = []; - const columns = []; - - // Obtain all the column names - for (let key in set[0]) { - if (set[0].hasOwnProperty(key)) { - if (columns.indexOf(key) == -1) { - columns.push(this._protect_identifiers(key)); - } - } - } - - for (let i = 0; i < set.length; i++) { - (i => { - const row = []; - for (let key in set[i]) { - if (set[i].hasOwnProperty(key)) { - row.push(this._qb_escape(set[i][key])); - } - } - if (row.length != columns.length) { - throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); - } - map.push('(' + row.join(', ') + ')'); - })(i); - } - - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; - } - - _count(table) { - if (typeof table === 'string') { - this.from(table); - } - - const sql = 'SELECT COUNT(*) AS ' + this._protect_identifiers('numrows') - + this._build_from_clause() - + this._build_join_string() - + this._build_where_clause(); - - return sql; - } -} - -module.exports = QueryBuilder; +const GenericQueryBuilder = require('../query_builder.js'); + +class QueryBuilder extends GenericQueryBuilder { + constructor() { + super(); + this.rand_word = 'RAND()'; + this.escape_char = '`'; + this.condition_rgx = /([\[\]\w\."\s-]+)(\s*[^\"\[`'\w-]+\s*)(.+)/i; + this.multi_condition_rgx = /\s(OR|AND)\s/i; + } + + // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // + _qb_escape(str) { + const mysql = require('mysql'); + + if (typeof str === 'boolean') { + str = (str === false ? 0 : 1); + } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { + str *= 1; + } else { + str = mysql.escape(str); + } + + return str; + }; + + // ---------------------------- SQL BUILD TOOLS ----------------------------// + _build_limit_clause(sql, limit, offset) { + if (!limit) return sql; + + sql += ' '; + + if (typeof offset !== 'number' || offset === 0) { + offset = ''; + } + else { + offset += ', '; + } + return sql.replace(/\s+$/, ' ') + 'LIMIT ' + offset + limit; + }; + + // ---------------------------- SQL EXEC TOOLS ----------------------------// + _compile_delete() { + if (this.from_array.length === 0) { + throw new Error('You have not specified any tables to delete from!'); + return ''; + } + + this.from_array = this.from_array.slice(0,1); + + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; + const from_clause = this._build_from_clause().trim(); + const where_clause = this._build_where_clause().trim(); + + const sql = (`DELETE ${from_clause} ${where_clause}`).trim(); + return this._build_limit_clause(sql, limit_to, offset_val); + }; + + _compile_insert(ignore, suffix='') { + const keys = []; + const values = []; + let table; + + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; + + keys.push(key); + values.push(val); + } + + const verb = ('INSERT ' + (ignore === true ? 'IGNORE ' : '')).trim(); + + if (this.from_array.length === 1) { + table = this.from_array.toString(); + } else { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build INSERT querty with!"); + return ''; + } + throw new Error("You have provided too many tables to build INSERT query with!"); + return ''; + } + + const sql = `${verb} INTO ${table} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; + return sql.trim(); + }; + + _compile_select() { + const distinct_clause = this.distinct_clause[0] || ''; + const from_clause = this._build_from_clause().trim(); + const join_string = this._build_join_string().trim(); + const where_clause = this._build_where_clause().trim(); + const group_by_clause = this._build_group_by_clause().trim(); + const having_clause = this._build_having_clause().trim(); + const order_by_clause = this._build_order_by_clause().trim(); + + let sql = (`SELECT ${distinct_clause}`).trim() + ' '; + if (this.select_array.length === 0) { + sql += '*'; + } else { + sql += this.select_array.join(', '); + } + + sql = `${sql} ${from_clause}`; + sql += (join_string ? ` ${join_string}` : ''); + sql += (where_clause ? ` ${where_clause}` : ''); + sql += (group_by_clause ? ` ${group_by_clause}` : ''); + sql += (having_clause ? ` ${having_clause}` : ''); + sql += (order_by_clause ? ` ${order_by_clause}` : ''); + + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; + + sql = this._build_limit_clause(sql, limit_to, offset_val); + return sql.trim(); + }; + + _compile_update() { + const valstr = []; + for (let i in this.set_array) { + const key = Object.keys(this.set_array[i])[0]; + const val = this.set_array[i][key]; + valstr.push(key + ' = ' + val); + } + + if (this.from_array.length !== 1) { + if (this.from_array.length === 0) { + throw new Error("You haven't provided any tables to build UPDATE query with!"); + return ''; + } + throw new Error("You have provided too many tables to build UPDATE query with!"); + return ''; + } + + const table = this.from_array.toString(); + + const limit_to = this.limit_to[0] || false; + const offset_val = this.offset_val[0] || false; + const where_clause = this._build_where_clause().trim(); + const order_by_clause = this._build_order_by_clause().trim(); + + let sql = `UPDATE (${table}) SET ${valstr.join(', ')}`; + sql += (where_clause ? ` ${where_clause}` : ''); + sql += (order_by_clause ? ` ${order_by_clause}` : ''); + return this._build_limit_clause(sql, limit_to, offset_val); + }; + + _insert_batch(table,set=null,ignore,suffix) { + const orig_table = table = table || ''; + ignore = (typeof ignore !== 'boolean' ? false : ignore); + suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + if (suffix == ' ') suffix = ''; + + if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + table = table.trim(); + + if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { + throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + } + + if (table == '') { + if (this.from_array.length === 0) { + throw new Error("insert_batch(): You have not set any tables to insert into."); + } + table = this.from_array[0]; + } else { + this._clear_array(this.from_array); + this.from(table); + } + + if (!Array.isArray(set)) { + throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); + } + + for (let key in set) { + const row = set[key]; + const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); + if (!is_object || (is_object && Object.keys(row).length === 0)) { + throw new Error('insert_batch(): An invalid item was found in the data array!'); + } else { + for (let i in row) { + const v = row[i]; + + if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { + throw new Error("set(): Invalid value provided!"); + } + else if (typeof v === 'number' && (v === Infinity || v !== +v)) { + throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + } + } + } + } + + if (set.length == 0) { + return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); + } + + const map = []; + const columns = []; + + // Obtain all the column names + for (let key in set[0]) { + if (set[0].hasOwnProperty(key)) { + if (columns.indexOf(key) == -1) { + columns.push(this._protect_identifiers(key)); + } + } + } + + for (let i = 0; i < set.length; i++) { + (i => { + const row = []; + for (let key in set[i]) { + if (set[i].hasOwnProperty(key)) { + row.push(this._qb_escape(set[i][key])); + } + } + if (row.length != columns.length) { + throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); + } + map.push('(' + row.join(', ') + ')'); + })(i); + } + + const verb = 'INSERT' + (ignore === true ? ' IGNORE' : ''); + const sql = `${verb} INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + return sql.trim(); + } + + _count(table) { + if (typeof table === 'string') { + this.from(table); + } + + const from_clause = this._build_from_clause().trim(); + const join_string = this._build_join_string().trim(); + const where_clause = this._build_where_clause().trim(); + + let sql = `SELECT COUNT(*) AS ${this._protect_identifiers('numrows')} ${from_clause}`; + sql += (join_string ? ` ${join_string}` : ''); + sql += (where_clause ? ` ${where_clause}` : ''); + + return sql.trim(); + } +} + +module.exports = QueryBuilder; diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 181cc8f..05ca8b4 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -138,7 +138,7 @@ class GenericQueryBuilder { * @param bool split Whether to split identifiers when a dot is encountered * @return mixed */ - _escape_identifiers(item = '*', split=true) { + _escape_identifiers(item='*', split=true) { if (item === '*') return item; // If object is supplied, escape the value of each key @@ -165,12 +165,12 @@ class GenericQueryBuilder { this.reserved_identifiers.forEach(v => { if (item.indexOf(v) === -1) { - return item.replace(RegExp(`${escape_chars[0]}?([^${escape_chars[1]}\.]+)${escape_chars[1]}?\.`, 'i'), `${escape_chars[0]}$1${escape_chars[1]}.`); + return item.replace(RegExp(`\\${escape_chars[0]}?([^\\${escape_chars[1]}\.]+)\\${escape_chars[1]}?\.`, 'ig'), `${escape_chars[0]}$1${escape_chars[1]}.`); } }); - const dot = (split !== FALSE ? '\.' : ''); - return item.replace(RegExp(`${escape_chars[0]}?([^${escape_chars[1]}${dot}]+)${escape_chars[1]}?(\.)?`, 'i'), `${escape_chars[0]}$1${escape_chars[1]}$2`); + const dot = (split !== false ? '\.' : ''); + return item.replace(RegExp(`\\${escape_chars[0]}?([^\\${escape_chars[1]}${dot}]+)\\${escape_chars[1]}?(\.)?`, 'ig'), `${escape_chars[0]}$1${escape_chars[1]}$2`); } /** @@ -197,98 +197,98 @@ class GenericQueryBuilder { * @param bool field_exists * @return string */ - _protect_identifiers(item, prefix_single=false, protect_identifiers = null, field_exists = true) { - if (item === '') return item; - - protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); - - if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { - const escaped_array = {}; - - for (let k in item) { - const v = item[k]; - escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v, prefix_single, protect_identifiers, field_exists); - } - return escaped_array; - } - - // Make sure item is a string... - if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); - - // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. - // If a parenthesis is found we know that we do not need to - // escape the data or add a prefix. - const match = /[\(\)\']{1}/.exec(str) - if (match && match.index !== item.length) return item; - // const parantheses_regex = /^([^\(]+)\(([^\)]+)\)\s+(?:as\s*)?([^\s]+)(?:\s*(.*))?$/; - // if (parantheses_regex.test(item)) { - // return item.replace(parantheses_regex, `$1($2) AS ${this._escape_identifiers("$3")} $4`); - // } - - // Convert tabs or multiple spaces into single spaces - item = item.trim().replace(/\s+/g, ' '); - - let alias = ''; - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - if (/\sAS\s/i.test(item)) { - const offset = item.indexOf(item.match(/\sAS\s/i)[0]); - alias = (protect_identifiers ? item.substr(offset, 4) + this._escape_identifiers(item.substr(offset + 4), false) : item.substr(offset)); - item = item.substr(0, alias_index); - } - else if (item.indexOf(' ') !== -1) { - const alias_index = item.indexOf(' '); - - alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1), false) : item.substr(alias_index)); - item = item.substr(0,alias_index); - } - - // Break the string apart if it contains periods, then insert the table prefix - // in the correct location, assuming the period doesn't indicate that we're dealing - // with an alias. While we're at it, we will escape the components - if (item.indexOf('.') !== -1) { - const parts = item.split('.'); - let first_seg = parts[0].trim(); - - if (Array.isArray(this.escape_char)) { - const rgx_esc = (str) => str.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); - let rgx = `^${rgx_esc(start)}([^${rgx_esc(end)}]+)${rgx_esc(end)}$`; - first_seg = first_seg.replace(new RegExp(rgx), "$1"); - } else { - first_seg = first_seg.replace(/`/g,''); - } - - // Does the first segment of the exploded item match - // one of the aliases previously identified? If so, - // we have nothing more to do other than escape the item - if (this.aliased_tables.indexOf(first_seg) !== -1) { - if (protect_identifiers === true) { - parts = parts.map((v,i) => { - if (!this.reserved_identifiers.includes(v)) { - return this._escape_identifiers(v); - return v; - } - }); - - item = parts.join('.'); - } - return item + alias; - } - - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - } - - if (protect_identifiers === true) { - item = this._escape_identifiers(item); - } - - return item + alias; - } + _protect_identifiers(item, prefix_single=false, protect_identifiers=null, field_exists=true) { + if (item === '') return item; + + protect_identifiers = (typeof protect_identifiers === 'boolean' ? protect_identifiers : true); + + if (Object.prototype.toString.call(item) === Object.prototype.toString.call({})) { + const escaped_array = {}; + + for (let k in item) { + const v = item[k]; + escaped_array[this._protect_identifiers(k)] = this._protect_identifiers(v); + } + return escaped_array; + } + + // Make sure item is a string... + if (typeof item !== 'string') throw new Error("Invalid item passed to _protect_identifiers:" + typeof item); + + // Convert tabs or multiple spaces into single spaces + item = item.trim().replace(/\s+/g, ' '); + + let alias = ''; + + // This is basically a bug fix for queries that use MAX, MIN, subqueries, etc. + // If a parenthesis is found we know that we do not need to + // escape the data or add a prefix. + const match = /[\(\)\']{1}/.exec(item) + if (match && match.index !== item.length) { + const has_alias = item.lastIndexOf(')'); + if (has_alias >= 0) { + alias = item.substr(has_alias + 1).replace(/\sAS\s/i,'').trim(); + alias = this._escape_identifiers(alias); + if (alias != '') + alias = ' AS ' + alias; + item = item.substr(0, has_alias + 1); + } else { + alias = ''; + } + + return item + alias; + } + + // If the item has an alias declaration we remove it and set it aside. + // Basically we remove everything to the right of the first space + if (/\sAS\s/ig.test(item)) { + const offset = item.indexOf(item.match(/\sAS\s/ig)[0]); + alias = (protect_identifiers ? item.substr(offset, 4) + this._escape_identifiers(item.slice(offset + 4), false) : item.substr(offset)); + item = item.substr(0, offset); + } + else if (item.indexOf(' ') !== -1) { + const alias_index = item.indexOf(' '); + + alias = (protect_identifiers && ! this._has_operator(item.substr(alias_index + 1)) ? ' ' + this._escape_identifiers(item.substr(alias_index + 1)) : item.substr(alias_index)); + item = item.substr(0, alias_index); + } + + // Break the string apart if it contains periods, then insert the table prefix + // in the correct location, assuming the period doesn't indicate that we're dealing + // with an alias. While we're at it, we will escape the components + if (item.indexOf('.') !== -1) { + let parts = item.split('.'); + const first_seg = parts[0].trim();//.replace(/`/g,''); + + // Does the first segment of the exploded item match + // one of the aliases previously identified? If so, + // we have nothing more to do other than escape the item + if (this.aliased_tables.indexOf(first_seg) !== -1) { + if (protect_identifiers === true) { + parts = parts.map((v,i) => { + if (!this.reserved_identifiers.includes(v)) { + return this._escape_identifiers(v); + return v; + } + }); + + item = parts.join('.'); + } + return item + alias; + } + + if (protect_identifiers === true) { + item = this._escape_identifiers(item); + } + + return item + alias; + } + if (protect_identifiers === true) { + item = this._escape_identifiers(item); + } + + return (item + alias).trim(); + } _track_aliases(table) { if (Object.prototype.toString.call(table) === Object.prototype.toString.call({})) { @@ -340,58 +340,40 @@ class GenericQueryBuilder { // ---------------------------- SQL BUILD TOOLS ----------------------------// _build_where_clause() { - let sql = ''; - if (this.where_array.length > 0) { - sql += " WHERE "; - } - sql += this.where_array.join(" "); - return sql; + if (this.where_array.length === 0) return ''; + return `WHERE ${this.where_array.join(" ")}`; }; _build_from_clause() { let sql = ''; - if (this.from_array.length > 0) { - sql += " FROM "; - } else { + if (this.from_array.length === 0) { throw new Error("You have not provided any tables, views, or store procedures for this query!!"); } - sql += this.from_array.join(', '); - return sql; + sql = `FROM ${this.from_array.join(', ')}`; + return sql.trim(); }; _build_join_string() { - let sql = ''; - sql += this.join_array.join(' '); - if (sql.length > 0) sql = ' ' + sql; - return sql; + if (this.join_array.length <= 0) return ''; + return this.join_array.join(' '); }; _build_group_by_clause() { if (this.group_by_array.length <= 0) return ''; - - let sql = ' GROUP BY '; - sql += this.group_by_array.join(', '); - return sql; + return `GROUP BY ${this.group_by_array.join(', ')}`; }; _build_having_clause() { if (this.having_array.length <= 0) return ''; - - let sql = ' HAVING '; - sql += this.having_array.join(' '); - return sql; + return `HAVING ${this.having_array.join(' ')}`; }; _build_order_by_clause() { if (this.order_by_array.length <= 0) return ''; - - let sql = ' ORDER BY '; - sql += this.order_by_array.join(', '); - - return sql; + return `ORDER BY ${this.order_by_array.join(', ')}`; }; - reset_query(new_last_query,debug) { + reset_query(new_last_query, debug=false) { this._clear_array(this.where_array, debug); this._clear_array(this.where_in_array); this._clear_array(this.from_array); @@ -413,9 +395,9 @@ class GenericQueryBuilder { } } - where(key, value = null, escape) { + where(key, value=null, escape=true) { if (Object.prototype.toString.call(key) === Object.prototype.toString.call({}) && typeof value === 'boolean') { - escape = (typeof escape === 'boolean' ? escape : value); + escape = value; } escape = (typeof escape === 'boolean' ? escape : true); @@ -426,7 +408,7 @@ class GenericQueryBuilder { return this._where(key, value, 'AND ', escape); } - or_where(key, value=null, escape) { + or_where(key, value=null, escape=true) { escape = (typeof escape === 'boolean' ? escape : true); if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { @@ -435,14 +417,14 @@ class GenericQueryBuilder { return this._where(key, value, 'OR ', escape); } - _where(key, value=null, type='AND ', escape) { + _where(key, value=null, type='AND ', escape=true) { escape = (typeof escape === 'boolean' ? escape : true); // Must be an object or a string if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { // If it's not an object, it must be a string if (typeof key !== 'string') { - throw new Error("where(): If first parameter is not an object, it must be a string. " + typeof key + " provided."); + throw new Error(`where(): If first parameter is not an object, it must be a string. ${typeof key} provided.`); } else { // If it is a string, it can't be an empty one if (key.length == 0) { @@ -450,7 +432,7 @@ class GenericQueryBuilder { } } - // If it's a actual where clause string (with no paranthesis), + // If it's a actual where clause string (with no parantheses), // not just a field name, split it into individual parts to escape it properly if (/(<=|>=|<>|>|<|!=|=)/.test(key) && key.indexOf('(') === -1 && escape === true) { const filters = key.split(/\s+(AND|OR)\s+/i); @@ -502,11 +484,11 @@ class GenericQueryBuilder { if (v !== null) { if (escape === true) { - k = this._protect_identifiers(k,escape); + k = this._protect_identifiers(k, false, escape); v = ' ' + this._qb_escape(v); } - if (escape === false && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { + if (escape !== true && Object.prototype.toString.call(key) === Object.prototype.toString.call({})) { v = ' ' + this._qb_escape(v); } @@ -515,7 +497,7 @@ class GenericQueryBuilder { } } else { - k = this._protect_identifiers(k,escape); + k = this._protect_identifiers(k, false, escape); } if (v) { @@ -529,23 +511,23 @@ class GenericQueryBuilder { return this; } - where_in(key, values, escape) { + where_in(key, values, escape=true) { return this._where_in(key, values, false, 'AND ', escape); } - or_where_in(key, values, escape) { + or_where_in(key, values, escape=true) { return this._where_in(key, values, false, 'OR ', escape); } - where_not_in(key, values, escape) { + where_not_in(key, values, escape=true) { return this._where_in(key, values, true, 'AND ', escape); } - or_where_not_in(key, values, escape) { + or_where_not_in(key, values, escape=true) { return this._where_in(key, values, true, 'OR ', escape); } - _where_in(key='', values=[], not, type='AND ', escape) { + _where_in(key='', values=[], not, type='AND ', escape=true) { not = (not ? ' NOT' : ''); escape = (typeof escape === 'boolean' ? escape : true); @@ -568,7 +550,7 @@ class GenericQueryBuilder { } const prefix = (this.where_array.length == 0 ? '' : type); - const where_in = prefix + this._protect_identifiers(key,escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; + const where_in = prefix + this._protect_identifiers(key, false, escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; this.where_array.push(where_in); // reset the array for multiple calls @@ -667,7 +649,7 @@ class GenericQueryBuilder { this._track_aliases(v); - this.from_array.push(this._protect_identifiers(v, true)); + this.from_array.push(this._protect_identifiers(v, false, true)); } } else { @@ -677,14 +659,14 @@ class GenericQueryBuilder { // in the protect_identifiers function to know whether to add a table prefix this._track_aliases(val); - this.from_array.push(this._protect_identifiers(val, true)); + this.from_array.push(this._protect_identifiers(val, false, true)); } } return this; } - select(select,escape) { + select(select, escape=true) { // First param must be a non-empty string or array if (typeof select === 'string') { select = select.trim(); @@ -736,25 +718,25 @@ class GenericQueryBuilder { const val = select[i].trim(); if (val !== '') { - this.select_array.push(this._protect_identifiers(val, escape)); + this.select_array.push(this._protect_identifiers(val, false, escape)); } } return this; } - select_min(select,alias) { + select_min(select, alias) { return this._min_max_avg_sum(select, alias, 'MIN'); } - select_max(select,alias) { + select_max(select, alias) { return this._min_max_avg_sum(select, alias, 'MAX'); } - select_avg(select,alias) { + select_avg(select, alias) { return this._min_max_avg_sum(select, alias, 'AVG'); } - select_sum(select,alias) { + select_sum(select, alias) { return this._min_max_avg_sum(select, alias, 'SUM'); } @@ -827,17 +809,17 @@ class GenericQueryBuilder { return this; } - having(key, value, escape) { + having(key, value, escape=true) { escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'AND ', escape); } - or_having(key, value, escape) { + or_having(key, value, escape=true) { escape = (typeof escape !== 'boolean' ? true : escape); return this._having(key, value, 'OR ', escape); } - _having(key, value, type='AND ', escape) { + _having(key, value, type='AND ', escape=true) { let m; let key_array = {}; @@ -907,7 +889,7 @@ class GenericQueryBuilder { } join(table='', relation='', direction='', escape=true) { - if (typeof table !== 'string' || table.trim().length === 0) { + if (typeof table !== 'string' || (typeof table === 'string' && table.trim().length === 0)) { throw new Error("You must provide a table, view, or stored procedure to join to!"); } @@ -935,18 +917,21 @@ class GenericQueryBuilder { // Find all the conditions and protect their identifiers if (escape === true && this.multi_condition_rgx.test(relation)) { const new_relation = relation.split(this.multi_condition_rgx).map((v,i) => { - if (i % 2 !== 0) return v; - return v.replace(this.condition_rgx, `${this._protect_identifiers("$1", escape)} $2 ${this._protect_identifiers("$3", escape)}`); + if (i % 2 !== 0) return v.trim(); + const match = v.match(this.condition_rgx); + if (!match) return v.trim(); + return `${this._protect_identifiers(match[1].trim(), false, escape)} ${match[2].trim()} ${this._protect_identifiers(match[3].trim(), false, escape)}`; }).join(' '); relation = `ON ${new_relation}`; } // Split apart the condition and protect the identifiers - else if (escape === true && statement_regex.test(relation)) { - relation = 'ON ' + v.replace(this.condition_rgx, `${this._protect_identifiers("$1", escape)} $2 ${this._protect_identifiers("$3", escape)}`); + else if (relation && escape !== false && this.condition_rgx.test(relation)) { + const match = relation.match(this.condition_rgx); + relation = `ON ${this._protect_identifiers(match[1].trim(), false, escape)} ${match[2].trim()} ${this._protect_identifiers(match[3].trim(), false, escape)}`; } - else if (!this._has_operator(relation)) { + else if (relation && !this._has_operator(relation)) { relation = `USING (${(escape ? this._escape_identifiers(relation) : relation)})`; } else if (relation && escape === false) { @@ -958,10 +943,10 @@ class GenericQueryBuilder { // Do we want to escape the table name? if (escape === true) { - table = this._protect_identifiers(table,true); + table = this._protect_identifiers(table, false, true); } - this.join_array.push(`${direction} JOIN ${table} ${relation}`); + this.join_array.push(`${direction} JOIN ${table} ${relation}`.trim()); return this; } @@ -1036,7 +1021,7 @@ class GenericQueryBuilder { return this; } - set(key, value, escape) { + set(key, value, escape=true) { escape = (typeof escape === 'boolean' ? escape : true); if (typeof key === 'string') { @@ -1080,7 +1065,7 @@ class GenericQueryBuilder { } // Escape the key to be DRY - const escaped_key = this._protect_identifiers(i, escape); + const escaped_key = this._protect_identifiers(i, false, escape); // Build a temporary object with escaped key and val const temp = {}; @@ -1119,6 +1104,7 @@ class GenericQueryBuilder { } _insert(table='', set='', ignore=false, suffix='') { + table = table || ''; // force falsy values to be an empty string ignore = (typeof ignore !== 'boolean' ? false : ignore); suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); @@ -1164,7 +1150,7 @@ class GenericQueryBuilder { return this.insert(table, set, true, suffix); } - insert_batch(table,set=null,ignore,suffix) { + insert_batch(table, set=null, ignore, suffix) { return this._insert_batch(table, set, ignore, suffix); } @@ -1177,11 +1163,11 @@ class GenericQueryBuilder { this.from(table); } else { - if (this.from_array.length == 0) { + if (this.from_array.length === 0) { throw new Error('You have not specified any tables to select from!'); } } - return this._compile_select(this); + return this._compile_select(); } get_where(table=null, where=null) { @@ -1225,8 +1211,7 @@ class GenericQueryBuilder { return this._update(table, set, where); } - _update(table, set, where=null) { - + _update(table='', set=null, where=null) { table = table || ''; set = set || null; diff --git a/package.json b/package.json index 0408887..269908f 100755 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "Daehyub Kim " ], "dependencies": { - "lodash.assign": "^4.0.7", - "mssql": "^4.1.0", "mysql": "^2.10.1", - "tedious-connection-pool": "^1.0.5" + "tedious": "^2.6.3", + "tedious-connection-pool": "^1.0.5", + "tsqlstring": "^1.0.0" }, "devDependencies": { "chai": "~1.4.2", diff --git a/test/mssql/01-tests-distinct.js b/test/mssql/01-tests-distinct.js index 423ea9f..8733cc5 100755 --- a/test/mssql/01-tests-distinct.js +++ b/test/mssql/01-tests-distinct.js @@ -4,15 +4,15 @@ const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); describe('distinct()', () => { - it('should exist', () => { - should.exist(qb.distinct); - }); - it('should be a function', () => { - qb.distinct.should.be.a('function'); - }); - it('should override the default distinct_clause with the "DISTINCT " keyword', () => { - qb.reset_query(); - qb.distinct(); - qb.distinct_clause.should.eql(['DISTINCT ']); - }); + it('should exist', () => { + should.exist(qb.distinct); + }); + it('should be a function', () => { + qb.distinct.should.be.a('function'); + }); + it('should override the default distinct_clause with the "DISTINCT " keyword', () => { + qb.reset_query(); + qb.distinct(); + qb.distinct_clause.should.eql(['DISTINCT ']); + }); }); diff --git a/test/mssql/01-tests-from.js b/test/mssql/01-tests-from.js index bd9cdf6..1c7a200 100755 --- a/test/mssql/01-tests-from.js +++ b/test/mssql/01-tests-from.js @@ -3,121 +3,121 @@ const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); describe('from()', () => { - it('should exist', () => { - should.exist(qb.from); - }); - it('should be a function', () => { - qb.from.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('from_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.from_array.should.be.empty; - }); - it('should add an item to an array and escape it properly', () => { - qb.from('universe'); - qb.from_array.should.eql(['[universe]']); - }) - it('should accept a comma-delimited string of items and trim and escape each properly', () => { - qb.reset_query(); - qb.from('universe,galaxy , star_system, planet'); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should have an empty array after resetting', () => { - qb.reset_query(); - qb.from_array.should.be.empty; - }); - it('should be allowed to be called multiple times to add multiple items to the from array', () => { - qb.reset_query(); - qb.from('universe').from('galaxy').from('star_system').from('planet'); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should accept an array of items and add them individually to the from array', () => { - qb.reset_query(); - qb.from(['universe','galaxy','star_system','planet']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should not double-escape an item', () => { - qb.reset_query(); - qb.from('`do`'); - qb.from_array.should.eql(['`do`']); - }); - it('should not double-escape items when provided with an array of pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe`','`galaxy`','`star_system`']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); - }); - it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe`','galaxy','`star_system`']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); - }); - it('should allow for aliases and it should escape them properly', () => { - qb.reset_query(); - qb.from('universe u'); - qb.from_array.should.eql(['`universe` `u`']); - }); - it('should allow for the word AS to be used to alias an item', () => { - qb.reset_query(); - qb.from('universe as u'); - qb.from_array.should.eql(['`universe` as `u`']); - }); - it('should allow for an array of item + aliases and it should escape them all properly', () => { - qb.reset_query(); - qb.from(['universe u', 'galaxy g']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { - qb.reset_query(); - qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe` u', 'galaxy `g`']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should add aliases to alias-tracking array', () => { - qb.reset_query(); - qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.aliased_tables.should.eql(['u','g']); - }); - it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { - qb.reset_query(); - qb.from(['universe u, galaxy g']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for namespacing in field name (host.db.table)', () => { - qb.reset_query(); - qb.from('star_system.planet'); - qb.from_array.should.eql(['`star_system`.`planet`']); + it('should exist', () => { + should.exist(qb.from); + }); + it('should be a function', () => { + qb.from.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('from_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.from_array.should.be.empty; + }); + it('should add an item to an array and escape it properly', () => { + qb.from('universe'); + qb.from_array.should.eql(['[universe]']); + }) + it('should accept a comma-delimited string of items and trim and escape each properly', () => { + qb.reset_query(); + qb.from('universe,galaxy , star_system, planet'); + qb.from_array.should.eql(['[universe]','[galaxy]','[star_system]','[planet]']); + }); + it('should have an empty array after resetting', () => { + qb.reset_query(); + qb.from_array.should.be.empty; + }); + it('should be allowed to be called multiple times to add multiple items to the from array', () => { + qb.reset_query(); + qb.from('universe').from('galaxy').from('star_system').from('planet'); + qb.from_array.should.eql(['[universe]','[galaxy]','[star_system]','[planet]']); + }); + it('should accept an array of items and add them individually to the from array', () => { + qb.reset_query(); + qb.from(['universe','galaxy','star_system','planet']); + qb.from_array.should.eql(['[universe]','[galaxy]','[star_system]','[planet]']); + }); + it('should not double-escape an item', () => { + qb.reset_query(); + qb.from('[do]'); + qb.from_array.should.eql(['[do]']); + }); + it('should not double-escape items when provided with an array of pre-escaped items', () => { + qb.reset_query(); + qb.from(['[universe]','[galaxy]','[star_system]']); + qb.from_array.should.eql(['[universe]','[galaxy]','[star_system]']); + }); + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { + qb.reset_query(); + qb.from(['[universe]','galaxy','[star_system]']); + qb.from_array.should.eql(['[universe]','[galaxy]','[star_system]']); + }); + it('should allow for aliases and it should escape them properly', () => { + qb.reset_query(); + qb.from('universe u'); + qb.from_array.should.eql(['[universe] [u]']); + }); + it('should allow for the word AS to be used to alias an item', () => { + qb.reset_query(); + qb.from('universe as u'); + qb.from_array.should.eql(['[universe] as [u]']); + }); + it('should allow for an array of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u', 'galaxy g']); + qb.from_array.should.eql(['[universe] [u]','[galaxy] [g]']); + }); + it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { + qb.reset_query(); + qb.from(['[universe] [u]', '[galaxy] [g]']); + qb.from_array.should.eql(['[universe] [u]','[galaxy] [g]']); + }); + it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { + qb.reset_query(); + qb.from(['[universe] u', 'galaxy [g]']); + qb.from_array.should.eql(['[universe] [u]','[galaxy] [g]']); + }); + it('should add aliases to alias-tracking array', () => { + qb.reset_query(); + qb.from(['[universe] [u]', '[galaxy] [g]']); + qb.aliased_tables.should.eql(['u','g']); + }); + it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u, galaxy g']); + qb.from_array.should.eql(['[universe] [u]','[galaxy] [g]']); + }); + it('should allow for namespacing in field name (host.db.table)', () => { + qb.reset_query(); + qb.from('star_system.planet'); + qb.from_array.should.eql(['[star_system].[planet]']); - qb.reset_query(); - qb.from('galaxy.star_system.planet'); - qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); - }); - it('should allow for namespacing in field name (host.db.table.column) + alias', () => { - qb.reset_query(); - qb.from('universe.galaxy.star_system planet'); - qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); - }); - it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { - qb.reset_query(); - qb.from('universe.galaxy.star_system as planet'); - qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); - }); - it('should accept but ignore empty strings and empty strings within arrays', () => { - qb.reset_query(); - qb.from(''); - qb.from_array.should.be.empty; + qb.reset_query(); + qb.from('galaxy.star_system.planet'); + qb.from_array.should.eql(['[galaxy].[star_system].[planet]']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system planet'); + qb.from_array.should.eql(['[universe].[galaxy].[star_system] [planet]']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system as planet'); + qb.from_array.should.eql(['[universe].[galaxy].[star_system] as [planet]']); + }); + it('should accept but ignore empty strings and empty strings within arrays', () => { + qb.reset_query(); + qb.from(''); + qb.from_array.should.be.empty; - qb.reset_query(); - qb.from(['','']); - qb.from_array.should.be.empty; + qb.reset_query(); + qb.from(['','']); + qb.from_array.should.be.empty; - qb.reset_query(); - qb.from(['','foobar']); - qb.from_array.should.eql(['`foobar`']); - }); + qb.reset_query(); + qb.from(['','foobar']); + qb.from_array.should.eql(['[foobar]']); + }); }); diff --git a/test/mssql/01-tests-get.js b/test/mssql/01-tests-get.js new file mode 100755 index 0000000..7507ad7 --- /dev/null +++ b/test/mssql/01-tests-get.js @@ -0,0 +1,202 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('get()', () => { + it('should exist', () => { + should.exist(qb.get); + }); + it('should be a function', () => { + qb.get.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.get('galaxies'); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', () => { + qb.reset_query(); + qb.get(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['[galaxies]','[star_systems]','[planets]']); + }); + it('should not accept anything but a non-empty string or an array of non-empty strings', () => { + qb.reset_query(); + expect(() => qb.get(), 'nothing provided').to.throw(Error); + expect(() => qb.get({foo: 'bar'}), 'object provided').to.throw(Error); + expect(() => qb.get(true), 'boolean provided').to.throw(Error); + expect(() => qb.get(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.get(null), 'NULL provided').to.throw(Error); + expect(() => qb.get(1), 'Integer provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get([]), 'Empty array provided').to.throw(Error); + expect(() => qb.get(''), 'Empty string provided').to.throw(Error); + expect(() => qb.get('galaxies'), 'Valid string provided').to.not.throw(Error); + expect(() => qb.get(['galaxies','planets']), 'Array of non-empty strings provided').to.not.throw(Error); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.get('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', () => { + qb.reset_query(); + const sql = qb.get('galaxies'); + sql.should.eql('SELECT * FROM [galaxies]'); + }); + it('should properly handle alias if provided in table string', () => { + qb.reset_query(); + const sql = qb.get('galaxies g'); + sql.should.eql('SELECT * FROM [galaxies] [g]'); + }); + it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', () => { + qb.reset_query(); + const sql = qb.select(['id','name']).get('galaxies'); + sql.should.eql("SELECT [id], [name] FROM [galaxies]"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', () => { + qb.reset_query(); + const sql = qb.where('class','M').get('galaxies'); + sql.should.eql("SELECT * FROM [galaxies] WHERE [class] = 'M'"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', () => { + qb.reset_query(); + const sql = qb.get(['galaxies','star_systems','planets']); + sql.should.eql('SELECT * FROM [galaxies], [star_systems], [planets]'); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', () => { + qb.reset_query(); + const sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); + sql.should.eql("SELECT * FROM [galaxies], [star_systems], [planets] WHERE [class] = 'M'"); + }); + it('should use tables added previously via the from() method', () => { + qb.reset_query(); + qb.from('galaxies'); + let sql = qb.get(); + sql.should.eql('SELECT * FROM [galaxies]'); + + qb.reset_query(); + sql = qb.from(['galaxies','star_systems','planets']).get(); + sql.should.eql('SELECT * FROM [galaxies], [star_systems], [planets]'); + }); + it('should accept where conditions added previously via the where() method', () => { + qb.reset_query(); + const sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); + sql.should.eql("SELECT * FROM [galaxies] WHERE [created] >= 4600000000 AND [classification] = 'M'"); + }); + it('should accept a limit on the number of rows selected', () => { + qb.reset_query(); + const sql = qb.limit(20).get('galaxies'); + sql.should.eql("SELECT TOP 20 * FROM [galaxies]"); + }); + it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', () => { + qb.reset_query(); + const sql = qb.limit(20, 10).get('galaxies'); + sql.should.eql("SELECT * FROM [galaxies] ORDER BY (SELECT NULL) OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY"); + }); + it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows while retaining the proper order', () => { + qb.reset_query(); + const sql = qb.order_by('name','asc').limit(20, 10).get('galaxies'); + sql.should.eql("SELECT * FROM [galaxies] ORDER BY [name] ASC OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY"); + }); + it('should include the DISTINCT keyword if the distinct() method is called', () => { + qb.reset_query(); + const sql = qb.distinct().select(['id','name']).get('galaxies'); + sql.should.eql("SELECT DISTINCT [id], [name] FROM [galaxies]"); + }); + it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', () => { + qb.reset_query(); + + // MIN + let sql = qb.select_min('size','min_size').get('galaxies'); + sql.should.eql("SELECT MIN([size]) AS min_size FROM [galaxies]"); + qb.reset_query(); + + // MAX + sql = qb.select_max('size','max_size').get('galaxies'); + sql.should.eql("SELECT MAX([size]) AS max_size FROM [galaxies]"); + qb.reset_query(); + + // AVG + sql = qb.select_avg('size','avg_size').get('galaxies'); + sql.should.eql("SELECT AVG([size]) AS avg_size FROM [galaxies]"); + qb.reset_query(); + + // SUM + sql = qb.select_sum('size','total_size').get('galaxies'); + sql.should.eql("SELECT SUM([size]) AS total_size FROM [galaxies]"); + }); + it('should include any joins that were added in the chain', () => { + qb.reset_query(); + const sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) + .join('galaxies g','g.id=s.galaxy_id','left') + .get('star_systems s'); + sql.should.eql("SELECT [s].[name] as [star_system_name], [g].[name] as [galaxy_name] FROM [star_systems] [s] LEFT JOIN [galaxies] [g] ON [g].[id] = [s].[galaxy_id]"); + }); + it('should include any GROUP BY statements added using the group_by() method.', () => { + qb.reset_query(); + const sql = qb.select('size').select('COUNT(id) as [num_of_size]',false).group_by('size').get('galaxies'); + sql.should.eql("SELECT [size], COUNT(id) AS [num_of_size] FROM [galaxies] GROUP BY [size]"); + }); + it('should add the ORDER BY clause of the order_by() method was called in the chain', () => { + qb.reset_query(); + const sql = qb.order_by('size').get('galaxies'); + sql.should.eql("SELECT * FROM [galaxies] ORDER BY [size] ASC"); + }); + it('should include any HAVING clauses added using the having() method', () => { + qb.reset_query(); + const sql = qb.select('size').select('COUNT(id) as [num_of_size]',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); + sql.should.eql("SELECT [size], COUNT(id) AS [num_of_size] FROM [galaxies] GROUP BY [size] HAVING [num_of_size] >= 456034960"); + }); +}); + +describe('get_where()', () => { + it('should exist', () => { + should.exist(qb.get_where); + }); + it('should be a function', () => { + qb.get_where.should.be.a('function'); + }); + it('should require the first parameter to be a table in string format or tables array format', () => { + qb.reset_query(); + expect(() => qb.get_where(), 'nothing provided').to.throw(Error); + expect(() => qb.get_where(''), 'empty string for table').to.throw(Error); + expect(() => qb.get_where([]), 'empty array for tables').to.throw(Error); + expect(() => qb.get_where(['']), 'array of empty strings for tables').to.throw(Error); + expect(() => qb.get_where(1), 'integer for table').to.throw(Error); + expect(() => qb.get_where(5.5), 'float for table').to.throw(Error); + expect(() => qb.get_where(true), 'TRUE for table').to.throw(Error); + expect(() => qb.get_where(false), 'FALSE for table').to.throw(Error); + expect(() => qb.get_where(null), 'NULL for table').to.throw(Error); + expect(() => qb.get_where({}), 'Standard object for table').to.throw(Error); + expect(() => qb.get_where(Infinite), 'Infinite for table').to.throw(Error); + expect(() => qb.get_where('galaxies'), 'valid table, no where').to.throw(Error); + expect(() => qb.get_where('galaxies',{}), 'valid table, empty where').to.throw(Error); + expect(() => qb.get_where('galaxies',[]), 'valid table, array for where').to.throw(Error); + expect(() => qb.get_where('galaxies',3), 'valid table, integer for where').to.throw(Error); + expect(() => qb.get_where('galaxies',33.3), 'valid table, float for where').to.throw(Error); + expect(() => qb.get_where('galaxies','foo'), 'valid table, string for where').to.throw(Error); + expect(() => qb.get_where('galaxies',true), 'valid table, TRUE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',false), 'valid table, FALSE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',Infinite), 'valid table, Infinite where').to.throw(Error); + expect(() => qb.get_where('galaxies',null), 'valid table, NULL where').to.throw(Error); + expect(() => qb.get_where('galaxies',{id: 3}), 'valid table, valid where').to.not.throw(Error); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.get('galaxies', {type: 'spiral'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should add table(s) to from_array and where items to where_array', () => { + qb.reset_query(); + const sql = qb.get_where('galaxies', {type: 'spiral'}); + qb.from_array.should.eql(['[galaxies]']); + qb.where_array.should.eql(["[type] = 'spiral'"]); + sql.should.eql("SELECT * FROM [galaxies] WHERE [type] = 'spiral'"); + }); +}); diff --git a/test/mssql/01-tests-groupby.js b/test/mssql/01-tests-groupby.js new file mode 100755 index 0000000..b5553f6 --- /dev/null +++ b/test/mssql/01-tests-groupby.js @@ -0,0 +1,52 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('group_by()', () => { + it('should exist', () => { + should.exist(qb.group_by); + }); + it('should be a function', () => { + qb.group_by.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('group_by_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.group_by_array.should.be.empty; + }); + it('should accept a single field in string form', () => { + qb.reset_query(); + qb.group_by('planet_type'); + qb.group_by_array.should.eql(['[planet_type]']); + }); + it('should accept a multiple fields delimited by commas', () => { + qb.reset_query(); + qb.group_by('planet_type, planet_position'); + qb.group_by_array.should.eql(['[planet_type]','[planet_position]']); + }); + it('should accept an array of fields', () => { + qb.reset_query(); + qb.group_by(['planet_type', 'planet_position']); + qb.group_by_array.should.eql(['[planet_type]','[planet_position]']); + }); + it('should not accept anything but a string or an array of strings', () => { + qb.reset_query(); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); + + // valid string + expect(() => qb.group_by('planet_type'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type']), 'array of string(s) provided').to.not.throw(Error); + + }); +}); diff --git a/test/mssql/01-tests-having.js b/test/mssql/01-tests-having.js new file mode 100755 index 0000000..93f13ef --- /dev/null +++ b/test/mssql/01-tests-having.js @@ -0,0 +1,112 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('having()', () => { + it('should exist', () => { + should.exist(qb.having); + }); + it('should be a function', () => { + qb.having.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('having_array'); + }); + it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', () => { + qb.reset_query(); + qb.having('planet_class > "M"'); + qb.having_array.should.eql(["[planet_class] > 'M'"]); + + qb.reset_query(); + qb.having('planet_class < "M"'); + qb.having_array.should.eql(["[planet_class] < 'M'"]); + + qb.reset_query(); + qb.having('planet_class <> "M"'); + qb.having_array.should.eql(["[planet_class] <> 'M'"]); + + qb.reset_query(); + qb.having('planet_class >= "M"'); + qb.having_array.should.eql(["[planet_class] >= 'M'"]); + + qb.reset_query(); + qb.having('planet_class <= "M"'); + qb.having_array.should.eql(["[planet_class] <= 'M'"]); + + qb.reset_query(); + qb.having('planet_class = "M"'); + qb.having_array.should.eql(["[planet_class] = 'M'"]); + + qb.reset_query(); + qb.having('planet_class != "M"'); + qb.having_array.should.eql(["[planet_class] != 'M'"]); + }); + it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', () => { + qb.reset_query(); + expect(() => qb.having('planet_class = "M", sentient_life = 1'), 'two conditions provided').to.throw(Error); + }); + it('should accept an array of conditions and prepend AND to each condition following the first one', () => { + qb.reset_query(); + qb.having(["planet_class = 'M'", 'sentient_life = 1']); + qb.having_array.should.eql(["[planet_class] = 'M'", 'AND [sentient_life] = 1']); + }); + it('should accept an object of conditions and prepend AND to each condition following the first one', () => { + qb.reset_query(); + const object = {planet_class: 'M', sentient_life: 1}; + object['planet_order <='] = 3; + qb.having(object); + qb.having_array.should.eql(["[planet_class] = 'M'", 'AND [sentient_life] = 1','AND [planet_order] <= 3']); + }); + it('should not accept anything but a non-empty array, object, or string', () => { + qb.reset_query(); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); + + // valid string + expect(() => qb.group_by('planet_type = "M"'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type = "M"']), 'array of string(s) provided').to.not.throw(Error); + }); + it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', () => { + qb.reset_query(); + qb.having('planet_class','M'); + qb.having_array.should.eql(["[planet_class] = 'M'"]); + }); + it('should not escape conditions if asked not to', () => { + qb.reset_query(); + qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); + qb.having_array.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); + qb.having_array.should.eql(["[planet_class] = 'M'", 'AND [sentient_life] = 1','AND [planet_order] <= 3']); + }); +}); + +describe('or_having()', () => { + it('should exist', () => { + should.exist(qb.or_having); + }); + it('should be a function', () => { + qb.or_having.should.be.a('function'); + }); + it('should accept an array of conditions and prepend OR to each condition following the first one', () => { + qb.reset_query(); + qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); + qb.having_array.should.eql(["[planet_class] = 'M'", 'OR [sentient_life] = 1']); + }); + it('should be chainable with normal having', () => { + qb.reset_query(); + qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); + qb.having_array.should.eql(["[planet_class] = 'M'", 'AND [sentient_life] = 1','OR [planet_order] <= 3']); + }); +}); diff --git a/test/mssql/01-tests-join.js b/test/mssql/01-tests-join.js new file mode 100755 index 0000000..c73dcae --- /dev/null +++ b/test/mssql/01-tests-join.js @@ -0,0 +1,124 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('join()', () => { + it('should exist', () => { + should.exist(qb.join); + }); + it('should be a function', () => { + qb.join.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('join_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.join_array.should.be.empty; + }); + it('should require a string to be passed as first parameter', () => { + const invalid_match = /must provide a table/; + expect(() => qb.join(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.join(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.join(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.join(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.join({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.join([]), 'empty array provided').to.throw(Error, invalid_match); + expect(() => qb.join(''), 'empty string provided').to.throw(Error, invalid_match); + expect(() => qb.join(' '), 'string of spaces provided').to.throw(Error, invalid_match); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + }); + it('should except single item and add it to join array as basic join and escape item', () => { + qb.reset_query(); + qb.join('universe'); + qb.join_array.should.eql(['JOIN [universe]']); + }); + it('should except single item with alias and add it to join array as basic join and escape each part', () => { + qb.reset_query(); + qb.join('universe u'); + qb.join_array.should.eql(['JOIN [universe] [u]']); + }); + it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', () => { + const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; + const invalid_direction = /Invalid join direction provided as third parameter/; + + expect(() => qb.join('universe',null,'left'), 'null 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',false,'left'), 'false 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','','left'), 'empty string 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',' ','left'), 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5,'left'), 'integer 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5.6,'left'), 'float 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',[],'left'), 'array 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',{},'left'), 'object 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe','foo = bar'), 'no 3rd param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), '3 valid params').to.not.throw(Error); + }); + it('should allow valid join direction to be passed in third parameter', () => { + // NOTE: A lot of this functionality was already tested when testing second param + const invalid_direction = /Invalid join direction provided as third parameter/; + + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe',null,null), 'invalid 2nd and 3rd params').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',''), 'empty third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' '), 'just spaces').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',null), 'null third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',false), 'false third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5), 'integer third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5.5), 'float third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',{}), 'object third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',[]), 'array third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left '), 'trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left '), 'leading and trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left'), 'leading space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), 'lowercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT'), 'uppercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT OUTER'), 'two word direction').to.not.throw(Error); + }); + it('should except a valid second parameter as a join condition and escape it properly', () => { + qb.reset_query(); + qb.join('universe u','u.type_id = ut.id'); + qb.join_array.should.eql(['JOIN [universe] [u] ON [u].[type_id] = [ut].[id]']); + }); + it('should escape compound objects properly', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s','s.type_id = st.id'); + qb.join_array.should.eql(['JOIN [universe].[galaxy].[star_system] [s] ON [s].[type_id] = [st].[id]']); + }); + it('should add aliases to alias-tracking array', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s'); + qb.aliased_tables.should.eql(['s']); + }); + it('should properly place join direction into join clause', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); + qb.join_array.should.eql(['LEFT OUTER JOIN [universe].[galaxy].[star_system] [s] ON [s].[type_id] = [st].[id]']); + }); + it('should be chainable to allow for multiple join clauses', () => { + qb.reset_query(); + qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); + qb.join_array.should.eql(['LEFT OUTER JOIN [star_system] [s] ON [s].[type_id] = [st].[id]', 'LEFT JOIN [planets] [p] ON [p].[star_system_id] = [s].[id]']); + }); + it('should escape complex join conditions', () => { + qb.reset_query(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); + qb.join_array.should.eql(["LEFT JOIN [star_system] [s] ON [s].[type_id] = [st].[id] AND [st].[active] = 1 AND [st].[created_on] > '2014-01-01'"]); + }); + it('should escape complex join conditions when there is [or] in the right-hand side of the condition', () => { + qb.reset_query(); + qb.join('star_system s', " st.type = 'foo or bar' AND s.type_id = st.id", 'left'); + qb.join_array.should.eql(["LEFT JOIN [star_system] [s] ON [st].[type] = 'foo or bar' AND [s].[type_id] = [st].[id]"]); + }); + it('should escape complex join conditions when there is [and] in the right-hand side of the condition', () => { + qb.reset_query(); + qb.join('star_system s', "st.type = 'foo and bar' AND s.type_id = st.id", 'left'); + qb.join_array.should.eql(["LEFT JOIN [star_system] [s] ON [st].[type] = 'foo and bar' AND [s].[type_id] = [st].[id]"]); + }); + it('should NOT escape any part of join query when asked not to', () => { + qb.reset_query(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left', false); + qb.join_array.should.eql(["LEFT JOIN star_system s ON s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'"]); + }); +}); diff --git a/test/mssql/01-tests-like.js b/test/mssql/01-tests-like.js new file mode 100755 index 0000000..e2f2846 --- /dev/null +++ b/test/mssql/01-tests-like.js @@ -0,0 +1,189 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); + + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); + }); +}); + +describe('or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'", "OR [planet_name] LIKE 'Jup%'"]); + }); +}); + +describe('not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); +}); + +describe('or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [galaxy_name] LIKE '%meda'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); +}); diff --git a/test/mssql/01-tests-limit.js b/test/mssql/01-tests-limit.js new file mode 100755 index 0000000..3a5fe8c --- /dev/null +++ b/test/mssql/01-tests-limit.js @@ -0,0 +1,87 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('limit()', () => { + it('should exist', () => { + should.exist(qb.limit); + }); + it('should be a function', () => { + qb.limit.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('limit_to'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.limit_to.should.be.empty; + }); + it('should require an integer (or integer in string form) in first parameter', () => { + qb.reset_query(); + expect(() => qb.limit(5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(5.7), 'float provided').to.throw(Error); + expect(() => qb.limit('5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.limit(), 'nothing provided').to.throw(Error); + expect(() => qb.limit(null), 'null provided').to.throw(Error); + expect(() => qb.limit(true), 'true provided').to.throw(Error); + expect(() => qb.limit(false), 'false provided').to.throw(Error); + expect(() => qb.limit(''), 'empty string provided').to.throw(Error); + expect(() => qb.limit({}), 'empty object provided').to.throw(Error); + expect(() => qb.limit([]), 'empty array provided').to.throw(Error); + expect(() => qb.limit([5]), 'array with integer in it provided').to.throw(Error); + }); + it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', () => { + qb.reset_query(); + expect(() => qb.limit(10,5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit(10,'5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(10,5.7), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit(10,'abc7'), 'alphanumerics provided').to.throw(Error); + expect(() => qb.limit(10,null), 'null provided').to.throw(Error); + expect(() => qb.limit(10,true), 'true provided').to.throw(Error); + expect(() => qb.limit(10,false), 'false provided').to.throw(Error); + expect(() => qb.limit(10,''), 'empty string provided').to.throw(Error); + expect(() => qb.limit(10,{}), 'empty object provided').to.throw(Error); + expect(() => qb.limit(10,[]), 'empty array provided').to.throw(Error); + expect(() => qb.limit(10,[5]), 'array with integer in it provided').to.throw(Error); + }); + it('should override the default limit_to value when a limit is provided', () => { + qb.reset_query(); + qb.limit(10); + qb.limit_to.should.eql([10]); + }); + it('should override the default limit_to and offset_val values when a limit and an offset are provided', () => { + qb.reset_query(); + qb.limit(10,20); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([20]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.limit('10 '); + qb.limit_to.should.eql([10]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.limit(' 10 ',' 12'); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([12]); + }); + it('should override values set by any previous calls to itself', () => { + qb.reset_query(); + qb.limit(10); + qb.limit_to.should.eql([10]); + qb.limit(20); + qb.limit_to.should.eql([20]); + }); + it('should be chainable whereby the last call to the method will contain the value(s) used', () => { + qb.reset_query(); + qb.limit(10,5).limit(20).limit(100,30); + qb.limit_to.should.eql([100]); + qb.offset_val.should.eql([30]); + }); +}); diff --git a/test/mssql/01-tests-offset.js b/test/mssql/01-tests-offset.js new file mode 100755 index 0000000..3a5d2b7 --- /dev/null +++ b/test/mssql/01-tests-offset.js @@ -0,0 +1,60 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('offset()', () => { + it('should exist', () => { + should.exist(qb.offset); + }); + it('should be a function', () => { + qb.offset.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('offset_val'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.offset_val.should.be.empty; + }); + it('should require an integer (or integer in string form) in first parameter', () => { + qb.reset_query(); + expect(() => qb.offset(5), 'integer provided').to.not.throw(Error); + expect(() => qb.offset('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.offset(5.7), 'float provided').to.throw(Error); + expect(() => qb.offset('5.7'), 'float provided').to.throw(Error); + expect(() => qb.offset('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.offset('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.offset(), 'nothing provided').to.throw(Error); + expect(() => qb.offset(null), 'null provided').to.throw(Error); + expect(() => qb.offset(true), 'true provided').to.throw(Error); + expect(() => qb.offset(false), 'false provided').to.throw(Error); + expect(() => qb.offset(''), 'empty string provided').to.throw(Error); + expect(() => qb.offset({}), 'empty object provided').to.throw(Error); + expect(() => qb.offset([]), 'empty array provided').to.throw(Error); + expect(() => qb.offset([5]), 'array with integer in it provided').to.throw(Error); + }); + it('should override the default offset_val value when a offset is provided', () => { + qb.reset_query(); + qb.offset(10); + qb.offset_val.should.eql([10]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.offset('10 '); + qb.offset(' 10'); + qb.offset(' 10 '); + qb.offset_val.should.eql([10]); + }); + it('should override values set by any previous calls to itself', () => { + qb.reset_query(); + qb.offset(10); + qb.offset_val.should.eql([10]); + qb.offset(20); + qb.offset_val.should.eql([20]); + }); + it('should be chainable whereby the last call to the method will contain the value used', () => { + qb.reset_query(); + qb.offset(10).offset(20).offset(100); + qb.offset_val.should.eql([100]); + }); +}); diff --git a/test/mssql/01-tests-orderby.js b/test/mssql/01-tests-orderby.js new file mode 100755 index 0000000..21fc46f --- /dev/null +++ b/test/mssql/01-tests-orderby.js @@ -0,0 +1,131 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('order_by()', () => { + it('should exist', () => { + should.exist(qb.order_by); + }); + it('should be a function', () => { + qb.order_by.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('order_by_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.order_by_array.should.be.empty; + }); + it('should require non-empty string or array as first param unless random is provided as second parameter', () => { + expect(() => qb.order_by(), 'nothing provided').to.throw(Error); + expect(() => qb.order_by(null), 'null provided').to.throw(Error); + expect(() => qb.order_by(false), 'false provided').to.throw(Error); + expect(() => qb.order_by(true), 'true provided').to.throw(Error); + expect(() => qb.order_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.order_by(3), 'integer provided').to.throw(Error); + expect(() => qb.order_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.order_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.order_by(''), 'empty string provided').to.throw(Error); + // If random + expect(() => qb.order_by('','rand'), 'empty string and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(undefined,'rand'), 'undefined and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(null,'rand'), 'null and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(false,'rand'), 'false and random direction provided').to.not.throw(Error); + expect(() => qb.order_by([],'rand'), 'empty array and random direction provided').to.not.throw(Error); + }); + it('should accept a field and direction separated by a space as first parameter and escape the field', () => { + qb.reset_query(); + qb.order_by('planet_position asc'); + qb.order_by_array.should.eql(['[planet_position] ASC']); + }); + it('should accept a field and direction as seperate parameters and escape the field', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc'); + qb.order_by_array.should.eql(['[planet_position] ASC']); + }); + it('should add additional order_by calls to teh order by array', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc'); + qb.order_by('planet_size', 'desc'); + qb.order_by_array.should.eql(['[planet_position] ASC', '[planet_size] DESC']); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); + qb.order_by_array.should.eql(['[planet_position] ASC', '[planet_size] DESC']); + }); + it('should assume ASC when no direction is provided', () => { + qb.reset_query(); + qb.order_by('planet_position'); + qb.order_by_array.should.eql(['[planet_position] ASC']); + }); + it('should only accept valid ordering directions (ASC, DESC, random)', () => { + qb.reset_query(); + expect(() => qb.order_by('planet_position')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','ASC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','DESC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','random')).to.not.throw(Error); + expect(() => qb.order_by('planet_position',null)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',undefined)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',false)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',3)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',true)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',[])).to.not.throw(Error); + expect(() => qb.order_by('planet_position',{})).to.not.throw(Error); + expect(() => qb.order_by('planet_position','')).to.not.throw(Error); + + // Only an invalid string will throw an error + expect(() => qb.order_by('planet_position','FAKE')).to.throw(Error); + }); + it('should accept a comma-separated list of fields to order by with a single direction at the end', () => { + qb.reset_query(); + qb.order_by('planet_position, planet_size asc'); + qb.order_by_array.should.eql(['[planet_position] ASC', '[planet_size] ASC']); + }); + it('should accept a comma-separated list of field + direction pairs', () => { + qb.reset_query(); + qb.order_by('planet_position desc, planet_size asc'); + qb.order_by_array.should.eql(['[planet_position] DESC', '[planet_size] ASC']); + }); + it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', () => { + qb.reset_query(); + qb.order_by('planet_position', 'random'); + qb.order_by_array.should.eql(['NEWID()']); + + qb.reset_query(); + qb.order_by('planet_size', 'RAND'); + qb.order_by_array.should.eql(['NEWID()']); + + qb.reset_query(); + qb.order_by('planet_position, planet_size', 'rand'); + qb.order_by_array.should.eql(['NEWID()']); + + qb.reset_query(); + qb.order_by(null, 'RAND()'); + qb.order_by_array.should.eql(['NEWID()']); + + qb.reset_query(); + qb.order_by('', 'rand'); + qb.order_by_array.should.eql(['NEWID()']); + }); + it('should accept an array of field + direction pairs', () => { + qb.reset_query(); + qb.order_by(['planet_position DESC', 'planet_size ASC']); + qb.order_by_array.should.eql(['[planet_position] DESC', '[planet_size] ASC']); + }); + it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', () => { + qb.reset_query(); + qb.order_by(['planet_position', 'planet_size'], 'desc'); + qb.order_by_array.should.eql(['[planet_position] DESC', '[planet_size] DESC']); + + qb.reset_query(); + qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); + qb.order_by_array.should.eql(['[planet_position] DESC', '[planet_size] DESC']); + }); + it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', () => { + qb.reset_query(); + qb.order_by(['planet_position', 'planet_size']); + qb.order_by_array.should.eql(['[planet_position] ASC', '[planet_size] ASC']); + + }); +}); diff --git a/test/mssql/01-tests-select.js b/test/mssql/01-tests-select.js new file mode 100755 index 0000000..ccd99ab --- /dev/null +++ b/test/mssql/01-tests-select.js @@ -0,0 +1,207 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('select()', () => { + it('should exist', () => { + should.exist(qb.select); + }); + it('should be a function', () => { + qb.select.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('select_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.select_array.should.be.empty; + }); + it('should require an array or string to be passed as first parameter', () => { + const invalid_match = /requires a string or array/; + const empty_str_match = /string is empty/; + const empty_arr_match = /array is empty/; + expect(() => qb.select(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.select(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.select(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.select(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.select({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.select([]), 'empty array provided').to.throw(Error, empty_arr_match); + expect(() => qb.select(''), 'empty string provided').to.throw(Error, empty_str_match); + expect(() => qb.select(' '), 'string of spaces provided').to.throw(Error, empty_str_match); + expect(() => qb.select('blah'), 'valid string provided').to.not.throw(Error); + }); + it('should add field to array and escape it properly', () => { + qb.reset_query(); + qb.select('notes'); + qb.select_array.should.eql(['[notes]']); + }); + it('should trim fields properly before placing them into the select array', () => { + qb.reset_query(); + qb.select(' notes '); + qb.select_array.should.eql(['[notes]']); + }); + it('should have an empty array after resetting', () => { + qb.reset_query(); + qb.select_array.should.be.empty; + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.select('foo',false); + qb.select_array.should.eql(['foo']); + }); + it('should accept a comma-delimited string of field names and trim and escape each properly', () => { + qb.reset_query(); + qb.select('do,re , mi, fa'); + qb.select_array.should.eql(['[do]','[re]','[mi]','[fa]']); + }); + it('should be allowed to be called multiple times to add multiple fields to the select array', () => { + qb.reset_query(); + qb.select('do').select('re').select('mi').select('fa'); + qb.select_array.should.eql(['[do]','[re]','[mi]','[fa]']); + }); + it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', () => { + qb.reset_query(); + qb.select('do').select('re',false).select('mi',false).select('fa'); + qb.select_array.should.eql(['[do]','re','mi','[fa]']); + }); + it('should accept an array of fields and add them individually to the select array', () => { + qb.reset_query(); + qb.select(['sol','la','ti','do']); + qb.select_array.should.eql(['[sol]','[la]','[ti]','[do]']); + }); + it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', () => { + qb.reset_query(); + qb.select(['sol','la','ti','do'],false); + qb.select_array.should.eql(['sol','la','ti','do']); + }); + it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', () => { + qb.reset_query(); + qb.select(['[sol]','la','ti','[do]'],false); + qb.select_array.should.eql(['[sol]','la','ti','[do]']); + }); + it('should not double-escape a field', () => { + qb.reset_query(); + qb.select('[do]'); + qb.select_array.should.eql(['[do]']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields', () => { + qb.reset_query(); + qb.select(['[do]','[re]','[mi]']); + qb.select_array.should.eql(['[do]','[re]','[mi]']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', () => { + qb.reset_query(); + qb.select(['[do]','re','[mi]']); + qb.select_array.should.eql(['[do]','[re]','[mi]']); + }); + it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select('foo as bar'); + qb.select_array.should.eql(['[foo] as [bar]']); + }); + it('should not double-escape aliases', () => { + qb.reset_query(); + qb.select(['foo as [bar]']); + qb.select_array.should.eql(['[foo] as [bar]']); + }); + it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select(['foo as bar','bar as foo']); + qb.select_array.should.eql(['[foo] as [bar]','[bar] as [foo]']); + }); + it('should allow for field aliases with spaces in them', () => { + qb.reset_query(); + qb.select('notes as The Notes'); + qb.select_array.should.eql(['[notes] as [The Notes]']); + }); + it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select('foo as bar, bar as foo, foobar as [Foo Bar]'); + qb.select_array.should.eql(['[foo] as [bar]','[bar] as [foo]','[foobar] as [Foo Bar]']); + }); + it('should allow for namespacing in field name (host.db.table.field)', () => { + qb.reset_query(); + qb.select('star_system.planet'); + qb.select_array.should.eql(['[star_system].[planet]']); + + qb.reset_query(); + qb.select('galaxy.star_system.planet'); + qb.select_array.should.eql(['[galaxy].[star_system].[planet]']); + + qb.reset_query(); + qb.select('universe.galaxy.star_system.planet'); + qb.select_array.should.eql(['[universe].[galaxy].[star_system].[planet]']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { + qb.reset_query(); + qb.select('universe.galaxy.star_system.planet as planet'); + qb.select_array.should.eql(['[universe].[galaxy].[star_system].[planet] as [planet]']); + }); + it('should not allow subqueries without the second parameter being false', () => { + qb.reset_query(); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') + ).to.throw(Error); + + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = 42) as num_planets') + ).to.throw(Error); + + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets', false) + ).to.not.throw(Error); + }); + it('should not allow functions without the second paramter being false', () => { + expect( + () => qb.select('s.star_systems, count(planets) as num_planets') + ).to.throw(Error); + + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets') + ).to.throw(Error); + + + expect( + () => qb.select('s.star_systems, count(planets) as num_planets', false) + ).to.not.throw(Error); + + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets', false) + ).to.not.throw(Error); + }); + it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', () => { + qb.reset_query(); + qb.select('count(*) as count', false); + qb.select_array.should.eql(['count(*) AS [count]']); + + qb.reset_query(); + qb.select('count(*) as count, m.*, MIN(id) as min', false); + qb.select_array.should.eql(['count(*) as count, m.*, MIN(id) AS [min]']); + + qb.reset_query(); + qb.select('(select count(p.*) as count from planets p) as num_planets', false); + qb.select_array.should.eql(['(select count(p.*) as count from planets p) AS [num_planets]']); + + qb.reset_query(); + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); + qb.select_array.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS [num_planets]']); + + }); +}); +const prefixes = ['min','max','avg','sum']; +for (const i in prefixes) { + const type = prefixes[i]; + describe('select_' + type+'()', () => { + it('should exist', () => { + should.exist(qb['select_' + type]); + }); + it('should be a function', () => { + qb['select_' + type].should.be.a('function'); + }); + it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', () => { + qb.reset_query(); + qb['select_' + type]('s.star_systems'); + qb.select_array.should.eql([type.toUpperCase() + '([s].[star_systems]) AS star_systems']); + }); + }); +} diff --git a/test/mssql/01-tests-set.js b/test/mssql/01-tests-set.js new file mode 100755 index 0000000..3895973 --- /dev/null +++ b/test/mssql/01-tests-set.js @@ -0,0 +1,104 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('set()', () => { + it('should exist', () => { + should.exist(qb.set); + }); + it('should be a function', () => { + qb.set.should.be.a('function'); + }); + it('should have an object to put fields into', () => { + qb.should.have.property('set_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.set_array.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object as first param', () => { + qb.reset_query(); + expect(() => qb.set(), 'nothing provided').to.throw(Error); + expect(() => qb.set(null), 'null provided').to.throw(Error); + expect(() => qb.set(false), 'false provided').to.throw(Error); + expect(() => qb.set(true), 'true provided').to.throw(Error); + expect(() => qb.set({}), 'empty object provided').to.throw(Error); + expect(() => qb.set(3), 'integer provided').to.throw(Error); + expect(() => qb.set(3.5), 'float provided').to.throw(Error); + expect(() => qb.set(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.set(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.set([]), 'empty array provided').to.throw(Error); + expect(() => qb.set([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.set(''), 'empty string provided').to.throw(Error); + expect(() => qb.set(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.set(/foobar/), 'regex provided').to.throw(Error); + + expect(() => qb.set('planet_position',3), 'valid string provided').to.not.throw(Error); + expect(() => qb.set({planet_position: 3}), 'valid object provided').to.not.throw(Error); + }); + it('should not accept anything but a string, number, date, null, or boolean as second param if first param is a string.', () => { + qb.reset_query(); + expect(() => qb.set('planet_position'), 'nothing provided').to.throw(Error); + expect(() => qb.set('planet_position',{}), 'empty object provided').to.throw(Error); + expect(() => qb.set('planet_position',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.set('planet_position',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.set('planet_position',[]), 'empty array provided').to.throw(Error); + expect(() => qb.set('planet_position',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.set('planet_position',/foobar/), 'regex provided').to.throw(Error); + + expect(() => qb.set('planet_position',new Date()), 'date provided').to.not.throw(Error); + expect(() => qb.set('planet_position',null), 'null provided').to.not.throw(Error); + expect(() => qb.set('planet_position',3), 'Integer provided').to.not.throw(Error); + expect(() => qb.set('planet_position',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.set('planet_position',false), 'false provided').to.not.throw(Error); + expect(() => qb.set('planet_position',true), 'true provided').to.not.throw(Error); + expect(() => qb.set('planet_position',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.set('planet_position',' '), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.set('planet_position','Three'), 'non-empty string provided').to.not.throw(Error); + }); + it('should add first param (key) and second param (value) to hash and escape them properly', () => { + qb.reset_query(); + qb.set('galaxy_name','Milky Way'); + qb.set_array.should.eql([{"[galaxy_name]": "'Milky Way'"}]); + }); + it('should merge passed object into set_array and escape items properly', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}); + qb.set_array.should.eql([{"[galaxy_name]": "'Milky Way'"}]); + + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}); + qb.set_array.should.eql([{"[galaxy_name]": "'Milky Way'"}, {"[galaxy_class]": "'C'"}]); + }); + it('should not escape items if asked not to', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}]); + }); + it('should append more items to set_array as set() is called', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}, null, false); + qb.set('galaxy_size','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"[galaxy_size]": "'D'"}]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way', galaxy_class: 'C'}, null, false).set('galaxy_size','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {galaxy_class: 'C'}, {"[galaxy_size]": "'D'"}]); + }); + it('should overwrite values of keys that have been set already', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D'); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"[galaxy_class]": "'D'"}]); + }); + it('should NOT overwrite values of keys that are the same but have different escape flags', () => { + qb.reset_query(); + qb.set({galaxy_name: 'Milky Way'}, null, false); + qb.set({galaxy_class: 'C'}); + qb.set('galaxy_class','D', false); + qb.set_array.should.eql([{galaxy_name: 'Milky Way'}, {"[galaxy_class]": "'C'"}, {galaxy_class: 'D'}]); + }); +}); diff --git a/test/mssql/01-tests-where.js b/test/mssql/01-tests-where.js new file mode 100755 index 0000000..4aef22a --- /dev/null +++ b/test/mssql/01-tests-where.js @@ -0,0 +1,174 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('where()', () => { + it('should exist', () => { + should.exist(qb.where); + }); + it('should be a function', () => { + qb.where.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('where_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.where_array.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object', () => { + qb.reset_query(); + expect(() => qb.where(), 'nothing provided').to.throw(Error); + expect(() => qb.where(null), 'null provided').to.throw(Error); + expect(() => qb.where(false), 'false provided').to.throw(Error); + expect(() => qb.where(true), 'true provided').to.throw(Error); + expect(() => qb.where({}), 'empty object provided').to.throw(Error); + expect(() => qb.where(3), 'integer provided').to.throw(Error); + expect(() => qb.where(3.5), 'float provided').to.throw(Error); + expect(() => qb.where([]), 'empty array provided').to.throw(Error); + expect(() => qb.where([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where(''), 'empty string provided').to.throw(Error); + + expect(() => qb.where('planet_position',3), 'valid string provided').to.not.throw(Error); + expect(() => qb.where({planet_position: 3}), 'valid object provided').to.not.throw(Error); + + }); + it('should accept a field name in the form of a string as the first parameter', () => { + qb.reset_query(); + qb.where('planet'); + qb.where_array.should.eql(['[planet] IS NULL']); + }); + it('should assume second param is NULL if not provided', () => { + qb.reset_query(); + qb.where('planet'); + qb.where_array.should.eql(['[planet] IS NULL']); + }); + it('should accept NULL as second parameter and assume IS NULL', () => { + qb.reset_query(); + qb.where('planet',null); + qb.where_array.should.eql(['[planet] IS NULL']); + }); + it('should accept boolean values and will transform them properly', () => { + qb.reset_query(); + qb.where('planet',true); + qb.where_array.should.eql(['[planet] = 1']); + + qb.reset_query(); + qb.where('planet',false); + qb.where_array.should.eql(['[planet] = 0']); + }); + it('should accept integer and float values', () => { + qb.reset_query(); + qb.where('planet',5); + qb.where_array.should.eql(['[planet] = 5']); + + qb.reset_query(); + qb.where('planet',123.456); + qb.where_array.should.eql(['[planet] = 123.456']); + }); + it('should accept string values', () => { + qb.reset_query(); + qb.where('planet','Earth'); + qb.where_array.should.eql(["[planet] = 'Earth'"]); + + qb.reset_query(); + qb.where('galaxy','Milky Way'); + qb.where_array.should.eql(["[galaxy] = 'Milky Way'"]); + }); + it('should accept arrays of values and assume a WHERE IN clause', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); + }); + it('should concatenate multiple where clauses with AND by default', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.where('galaxy','Milky Way'); + qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [galaxy] = 'Milky Way'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); + qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [galaxy] = 'Milky Way'"]); + }); + it('should accept an object of key/value pairs (field: value)', () => { + qb.reset_query(); + qb.where({planet:'Earth',star_system:'Solar'}); + qb.where_array.should.eql(["[planet] = 'Earth'", "AND [star_system] = 'Solar'"]); + }); + it('should accept an object of key/value pairs (field: value) where values can be arrays', () => { + qb.reset_query(); + qb.where({star_system:'Solar',planet:['Earth','Mars']}); + qb.where_array.should.eql(["[star_system] = 'Solar'", "AND [planet] IN ('Earth', 'Mars')"]); + }); + it('should accept an operators in the first parameter', () => { + qb.reset_query(); + qb.where('position >',3); + qb.where_array.should.eql(["[position] > 3"]); + + qb.reset_query(); + qb.where('position <',3); + qb.where_array.should.eql(["[position] < 3"]); + + qb.reset_query(); + qb.where('position >=',3); + qb.where_array.should.eql(["[position] >= 3"]); + + qb.reset_query(); + qb.where('position <=',3); + qb.where_array.should.eql(["[position] <= 3"]); + + qb.reset_query(); + qb.where('position <>',3); + qb.where_array.should.eql(["[position] <> 3"]); + + qb.reset_query(); + qb.where('position !=',3); + qb.where_array.should.eql(["[position] != 3"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where({star_system: 'Solar', planet: ['Earth', 'Mars']}, false); + qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); + }); + // it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { + // qb.reset_query(); + // qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); + // qb.where_array.should.eql(['[planet_id] = 3', 'AND [galaxy_id] > 21645', "OR [planet] = 'Earth'"]); + // }); + // it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { + // qb.reset_query(); + // qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + // qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + // }); + // it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { + // qb.reset_query(); + // qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + // qb.where_array.should.eql(["[galaxy_id] > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); + // }); +}); + +describe('or_where()', () => { + // it('should exist', () => { + // should.exist(qb.or_where); + // }); + // it('should be a function', () => { + // qb.or_where.should.be.a('function'); + // }); + // it('should prepend tertiary WHERE clauses with "OR"', () => { + // qb.reset_query(); + // qb.or_where('planet','Mars'); + // qb.or_where('planet','Venus'); + // qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); + // }); + // it('should be chainable', () => { + // qb.reset_query(); + // qb.or_where('planet','Mars').or_where('planet','Venus'); + // qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); + // }); + // it('should be chainable with normal where', () => { + // qb.reset_query(); + // qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); + // qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "OR [planet] = 'Venus'"]); + // }); +}); diff --git a/test/mssql/01-tests-where_in.js b/test/mssql/01-tests-where_in.js new file mode 100755 index 0000000..d4a745a --- /dev/null +++ b/test/mssql/01-tests-where_in.js @@ -0,0 +1,177 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('where_in()', () => { + it('should exist', () => { + should.exist(qb.where_in); + }); + it('should be a function', () => { + qb.where_in.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('where_in_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.where_in_array.should.be.empty; + }); + it('should not accept anything but a non-empty string as first parameter', () => { + qb.reset_query(); + expect(() => qb.where_in(), 'nothing provided').to.throw(Error); + expect(() => qb.where_in(null), 'null provided').to.throw(Error); + expect(() => qb.where_in(false), 'false provided').to.throw(Error); + expect(() => qb.where_in(true), 'true provided').to.throw(Error); + expect(() => qb.where_in({}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in({foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in(3), 'integer provided').to.throw(Error); + expect(() => qb.where_in(3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in([]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where_in(''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in(/foobar/), 'regex provided').to.throw(Error); + + expect(() => qb.where_in('planet_position',[1,2,3]), 'valid string provided').to.not.throw(Error); + }); + it('should not accept anything but a non-empty array of values as second parameter', () => { + qb.reset_query(); + expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); + expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); + expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); + expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); + expect(() => qb.where_in('planet',{}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',{foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',3), 'integer provided').to.throw(Error); + expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); + + expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); + }); + it('should require both a field name an array of values as first and second parameters, respectively', () => { + qb.reset_query(); + qb.where_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['[planet_position] IN (1, 2, 3)']); + }); + it('should concatenate multiple WHERE IN clauses with AND ', () => { + qb.reset_query(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']); + qb.where_in('galaxy_id',[123,456,789,0110]); + qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [galaxy_id] IN (123, 456, 789, 72)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); + qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [planet_position] IN (1, 2, 3, 4)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); + }); +}); + +describe('where_not_in()', () => { + it('should exist', () => { + should.exist(qb.where_not_in); + }); + it('should be a function', () => { + qb.where_not_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "AND"', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]); + qb.where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)', "AND [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)', "AND [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "AND [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); +}); + +describe('or_where_in()', () => { + it('should exist', () => { + should.exist(qb.or_where_in); + }); + it('should be a function', () => { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3]); + qb.or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] IN (1, 2, 3)', "OR [planet_position] IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] IN (1, 2, 3)', "OR [planet_position] IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "OR [planet_position] IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); + }); +}); + +describe('or_where_not_in()', () => { + it('should exist', () => { + should.exist(qb.or_where_in); + }); + it('should be a function', () => { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)', "OR [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['[planet_position] NOT IN (1, 2, 3)', "OR [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "OR [planet_position] NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); +}); diff --git a/test/mssql/02-tests-compilation_methods.js b/test/mssql/02-tests-compilation_methods.js new file mode 100755 index 0000000..2a0ad4f --- /dev/null +++ b/test/mssql/02-tests-compilation_methods.js @@ -0,0 +1,70 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('get_compiled_select()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_select); + }); + it('should be a function', () => { + qb.get_compiled_select.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.get_compiled_select('galaxies'); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', () => { + qb.reset_query(); + qb.get_compiled_select(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['[galaxies]','[star_systems]','[planets]']); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.get_compiled_select('galaxies'); + sql.should.eql('SELECT * FROM [galaxies]'); + }); +}); + +describe('get_compiled_insert()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_insert); + }); + it('should be a function', () => { + qb.get_compiled_insert.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); + sql.should.eql("INSERT INTO [galaxies] ([foo]) VALUES ('bar')"); + }); +}); + +describe('get_compiled_update()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_update); + }); + it('should be a function', () => { + qb.get_compiled_update.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); + sql.should.eql("UPDATE [galaxies] SET [foo] = 'bar' WHERE [id] = 45"); + }); +}); + +describe('get_compiled_delete()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_delete); + }); + it('should be a function', () => { + qb.get_compiled_delete.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.where('id',45).get_compiled_delete('galaxies'); + sql.should.eql("DELETE FROM [galaxies] WHERE [id] = 45"); + }); +}); diff --git a/test/mssql/03-tests-count.js b/test/mssql/03-tests-count.js new file mode 100755 index 0000000..69de821 --- /dev/null +++ b/test/mssql/03-tests-count.js @@ -0,0 +1,56 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('count()', () => { + it('should exist', () => { + should.exist(qb.count); + }); + it('should be a function', () => { + qb.count.should.be.a('function'); + }); + it('should require that an item already exists in the from_array if one is not provided as the first parameter', () => { + qb.reset_query(); + expect(() => qb.count(), 'no tables supplied in chain').to.throw(Error); + expect(() => qb.from('galaxies').count(), 'table supplied by from()').to.not.throw(Error); + expect(() => qb.count('galaxies'), 'table supplied as first parameter').to.not.throw(Error); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should create a properly-escaped SELECT query', () => { + qb.reset_query(); + const sql = qb.count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS [numrows] FROM [galaxies]"); + }); + it('should include WHERE statements', () => { + qb.reset_query(); + const sql = qb.where({type:'spiral'}).count('galaxies'); + sql.should.eql("SELECT COUNT(*) AS [numrows] FROM [galaxies] WHERE [type] = 'spiral'"); + }); + it('should work when table/view/procedure is provided earlier in chain but not in count() method', () => { + qb.reset_query(); + const sql = qb.from('galaxies').count(); + sql.should.eql("SELECT COUNT(*) AS [numrows] FROM [galaxies]"); + }); + it('should work with multiple tables/views/stored procedures', () => { + qb.reset_query(); + const sql = qb.from(['planets','galaxies']).count(); + sql.should.eql("SELECT COUNT(*) AS [numrows] FROM [planets], [galaxies]"); + }); + it('should include any joins that were added in the chain', () => { + qb.reset_query(); + const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); + sql.should.eql("SELECT COUNT(*) AS [numrows] FROM [star_systems] [s] LEFT JOIN [galaxies] [g] ON [g].[id] = [s].[galaxy_id]"); + }); +}); diff --git a/test/mssql/03-tests-delete.js b/test/mssql/03-tests-delete.js new file mode 100755 index 0000000..dbb4c76 --- /dev/null +++ b/test/mssql/03-tests-delete.js @@ -0,0 +1,90 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('delete()', () => { + it('should exist', () => { + should.exist(qb.delete); + }); + it('should be a function', () => { + qb.delete.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.delete('galaxies'); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { + qb.reset_query(); + + expect(() => qb.delete([]), 'empty array provided').to.throw(Error); + expect(() => qb.delete({}), 'empty object provided').to.throw(Error); + expect(() => qb.delete(3), 'integer provided').to.throw(Error); + expect(() => qb.delete(3.5), 'float provided').to.throw(Error); + expect(() => qb.delete(true), 'true provided').to.throw(Error); + expect(() => qb.delete(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.delete([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.delete(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.delete(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.delete(false), 'false provided').to.throw(Error); + expect(() => qb.delete(''), 'empty string provided').to.throw(Error); + expect(() => qb.delete(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.delete(null), 'null provided').to.throw(Error); + + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(() => qb.delete(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.delete(),'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { + qb.reset_query(); + const sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql("DELETE FROM [galaxies]"); + }); + it('should add where conditions to where_array when conditions are supplied', () => { + qb.reset_query(); + qb.delete('planets', {continents: 7, star_system: 'Solar'}); + qb.where_array.should.eql(["[continents] = 7", "AND [star_system] = 'Solar'"]); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', () => { + qb.reset_query(); + const sql = qb.delete('galaxies'); + sql.should.eql('DELETE FROM [galaxies]'); + }); + it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', () => { + qb.reset_query(); + const sql = qb.delete('galaxies', {class: 'M'}); + sql.should.eql("DELETE FROM [galaxies] WHERE [class] = 'M'"); + }); + it('should use ONLY the FIRST table added previously via the from() method', () => { + qb.reset_query(); + qb.from('galaxies'); + let sql = qb.delete(); + sql.should.eql('DELETE FROM [galaxies]'); + + qb.reset_query(); + sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql('DELETE FROM [galaxies]'); + }); + it('should accept where conditions added previously via the where() method', () => { + qb.reset_query(); + const sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); + sql.should.eql("DELETE FROM [galaxies] WHERE [created] >= 4600000000 AND [class] = 'M'"); + }); + it('should accept a limit on the number of rows deleted', () => { + qb.reset_query(); + const sql = qb.limit(20).delete('galaxies'); + sql.should.eql("DELETE TOP 20 FROM [galaxies]"); + }); + it('should not a allow an offset delete', () => { + qb.reset_query(); + expect(() => qb.limit(20, 10).delete('galaxies'), 'offset provided').to.throw(Error); + }); +}); diff --git a/test/mssql/03-tests-empty_table.js b/test/mssql/03-tests-empty_table.js new file mode 100755 index 0000000..a1d10b2 --- /dev/null +++ b/test/mssql/03-tests-empty_table.js @@ -0,0 +1,51 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('emtpy_table()', () => { + it('should exist', () => { + should.exist(qb.empty_table); + }); + it('should be a function', () => { + qb.empty_table.should.be.a('function'); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.empty_table('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper DELETE statement', () => { + qb.reset_query(); + const sql = qb.empty_table('galaxies'); + sql.should.eql('DELETE FROM [galaxies]'); + }); + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { + qb.reset_query(); + + expect(() => qb.empty_table([]), 'empty array provided').to.throw(Error); + expect(() => qb.empty_table({}), 'empty object provided').to.throw(Error); + expect(() => qb.empty_table(3), 'integer provided').to.throw(Error); + expect(() => qb.empty_table(3.5), 'float provided').to.throw(Error); + expect(() => qb.empty_table(true), 'true provided').to.throw(Error); + expect(() => qb.empty_table(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.empty_table([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.empty_table(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.empty_table(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.empty_table(false), 'false provided').to.throw(Error); + expect(() => qb.empty_table(''), 'empty string provided').to.throw(Error); + expect(() => qb.empty_table(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.empty_table(null), 'null provided').to.throw(Error); + + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(() => qb.empty_table(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.empty_table(),'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { + qb.reset_query(); + const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); + sql.should.eql("DELETE FROM [galaxies]"); + }); +}); diff --git a/test/mssql/03-tests-insert_batch.js b/test/mssql/03-tests-insert_batch.js new file mode 100755 index 0000000..dd46c82 --- /dev/null +++ b/test/mssql/03-tests-insert_batch.js @@ -0,0 +1,104 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +const test_where = {id:3}; +const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +describe('insert_batch()', () => { + it('should exist', () => { + should.exist(qb.insert_batch); + }); + it('should be a function', () => { + qb.insert_batch.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.insert_batch('galaxies', test_data); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should only accept nothing or a string for the table (first) parameter', () => { + qb.reset_query(); + + // Doing these to prevent other errors + qb.from('galaxies'); + + expect(() => qb.insert_batch([], test_data), 'empty array provided').to.throw(Error); + expect(() => qb.insert_batch({}, test_data), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch(3, test_data), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch(3.5, test_data), 'float provided').to.throw(Error); + expect(() => qb.insert_batch(true, test_data), 'true provided').to.throw(Error); + expect(() => qb.insert_batch(Infinity, test_data), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch([1,2], test_data), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch(/foobar/, test_data), 'regex provided').to.throw(Error); + + expect(() => qb.insert_batch(NaN, test_data), 'NaN provided').to.not.throw(Error); + expect(() => qb.insert_batch(false, test_data), 'false provided').to.not.throw(Error); + expect(() => qb.insert_batch('', test_data), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert_batch(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.insert_batch(null, test_data), 'null provided').to.not.throw(Error); + expect(() => qb.insert_batch(undefined, test_data), 'undefined provided').to.not.throw(Error); + }); + it('should build a proper batch INSERT string', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies', test_data); + sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should only accept an array as the second parameter', () => { + qb.reset_query(); + + expect(() => qb.insert_batch('galaxies',test_data), 'array of objects provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies',[]), 'empty array provided').to.not.throw(Error); + + expect(() => qb.insert_batch('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[test_data,test_data]), 'array of arrays provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',{}), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',''), 'empty string provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',null), 'null provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',undefined), 'undefined provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies'), 'nothing provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3.5), 'float provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',true), 'true provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',false), 'false provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies',[]); + sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', () => { + qb.reset_query(); + qb.from('galaxies'); + const sql = qb.insert_batch(null,[]); + sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); + }); + it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { + qb.reset_query(); + const func = () => console.log("foo"); + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; + + expect(() => qb.insert_batch('galaxies',[{id: func}]), 'function in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: regex}]), 'regex in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: Infinity}]), 'Infinity in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: undefined}]), 'undefined in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: NaN}]), 'NaN in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: arr}]), 'array in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: obj}]), 'object in data').to.throw(Error); + }); + it('should support insert ignore statements', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); +}); diff --git a/test/mssql/03-tests-update.js b/test/mssql/03-tests-update.js new file mode 100755 index 0000000..01c4808 --- /dev/null +++ b/test/mssql/03-tests-update.js @@ -0,0 +1,126 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +const test_where = {id:3}; +const test_data = {name:'Milky Way', type: 'spiral'}; +const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +// table, data, callback, ignore, suffix + +describe('update()', () => { + it('should exist', () => { + should.exist(qb.update); + }); + it('should be a function', () => { + qb.update.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.update('galaxies', test_data, test_where); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should accept a string or falsy value for the table (first) parameter', () => { + qb.reset_query(); + + // One could choose to pass a falsy value to the first param because they have or will + // supply it with the from() method instead. + + qb.reset_query(); expect(() => qb.from('galaxies').update([], test_data), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update({}, test_data), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3, test_data), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3.5, test_data), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(true, test_data), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(Infinity, test_data), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update([1,2], test_data), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(/foobar/, test_data), 'regex provided').to.throw(Error); + + qb.reset_query(); expect(() => qb.from('galaxies').update(NaN, test_data), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(false, test_data), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update('', test_data), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(null, test_data), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(undefined, test_data),'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { + // One could choose to pass a falsy value to the second param because they have or will + // supply data with the set() method instead. + + qb.reset_query(); expect(() => qb.update('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',test_data_set), 'array of non-empty standard objects provided').to.not.throw(Error); + + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',NaN), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',false), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',''), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',null), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',undefined), 'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies'), 'nothing provided').to.not.throw(Error); + + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3.5), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',true), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',Infinity), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('foobar',{}), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[]), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',/foobar/), 'regex provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should require that there is at least something being updated', () => { + // TODO + }); + it('should utilize pre-existing tables set in from_array', () => { + qb.reset_query(); + qb.from('galaxies'); + const sql = qb.update(null, test_data, test_where); + sql.should.eql("UPDATE [galaxies] SET [name] = 'Milky Way', [type] = 'spiral' WHERE [id] = 3"); + }); + it('should utilize pre-existing value set in in set_array', () => { + qb.reset_query(); + qb.set(test_data); + const sql = qb.update('galaxies'); + sql.should.eql("UPDATE [galaxies] SET [name] = 'Milky Way', [type] = 'spiral'"); + }); + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { + qb.reset_query(); + qb.from('galaxies').set(test_data); + const sql = qb.update(); + sql.should.eql("UPDATE [galaxies] SET [name] = 'Milky Way', [type] = 'spiral'"); + }); + it('should accept a non-empty object for the data parameter', () => { + qb.reset_query(); + const sql = qb.update('galaxies', test_data); + sql.should.eql("UPDATE [galaxies] SET [name] = 'Milky Way', [type] = 'spiral'"); + }); + it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', () => { + qb.reset_query(); + const sql = qb.update('galaxies', test_data_set); + qb.reset_query(); + const sql_b = qb.update_batch('galaxies', test_data_set, 'id'); + sql.should.eql(sql_b); + }); + it('should fail if any invalid values are passed in the data object.', () => { + qb.reset_query(); + const func = () => console.log("foo"); + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; + + qb.reset_query(); expect(() => qb.update('galaxies',{id: func}), 'function in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: regex}), 'regex in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: undefined}), 'undefined in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: arr}), 'array in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: obj}), 'object in data').to.throw(Error); + + }); + it('should allow for a LIMITed update', () => { + qb.reset_query(); + const sql = qb.limit(10).update('galaxies', test_data, test_where); + sql.should.eql("UPDATE [t] SET [name] = 'Milky Way', [type] = 'spiral' FROM (SELECT TOP 10 * FROM [galaxies] WHERE [id] = 3) [t]"); + }); +}); diff --git a/test/mysql/01-tests-from.js b/test/mysql/01-tests-from.js index 6f97d0e..412e4b2 100755 --- a/test/mysql/01-tests-from.js +++ b/test/mysql/01-tests-from.js @@ -3,121 +3,121 @@ const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); describe('from()', () => { - it('should exist', () => { - should.exist(qb.from); - }); - it('should be a function', () => { - qb.from.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('from_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.from_array.should.be.empty; - }); - it('should add an item to an array and escape it properly', () => { - qb.from('universe'); - qb.from_array.should.eql(['`universe`']); - }) - it('should accept a comma-delimited string of items and trim and escape each properly', () => { - qb.reset_query(); - qb.from('universe,galaxy , star_system, planet'); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should have an empty array after resetting', () => { - qb.reset_query(); - qb.from_array.should.be.empty; - }); - it('should be allowed to be called multiple times to add multiple items to the from array', () => { - qb.reset_query(); - qb.from('universe').from('galaxy').from('star_system').from('planet'); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should accept an array of items and add them individually to the from array', () => { - qb.reset_query(); - qb.from(['universe','galaxy','star_system','planet']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); - }); - it('should not double-escape an item', () => { - qb.reset_query(); - qb.from('`do`'); - qb.from_array.should.eql(['`do`']); - }); - it('should not double-escape items when provided with an array of pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe`','`galaxy`','`star_system`']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); - }); - it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe`','galaxy','`star_system`']); - qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); - }); - it('should allow for aliases and it should escape them properly', () => { - qb.reset_query(); - qb.from('universe u'); - qb.from_array.should.eql(['`universe` `u`']); - }); - it('should allow for the word AS to be used to alias an item', () => { - qb.reset_query(); - qb.from('universe as u'); - qb.from_array.should.eql(['`universe` as `u`']); - }); - it('should allow for an array of item + aliases and it should escape them all properly', () => { - qb.reset_query(); - qb.from(['universe u', 'galaxy g']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { - qb.reset_query(); - qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { - qb.reset_query(); - qb.from(['`universe` u', 'galaxy `g`']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should add aliases to alias-tracking array', () => { - qb.reset_query(); - qb.from(['`universe` `u`', '`galaxy` `g`']); - qb.aliased_tables.should.eql(['u','g']); - }); - it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { - qb.reset_query(); - qb.from(['universe u, galaxy g']); - qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); - }); - it('should allow for namespacing in field name (host.db.table)', () => { - qb.reset_query(); - qb.from('star_system.planet'); - qb.from_array.should.eql(['`star_system`.`planet`']); + it('should exist', () => { + should.exist(qb.from); + }); + it('should be a function', () => { + qb.from.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('from_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.from_array.should.be.empty; + }); + it('should add an item to an array and escape it properly', () => { + qb.from('universe'); + qb.from_array.should.eql(['`universe`']); + }) + it('should accept a comma-delimited string of items and trim and escape each properly', () => { + qb.reset_query(); + qb.from('universe,galaxy , star_system, planet'); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should have an empty array after resetting', () => { + qb.reset_query(); + qb.from_array.should.be.empty; + }); + it('should be allowed to be called multiple times to add multiple items to the from array', () => { + qb.reset_query(); + qb.from('universe').from('galaxy').from('star_system').from('planet'); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should accept an array of items and add them individually to the from array', () => { + qb.reset_query(); + qb.from(['universe','galaxy','star_system','planet']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`','`planet`']); + }); + it('should not double-escape an item', () => { + qb.reset_query(); + qb.from('`do`'); + qb.from_array.should.eql(['`do`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe`','`galaxy`','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should not double-escape items when provided with an array of pre-escaped items but should escpae non-pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe`','galaxy','`star_system`']); + qb.from_array.should.eql(['`universe`','`galaxy`','`star_system`']); + }); + it('should allow for aliases and it should escape them properly', () => { + qb.reset_query(); + qb.from('universe u'); + qb.from_array.should.eql(['`universe` `u`']); + }); + it('should allow for the word AS to be used to alias an item', () => { + qb.reset_query(); + qb.from('universe as u'); + qb.from_array.should.eql(['`universe` as `u`']); + }); + it('should allow for an array of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u', 'galaxy g']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases that are pre-escaped and it should not double-escape them', () => { + qb.reset_query(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for an array of item + aliases where some are pre-escaped and it should not double-escape pre-escaped items', () => { + qb.reset_query(); + qb.from(['`universe` u', 'galaxy `g`']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should add aliases to alias-tracking array', () => { + qb.reset_query(); + qb.from(['`universe` `u`', '`galaxy` `g`']); + qb.aliased_tables.should.eql(['u','g']); + }); + it('should allow for an comma-delimited list of item + aliases and it should escape them all properly', () => { + qb.reset_query(); + qb.from(['universe u, galaxy g']); + qb.from_array.should.eql(['`universe` `u`','`galaxy` `g`']); + }); + it('should allow for namespacing in field name (host.db.table)', () => { + qb.reset_query(); + qb.from('star_system.planet'); + qb.from_array.should.eql(['`star_system`.`planet`']); - qb.reset_query(); - qb.from('galaxy.star_system.planet'); - qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); - }); - it('should allow for namespacing in field name (host.db.table.column) + alias', () => { - qb.reset_query(); - qb.from('universe.galaxy.star_system planet'); - qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); - }); - it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { - qb.reset_query(); - qb.from('universe.galaxy.star_system as planet'); - qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); - }); - it('should accept but ignore empty strings and empty strings within arrays', () => { - qb.reset_query(); - qb.from(''); - qb.from_array.should.be.empty; + qb.reset_query(); + qb.from('galaxy.star_system.planet'); + qb.from_array.should.eql(['`galaxy`.`star_system`.`planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system planet'); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` `planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias (declare with AS)', () => { + qb.reset_query(); + qb.from('universe.galaxy.star_system as planet'); + qb.from_array.should.eql(['`universe`.`galaxy`.`star_system` as `planet`']); + }); + it('should accept but ignore empty strings and empty strings within arrays', () => { + qb.reset_query(); + qb.from(''); + qb.from_array.should.be.empty; - qb.reset_query(); - qb.from(['','']); - qb.from_array.should.be.empty; + qb.reset_query(); + qb.from(['','']); + qb.from_array.should.be.empty; - qb.reset_query(); - qb.from(['','foobar']); - qb.from_array.should.eql(['`foobar`']); - }); + qb.reset_query(); + qb.from(['','foobar']); + qb.from_array.should.eql(['`foobar`']); + }); }); diff --git a/test/mysql/01-tests-get.js b/test/mysql/01-tests-get.js index b8ba156..a22a31d 100755 --- a/test/mysql/01-tests-get.js +++ b/test/mysql/01-tests-get.js @@ -4,194 +4,194 @@ const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); describe('get()', () => { - it('should exist', () => { - should.exist(qb.get); - }); - it('should be a function', () => { - qb.get.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - qb.get('galaxies'); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should add a set of tables to from_array when an array of tables is supplied', () => { - qb.reset_query(); - qb.get(['galaxies','star_systems','planets']); - qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); - }); - it('should not accept anything but a non-empty string or an array of non-empty strings', () => { - qb.reset_query(); - expect(() => qb.get(), 'nothing provided').to.throw(Error); - expect(() => qb.get({foo: 'bar'}), 'object provided').to.throw(Error); - expect(() => qb.get(true), 'boolean provided').to.throw(Error); - expect(() => qb.get(NaN), 'NaN provided').to.throw(Error); - expect(() => qb.get(null), 'NULL provided').to.throw(Error); - expect(() => qb.get(1), 'Integer provided').to.throw(Error); - expect(() => qb.get(1.1), 'Float provided').to.throw(Error); - expect(() => qb.get(1.1), 'Float provided').to.throw(Error); - expect(() => qb.get([]), 'Empty array provided').to.throw(Error); - expect(() => qb.get(''), 'Empty string provided').to.throw(Error); - expect(() => qb.get('galaxies'), 'Valid string provided').to.not.throw(Error); - expect(() => qb.get(['galaxies','planets']), 'Array of non-empty strings provided').to.not.throw(Error); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.get('galaxies'); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', () => { - qb.reset_query(); - const sql = qb.get('galaxies'); - sql.should.eql('SELECT * FROM `galaxies`'); - }); - it('should properly handle alias if provided in table string', () => { - qb.reset_query(); - const sql = qb.get('galaxies g'); - sql.should.eql('SELECT * FROM `galaxies` `g`'); - }); - it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', () => { - qb.reset_query(); - const sql = qb.select(['id','name']).get('galaxies'); - sql.should.eql("SELECT `id`, `name` FROM `galaxies`"); - }); - it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', () => { - qb.reset_query(); - const sql = qb.where('class','M').get('galaxies'); - sql.should.eql("SELECT * FROM `galaxies` WHERE `class` = 'M'"); - }); - it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', () => { - qb.reset_query(); - const sql = qb.get(['galaxies','star_systems','planets']); - sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); - }); - it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', () => { - qb.reset_query(); - const sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); - sql.should.eql("SELECT * FROM `galaxies`, `star_systems`, `planets` WHERE `class` = 'M'"); - }); - it('should use tables added previously via the from() method', () => { - qb.reset_query(); - qb.from('galaxies'); - let sql = qb.get(); - sql.should.eql('SELECT * FROM `galaxies`'); + it('should exist', () => { + should.exist(qb.get); + }); + it('should be a function', () => { + qb.get.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.get('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', () => { + qb.reset_query(); + qb.get(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + }); + it('should not accept anything but a non-empty string or an array of non-empty strings', () => { + qb.reset_query(); + expect(() => qb.get(), 'nothing provided').to.throw(Error); + expect(() => qb.get({foo: 'bar'}), 'object provided').to.throw(Error); + expect(() => qb.get(true), 'boolean provided').to.throw(Error); + expect(() => qb.get(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.get(null), 'NULL provided').to.throw(Error); + expect(() => qb.get(1), 'Integer provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get(1.1), 'Float provided').to.throw(Error); + expect(() => qb.get([]), 'Empty array provided').to.throw(Error); + expect(() => qb.get(''), 'Empty string provided').to.throw(Error); + expect(() => qb.get('galaxies'), 'Valid string provided').to.not.throw(Error); + expect(() => qb.get(['galaxies','planets']), 'Array of non-empty strings provided').to.not.throw(Error); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.get('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table if only a table is given', () => { + qb.reset_query(); + const sql = qb.get('galaxies'); + sql.should.eql('SELECT * FROM `galaxies`'); + }); + it('should properly handle alias if provided in table string', () => { + qb.reset_query(); + const sql = qb.get('galaxies g'); + sql.should.eql('SELECT * FROM `galaxies` `g`'); + }); + it('should build a properly-escaped SELECT statement that retrieves all fields specified from a table', () => { + qb.reset_query(); + const sql = qb.select(['id','name']).get('galaxies'); + sql.should.eql("SELECT `id`, `name` FROM `galaxies`"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a table that match passed WHERE conditions', () => { + qb.reset_query(); + const sql = qb.where('class','M').get('galaxies'); + sql.should.eql("SELECT * FROM `galaxies` WHERE `class` = 'M'"); + }); + it('should build a properly-escaped SELECT statement that retrieves all records from a set of joined tables if an array of tables is given', () => { + qb.reset_query(); + const sql = qb.get(['galaxies','star_systems','planets']); + sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); + }); + it('should build a properly-escaped SELECT statement that retrieves all records in a set of tables that match the passed conditions', () => { + qb.reset_query(); + const sql = qb.where('class', 'M').get(['galaxies','star_systems','planets']); + sql.should.eql("SELECT * FROM `galaxies`, `star_systems`, `planets` WHERE `class` = 'M'"); + }); + it('should use tables added previously via the from() method', () => { + qb.reset_query(); + qb.from('galaxies'); + let sql = qb.get(); + sql.should.eql('SELECT * FROM `galaxies`'); - qb.reset_query(); - sql = qb.from(['galaxies','star_systems','planets']).get(); - sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); - }); - it('should accept where conditions added previously via the where() method', () => { - qb.reset_query(); - const sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); - sql.should.eql("SELECT * FROM `galaxies` WHERE `created` >= 4600000000 AND `classification` = 'M'"); - }); - it('should accept a limit on the number of rows selected', () => { - qb.reset_query(); - const sql = qb.limit(20).get('galaxies'); - sql.should.eql("SELECT * FROM `galaxies` LIMIT 20"); - }); - it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', () => { - qb.reset_query(); - const sql = qb.limit(20,10).get('galaxies'); - sql.should.eql("SELECT * FROM `galaxies` LIMIT 10, 20"); - }); - it('should include the DISTINCT keyword if the distinct() method is called', () => { - qb.reset_query(); - const sql = qb.distinct().select(['id','name']).get('galaxies'); - sql.should.eql("SELECT DISTINCT `id`, `name` FROM `galaxies`"); - }); - it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', () => { - qb.reset_query(); + qb.reset_query(); + sql = qb.from(['galaxies','star_systems','planets']).get(); + sql.should.eql('SELECT * FROM `galaxies`, `star_systems`, `planets`'); + }); + it('should accept where conditions added previously via the where() method', () => { + qb.reset_query(); + const sql = qb.where('created >=',4.6E9).where({classification: 'M'}).get('galaxies'); + sql.should.eql("SELECT * FROM `galaxies` WHERE `created` >= 4600000000 AND `classification` = 'M'"); + }); + it('should accept a limit on the number of rows selected', () => { + qb.reset_query(); + const sql = qb.limit(20).get('galaxies'); + sql.should.eql("SELECT * FROM `galaxies` LIMIT 20"); + }); + it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', () => { + qb.reset_query(); + const sql = qb.limit(20,10).get('galaxies'); + sql.should.eql("SELECT * FROM `galaxies` LIMIT 10, 20"); + }); + it('should include the DISTINCT keyword if the distinct() method is called', () => { + qb.reset_query(); + const sql = qb.distinct().select(['id','name']).get('galaxies'); + sql.should.eql("SELECT DISTINCT `id`, `name` FROM `galaxies`"); + }); + it('should include the MIN, MAX, AVG, or SUM aggregation methods in the select statement if provided', () => { + qb.reset_query(); - // MIN - let sql = qb.select_min('size','min_size').get('galaxies'); - sql.should.eql("SELECT MIN(`size`) AS min_size FROM `galaxies`"); - qb.reset_query(); + // MIN + let sql = qb.select_min('size','min_size').get('galaxies'); + sql.should.eql("SELECT MIN(`size`) AS min_size FROM `galaxies`"); + qb.reset_query(); - // MAX - sql = qb.select_max('size','max_size').get('galaxies'); - sql.should.eql("SELECT MAX(`size`) AS max_size FROM `galaxies`"); - qb.reset_query(); + // MAX + sql = qb.select_max('size','max_size').get('galaxies'); + sql.should.eql("SELECT MAX(`size`) AS max_size FROM `galaxies`"); + qb.reset_query(); - // AVG - sql = qb.select_avg('size','avg_size').get('galaxies'); - sql.should.eql("SELECT AVG(`size`) AS avg_size FROM `galaxies`"); - qb.reset_query(); + // AVG + sql = qb.select_avg('size','avg_size').get('galaxies'); + sql.should.eql("SELECT AVG(`size`) AS avg_size FROM `galaxies`"); + qb.reset_query(); - // SUM - sql = qb.select_sum('size','total_size').get('galaxies'); - sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); - }); - it('should include any joins that were added in the chain', () => { - qb.reset_query(); - const sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) - .join('galaxies g','g.id=s.galaxy_id','left') - .get('star_systems s'); - sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); - }); - it('should include any GROUP BY statements added using the group_by() method.', () => { - qb.reset_query(); - const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); - }); - it('should add the ORDER BY clause of the order_by() method was called in the chain', () => { - qb.reset_query(); - const sql = qb.order_by('size').get('galaxies'); - sql.should.eql("SELECT * FROM `galaxies` ORDER BY `size` ASC"); - }); - it('should include any HAVING clauses added using the having() method', () => { - qb.reset_query(); - const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); - sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); - }); + // SUM + sql = qb.select_sum('size','total_size').get('galaxies'); + sql.should.eql("SELECT SUM(`size`) AS total_size FROM `galaxies`"); + }); + it('should include any joins that were added in the chain', () => { + qb.reset_query(); + const sql = qb.select(['s.name as star_system_name', 'g.name as galaxy_name']) + .join('galaxies g','g.id=s.galaxy_id','left') + .get('star_systems s'); + sql.should.eql("SELECT `s`.`name` as `star_system_name`, `g`.`name` as `galaxy_name` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id` = `s`.`galaxy_id`"); + }); + it('should include any GROUP BY statements added using the group_by() method.', () => { + qb.reset_query(); + const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').get('galaxies'); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size`"); + }); + it('should add the ORDER BY clause of the order_by() method was called in the chain', () => { + qb.reset_query(); + const sql = qb.order_by('size').get('galaxies'); + sql.should.eql("SELECT * FROM `galaxies` ORDER BY `size` ASC"); + }); + it('should include any HAVING clauses added using the having() method', () => { + qb.reset_query(); + const sql = qb.select('size').select('COUNT(id) as `num_of_size`',false).group_by('size').having('num_of_size >=',456034960).get('galaxies'); + sql.should.eql("SELECT `size`, COUNT(id) AS `num_of_size` FROM `galaxies` GROUP BY `size` HAVING `num_of_size` >= 456034960"); + }); }); describe('get_where()', () => { - it('should exist', () => { - should.exist(qb.get_where); - }); - it('should be a function', () => { - qb.get_where.should.be.a('function'); - }); - it('should require the first parameter to be a table in string format or tables array format', () => { - qb.reset_query(); - expect(() => qb.get_where(), 'nothing provided').to.throw(Error); - expect(() => qb.get_where(''), 'empty string for table').to.throw(Error); - expect(() => qb.get_where([]), 'empty array for tables').to.throw(Error); - expect(() => qb.get_where(['']), 'array of empty strings for tables').to.throw(Error); - expect(() => qb.get_where(1), 'integer for table').to.throw(Error); - expect(() => qb.get_where(5.5), 'float for table').to.throw(Error); - expect(() => qb.get_where(true), 'TRUE for table').to.throw(Error); - expect(() => qb.get_where(false), 'FALSE for table').to.throw(Error); - expect(() => qb.get_where(null), 'NULL for table').to.throw(Error); - expect(() => qb.get_where({}), 'Standard object for table').to.throw(Error); - expect(() => qb.get_where(Infinite), 'Infinite for table').to.throw(Error); - expect(() => qb.get_where('galaxies'), 'valid table, no where').to.throw(Error); - expect(() => qb.get_where('galaxies',{}), 'valid table, empty where').to.throw(Error); - expect(() => qb.get_where('galaxies',[]), 'valid table, array for where').to.throw(Error); - expect(() => qb.get_where('galaxies',3), 'valid table, integer for where').to.throw(Error); - expect(() => qb.get_where('galaxies',33.3), 'valid table, float for where').to.throw(Error); - expect(() => qb.get_where('galaxies','foo'), 'valid table, string for where').to.throw(Error); - expect(() => qb.get_where('galaxies',true), 'valid table, TRUE for where').to.throw(Error); - expect(() => qb.get_where('galaxies',false), 'valid table, FALSE for where').to.throw(Error); - expect(() => qb.get_where('galaxies',Infinite), 'valid table, Infinite where').to.throw(Error); - expect(() => qb.get_where('galaxies',null), 'valid table, NULL where').to.throw(Error); - expect(() => qb.get_where('galaxies',{id: 3}), 'valid table, valid where').to.not.throw(Error); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.get('galaxies', {type: 'spiral'}); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should add table(s) to from_array and where items to where_array', () => { - qb.reset_query(); - const sql = qb.get_where('galaxies', {type: 'spiral'}); - qb.from_array.should.eql(['`galaxies`']); - qb.where_array.should.eql(["`type` = 'spiral'"]); - sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); - }); + it('should exist', () => { + should.exist(qb.get_where); + }); + it('should be a function', () => { + qb.get_where.should.be.a('function'); + }); + it('should require the first parameter to be a table in string format or tables array format', () => { + qb.reset_query(); + expect(() => qb.get_where(), 'nothing provided').to.throw(Error); + expect(() => qb.get_where(''), 'empty string for table').to.throw(Error); + expect(() => qb.get_where([]), 'empty array for tables').to.throw(Error); + expect(() => qb.get_where(['']), 'array of empty strings for tables').to.throw(Error); + expect(() => qb.get_where(1), 'integer for table').to.throw(Error); + expect(() => qb.get_where(5.5), 'float for table').to.throw(Error); + expect(() => qb.get_where(true), 'TRUE for table').to.throw(Error); + expect(() => qb.get_where(false), 'FALSE for table').to.throw(Error); + expect(() => qb.get_where(null), 'NULL for table').to.throw(Error); + expect(() => qb.get_where({}), 'Standard object for table').to.throw(Error); + expect(() => qb.get_where(Infinite), 'Infinite for table').to.throw(Error); + expect(() => qb.get_where('galaxies'), 'valid table, no where').to.throw(Error); + expect(() => qb.get_where('galaxies',{}), 'valid table, empty where').to.throw(Error); + expect(() => qb.get_where('galaxies',[]), 'valid table, array for where').to.throw(Error); + expect(() => qb.get_where('galaxies',3), 'valid table, integer for where').to.throw(Error); + expect(() => qb.get_where('galaxies',33.3), 'valid table, float for where').to.throw(Error); + expect(() => qb.get_where('galaxies','foo'), 'valid table, string for where').to.throw(Error); + expect(() => qb.get_where('galaxies',true), 'valid table, TRUE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',false), 'valid table, FALSE for where').to.throw(Error); + expect(() => qb.get_where('galaxies',Infinite), 'valid table, Infinite where').to.throw(Error); + expect(() => qb.get_where('galaxies',null), 'valid table, NULL where').to.throw(Error); + expect(() => qb.get_where('galaxies',{id: 3}), 'valid table, valid where').to.not.throw(Error); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.get('galaxies', {type: 'spiral'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should add table(s) to from_array and where items to where_array', () => { + qb.reset_query(); + const sql = qb.get_where('galaxies', {type: 'spiral'}); + qb.from_array.should.eql(['`galaxies`']); + qb.where_array.should.eql(["`type` = 'spiral'"]); + sql.should.eql("SELECT * FROM `galaxies` WHERE `type` = 'spiral'"); + }); }); diff --git a/test/mysql/01-tests-groupby.js b/test/mysql/01-tests-groupby.js index d26e68f..ca633d5 100755 --- a/test/mysql/01-tests-groupby.js +++ b/test/mysql/01-tests-groupby.js @@ -4,49 +4,49 @@ const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); describe('group_by()', () => { - it('should exist', () => { - should.exist(qb.group_by); - }); - it('should be a function', () => { - qb.group_by.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('group_by_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.group_by_array.should.be.empty; - }); - it('should accept a single field in string form', () => { - qb.reset_query(); - qb.group_by('planet_type'); - qb.group_by_array.should.eql(['`planet_type`']); - }); - it('should accept a multiple fields delimited by commas', () => { - qb.reset_query(); - qb.group_by('planet_type, planet_position'); - qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); - }); - it('should accept an array of fields', () => { - qb.reset_query(); - qb.group_by(['planet_type', 'planet_position']); - qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); - }); - it('should not accept anything but a string or an array of strings', () => { - qb.reset_query(); - expect(() => qb.group_by(), 'nothing provided').to.throw(Error); - expect(() => qb.group_by(null), 'null provided').to.throw(Error); - expect(() => qb.group_by(false), 'false provided').to.throw(Error); - expect(() => qb.group_by(true), 'true provided').to.throw(Error); - expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); - expect(() => qb.group_by(3), 'integer provided').to.throw(Error); - expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); - expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); - expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); + it('should exist', () => { + should.exist(qb.group_by); + }); + it('should be a function', () => { + qb.group_by.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('group_by_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.group_by_array.should.be.empty; + }); + it('should accept a single field in string form', () => { + qb.reset_query(); + qb.group_by('planet_type'); + qb.group_by_array.should.eql(['`planet_type`']); + }); + it('should accept a multiple fields delimited by commas', () => { + qb.reset_query(); + qb.group_by('planet_type, planet_position'); + qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); + }); + it('should accept an array of fields', () => { + qb.reset_query(); + qb.group_by(['planet_type', 'planet_position']); + qb.group_by_array.should.eql(['`planet_type`','`planet_position`']); + }); + it('should not accept anything but a string or an array of strings', () => { + qb.reset_query(); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); - // valid string - expect(() => qb.group_by('planet_type'), 'valid string provided').to.not.throw(Error); - expect(() => qb.group_by(['planet_type']), 'array of string(s) provided').to.not.throw(Error); + // valid string + expect(() => qb.group_by('planet_type'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type']), 'array of string(s) provided').to.not.throw(Error); - }); + }); }); diff --git a/test/mysql/01-tests-join.js b/test/mysql/01-tests-join.js index ec659f3..d05fbe0 100755 --- a/test/mysql/01-tests-join.js +++ b/test/mysql/01-tests-join.js @@ -4,111 +4,121 @@ const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); describe('join()', () => { - it('should exist', () => { - should.exist(qb.join); - }); - it('should be a function', () => { - qb.join.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('join_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.join_array.should.be.empty; - }); - it('should require a string to be passed as first parameter', () => { - const invalid_match = /must provide a table/; - expect(() => qb.join(), 'nothing provided').to.throw(Error, invalid_match); - expect(() => qb.join(true), 'true provided').to.throw(Error, invalid_match); - expect(() => qb.join(null), 'null provided').to.throw(Error, invalid_match); - expect(() => qb.join(false), 'false provided').to.throw(Error, invalid_match); - expect(() => qb.join({}), 'object provided').to.throw(Error, invalid_match); - expect(() => qb.join([]), 'empty array provided').to.throw(Error, invalid_match); - expect(() => qb.join(''), 'empty string provided').to.throw(Error, invalid_match); - expect(() => qb.join(' '), 'string of spaces provided').to.throw(Error, invalid_match); - expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); - expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); - }); - it('should except single item and add it to join array as basic join and escape item', () => { - qb.reset_query(); - qb.join('universe'); - qb.join_array.should.eql(['JOIN `universe` ']); - }); - it('should except single item with alias and add it to join array as basic join and escape each part', () => { - qb.reset_query(); - qb.join('universe u'); - qb.join_array.should.eql(['JOIN `universe` `u` ']); - }); - it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', () => { - const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; - const invalid_direction = /Invalid join direction provided as third parameter/; + it('should exist', () => { + should.exist(qb.join); + }); + it('should be a function', () => { + qb.join.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('join_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.join_array.should.be.empty; + }); + it('should require a string to be passed as first parameter', () => { + const invalid_match = /must provide a table/; + expect(() => qb.join(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.join(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.join(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.join(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.join({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.join([]), 'empty array provided').to.throw(Error, invalid_match); + expect(() => qb.join(''), 'empty string provided').to.throw(Error, invalid_match); + expect(() => qb.join(' '), 'string of spaces provided').to.throw(Error, invalid_match); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + expect(() => qb.join('foo'), 'valid string provided').to.not.throw(Error); + }); + it('should except single item and add it to join array as basic join and escape item', () => { + qb.reset_query(); + qb.join('universe'); + qb.join_array.should.eql(['JOIN `universe`']); + }); + it('should except single item with alias and add it to join array as basic join and escape each part', () => { + qb.reset_query(); + qb.join('universe u'); + qb.join_array.should.eql(['JOIN `universe` `u`']); + }); + it('should allow a string (and only a string) to be passed as second parameter but only if a valid (or no) third parameter is provided', () => { + const invalid_2nd_param = /You must provide a valid condition to join on when providing a join direction/; + const invalid_direction = /Invalid join direction provided as third parameter/; - expect(() => qb.join('universe',null,'left'), 'null 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',false,'left'), 'false 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe','','left'), 'empty string 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',' ','left'), 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',5,'left'), 'integer 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',5.6,'left'), 'float 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',[],'left'), 'array 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe',{},'left'), 'object 2nd param').to.throw(Error,invalid_2nd_param); - expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); - expect(() => qb.join('universe','foo = bar'), 'no 3rd param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar','left'), '3 valid params').to.not.throw(Error); - }); - it('should allow valid join direction to be passed in third parameter', () => { - // NOTE: A lot of this functionality was already tested when testing second param - const invalid_direction = /Invalid join direction provided as third parameter/; + expect(() => qb.join('universe',null,'left'), 'null 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',false,'left'), 'false 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','','left'), 'empty string 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',' ','left'), 'just spaces 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5,'left'), 'integer 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',5.6,'left'), 'float 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',[],'left'), 'array 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe',{},'left'), 'object 2nd param').to.throw(Error,invalid_2nd_param); + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe','foo = bar'), 'no 3rd param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), '3 valid params').to.not.throw(Error); + }); + it('should allow valid join direction to be passed in third parameter', () => { + // NOTE: A lot of this functionality was already tested when testing second param + const invalid_direction = /Invalid join direction provided as third parameter/; - expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); - expect(() => qb.join('universe',null,null), 'invalid 2nd and 3rd params').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',''), 'empty third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',' '), 'just spaces').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',null), 'null third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',false), 'false third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',5), 'integer third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',5.5), 'float third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',{}), 'object third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',[]), 'array third param').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar','left '), 'trailing space').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',' left '), 'leading and trailing space').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar',' left'), 'leading space').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar','left'), 'lowercase direction').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar','LEFT'), 'uppercase direction').to.not.throw(Error); - expect(() => qb.join('universe','foo = bar','LEFT OUTER'), 'two word direction').to.not.throw(Error); - }); - it('should except a valid second parameter as a join condition and escape it properly', () => { - qb.reset_query(); - qb.join('universe u','u.type_id = ut.id'); - qb.join_array.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); - }); - it('should escape compound objects properly', () => { - qb.reset_query(); - qb.join('universe.galaxy.star_system s','s.type_id = st.id'); - qb.join_array.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); - }); - it('should add aliases to alias-tracking array', () => { - qb.reset_query(); - qb.join('universe.galaxy.star_system s'); - qb.aliased_tables.should.eql(['s']); - }); - it('should properly place join direction into join clause', () => { - qb.reset_query(); - qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); - qb.join_array.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); - }); - it('should be chainable to allow for multiple join clauses', () => { - qb.reset_query(); - qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); - qb.join_array.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); - }); - it('should escape complex join conditions', () => { - qb.reset_query(); - qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); - qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); - }); + expect(() => qb.join('universe','foo = bar','fake'), 'invalid 3rd param').to.throw(Error,invalid_direction); + expect(() => qb.join('universe',null,null), 'invalid 2nd and 3rd params').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',''), 'empty third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' '), 'just spaces').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',null), 'null third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',false), 'false third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5), 'integer third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',5.5), 'float third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',{}), 'object third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',[]), 'array third param').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left '), 'trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left '), 'leading and trailing space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar',' left'), 'leading space').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','left'), 'lowercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT'), 'uppercase direction').to.not.throw(Error); + expect(() => qb.join('universe','foo = bar','LEFT OUTER'), 'two word direction').to.not.throw(Error); + }); + it('should except a valid second parameter as a join condition and escape it properly', () => { + qb.reset_query(); + qb.join('universe u','u.type_id = ut.id'); + qb.join_array.should.eql(['JOIN `universe` `u` ON `u`.`type_id` = `ut`.`id`']); + }); + it('should escape compound objects properly', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s','s.type_id = st.id'); + qb.join_array.should.eql(['JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + }); + it('should add aliases to alias-tracking array', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s'); + qb.aliased_tables.should.eql(['s']); + }); + it('should properly place join direction into join clause', () => { + qb.reset_query(); + qb.join('universe.galaxy.star_system s', 's.type_id = st.id', 'left outer'); + qb.join_array.should.eql(['LEFT OUTER JOIN `universe`.`galaxy`.`star_system` `s` ON `s`.`type_id` = `st`.`id`']); + }); + it('should be chainable to allow for multiple join clauses', () => { + qb.reset_query(); + qb.join('star_system s', 's.type_id = st.id', 'left outer').join('planets p','p.star_system_id = s.id','left'); + qb.join_array.should.eql(['LEFT OUTER JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id`', 'LEFT JOIN `planets` `p` ON `p`.`star_system_id` = `s`.`id`']); + }); + it('should escape complex join conditions', () => { + qb.reset_query(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left'); + qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `s`.`type_id` = `st`.`id` AND `st`.`active` = 1 AND `st`.`created_on` > '2014-01-01'"]); + }); + it('should escape complex join conditions when there is `or` in the right-hand side of the condition', () => { + qb.reset_query(); + qb.join('star_system s', " st.type = 'foo or bar' AND s.type_id = st.id", 'left'); + qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `st`.`type` = 'foo or bar' AND `s`.`type_id` = `st`.`id`"]); + }); + it('should escape complex join conditions when there is `and` in the right-hand side of the condition', () => { + qb.reset_query(); + qb.join('star_system s', "st.type = 'foo and bar' AND s.type_id = st.id", 'left'); + qb.join_array.should.eql(["LEFT JOIN `star_system` `s` ON `st`.`type` = 'foo and bar' AND `s`.`type_id` = `st`.`id`"]); + }); it('should NOT escape any part of join query when asked not to', () => { - qb.reset_query(); - qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left', false); - qb.join_array.should.eql(["LEFT JOIN star_system s ON s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'"]); - }); + qb.reset_query(); + qb.join('star_system s', "s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'", 'left', false); + qb.join_array.should.eql(["LEFT JOIN star_system s ON s.type_id = st.id AND st.active = 1 AND st.created_on > '2014-01-01'"]); + }); }); diff --git a/test/mysql/01-tests-where.js b/test/mysql/01-tests-where.js index c3447ef..ae79e01 100755 --- a/test/mysql/01-tests-where.js +++ b/test/mysql/01-tests-where.js @@ -4,171 +4,171 @@ const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); describe('where()', () => { - it('should exist', () => { - should.exist(qb.where); - }); - it('should be a function', () => { - qb.where.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('where_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.where_array.should.be.empty; - }); - it('should not accept anything but a non-empty string or a non-empty object', () => { - qb.reset_query(); - expect(() => qb.where(), 'nothing provided').to.throw(Error); - expect(() => qb.where(null), 'null provided').to.throw(Error); - expect(() => qb.where(false), 'false provided').to.throw(Error); - expect(() => qb.where(true), 'true provided').to.throw(Error); - expect(() => qb.where({}), 'empty object provided').to.throw(Error); - expect(() => qb.where(3), 'integer provided').to.throw(Error); - expect(() => qb.where(3.5), 'float provided').to.throw(Error); - expect(() => qb.where([]), 'empty array provided').to.throw(Error); - expect(() => qb.where([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.where(''), 'empty string provided').to.throw(Error); + it('should exist', () => { + should.exist(qb.where); + }); + it('should be a function', () => { + qb.where.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('where_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.where_array.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty object', () => { + qb.reset_query(); + expect(() => qb.where(), 'nothing provided').to.throw(Error); + expect(() => qb.where(null), 'null provided').to.throw(Error); + expect(() => qb.where(false), 'false provided').to.throw(Error); + expect(() => qb.where(true), 'true provided').to.throw(Error); + expect(() => qb.where({}), 'empty object provided').to.throw(Error); + expect(() => qb.where(3), 'integer provided').to.throw(Error); + expect(() => qb.where(3.5), 'float provided').to.throw(Error); + expect(() => qb.where([]), 'empty array provided').to.throw(Error); + expect(() => qb.where([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where(''), 'empty string provided').to.throw(Error); - expect(() => qb.where('planet_position',3), 'valid string provided').to.not.throw(Error); - expect(() => qb.where({planet_position: 3}), 'valid object provided').to.not.throw(Error); + expect(() => qb.where('planet_position',3), 'valid string provided').to.not.throw(Error); + expect(() => qb.where({planet_position: 3}), 'valid object provided').to.not.throw(Error); - }); - it('should accept a field name in the form of a string as the first parameter', () => { - qb.reset_query(); - qb.where('planet'); - qb.where_array.should.eql(['`planet` IS NULL']); - }); - it('should assume second param is NULL if not provided', () => { - qb.reset_query(); - qb.where('planet'); - qb.where_array.should.eql(['`planet` IS NULL']); - }); - it('should accept NULL as second parameter and assume IS NULL', () => { - qb.reset_query(); - qb.where('planet',null); - qb.where_array.should.eql(['`planet` IS NULL']); - }); - it('should accept boolean values and will transform them properly', () => { - qb.reset_query(); - qb.where('planet',true); - qb.where_array.should.eql(['`planet` = 1']); + }); + it('should accept a field name in the form of a string as the first parameter', () => { + qb.reset_query(); + qb.where('planet'); + qb.where_array.should.eql(['`planet` IS NULL']); + }); + it('should assume second param is NULL if not provided', () => { + qb.reset_query(); + qb.where('planet'); + qb.where_array.should.eql(['`planet` IS NULL']); + }); + it('should accept NULL as second parameter and assume IS NULL', () => { + qb.reset_query(); + qb.where('planet',null); + qb.where_array.should.eql(['`planet` IS NULL']); + }); + it('should accept boolean values and will transform them properly', () => { + qb.reset_query(); + qb.where('planet',true); + qb.where_array.should.eql(['`planet` = 1']); - qb.reset_query(); - qb.where('planet',false); - qb.where_array.should.eql(['`planet` = 0']); - }); - it('should accept integer and float values', () => { - qb.reset_query(); - qb.where('planet',5); - qb.where_array.should.eql(['`planet` = 5']); + qb.reset_query(); + qb.where('planet',false); + qb.where_array.should.eql(['`planet` = 0']); + }); + it('should accept integer and float values', () => { + qb.reset_query(); + qb.where('planet',5); + qb.where_array.should.eql(['`planet` = 5']); - qb.reset_query(); - qb.where('planet',123.456); - qb.where_array.should.eql(['`planet` = 123.456']); - }); - it('should accept string values', () => { - qb.reset_query(); - qb.where('planet','Earth'); - qb.where_array.should.eql(["`planet` = 'Earth'"]); + qb.reset_query(); + qb.where('planet',123.456); + qb.where_array.should.eql(['`planet` = 123.456']); + }); + it('should accept string values', () => { + qb.reset_query(); + qb.where('planet','Earth'); + qb.where_array.should.eql(["`planet` = 'Earth'"]); - qb.reset_query(); - qb.where('galaxy','Milky Way'); - qb.where_array.should.eql(["`galaxy` = 'Milky Way'"]); - }); - it('should accept arrays of values and assume a WHERE IN clause', () => { - qb.reset_query(); - qb.where('planet',['Mercury','Venus','Earth','Mars']); - qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); - }); - it('should concatenate multiple where clauses with AND by default', () => { - qb.reset_query(); - qb.where('planet',['Mercury','Venus','Earth','Mars']); - qb.where('galaxy','Milky Way'); - qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); - qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); - }); - it('should accept an object of key/value pairs (field: value)', () => { - qb.reset_query(); - qb.where({planet:'Earth',star_system:'Solar'}); - qb.where_array.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); - }); - it('should accept an object of key/value pairs (field: value) where values can be arrays', () => { - qb.reset_query(); - qb.where({star_system:'Solar',planet:['Earth','Mars']}); - qb.where_array.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); - }); - it('should accept an operators in the first parameter', () => { - qb.reset_query(); - qb.where('position >',3); - qb.where_array.should.eql(["`position` > 3"]); + qb.reset_query(); + qb.where('galaxy','Milky Way'); + qb.where_array.should.eql(["`galaxy` = 'Milky Way'"]); + }); + it('should accept arrays of values and assume a WHERE IN clause', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')"]); + }); + it('should concatenate multiple where clauses with AND by default', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']); + qb.where('galaxy','Milky Way'); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where('planet',['Mercury','Venus','Earth','Mars']).where('galaxy','Milky Way'); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy` = 'Milky Way'"]); + }); + it('should accept an object of key/value pairs (field: value)', () => { + qb.reset_query(); + qb.where({planet:'Earth',star_system:'Solar'}); + qb.where_array.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'"]); + }); + it('should accept an object of key/value pairs (field: value) where values can be arrays', () => { + qb.reset_query(); + qb.where({star_system:'Solar',planet:['Earth','Mars']}); + qb.where_array.should.eql(["`star_system` = 'Solar'", "AND `planet` IN ('Earth', 'Mars')"]); + }); + it('should accept an operators in the first parameter', () => { + qb.reset_query(); + qb.where('position >',3); + qb.where_array.should.eql(["`position` > 3"]); - qb.reset_query(); - qb.where('position <',3); - qb.where_array.should.eql(["`position` < 3"]); + qb.reset_query(); + qb.where('position <',3); + qb.where_array.should.eql(["`position` < 3"]); - qb.reset_query(); - qb.where('position >=',3); - qb.where_array.should.eql(["`position` >= 3"]); + qb.reset_query(); + qb.where('position >=',3); + qb.where_array.should.eql(["`position` >= 3"]); - qb.reset_query(); - qb.where('position <=',3); - qb.where_array.should.eql(["`position` <= 3"]); + qb.reset_query(); + qb.where('position <=',3); + qb.where_array.should.eql(["`position` <= 3"]); - qb.reset_query(); - qb.where('position <>',3); - qb.where_array.should.eql(["`position` <> 3"]); + qb.reset_query(); + qb.where('position <>',3); + qb.where_array.should.eql(["`position` <> 3"]); - qb.reset_query(); - qb.where('position !=',3); - qb.where_array.should.eql(["`position` != 3"]); - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.where({star_system:'Solar',planet:['Earth','Mars']},false); - qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); - }); - it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { - qb.reset_query(); - qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); - qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); - }); - it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { - qb.reset_query(); - qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); - }); - it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { - qb.reset_query(); - qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); - }); + qb.reset_query(); + qb.where('position !=',3); + qb.where_array.should.eql(["`position` != 3"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where({star_system: 'Solar', planet: ['Earth', 'Mars']}, false); + qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); + }); + // it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { + // qb.reset_query(); + // qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); + // qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); + // }); + // it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { + // qb.reset_query(); + // qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + // qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + // }); + // it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { + // qb.reset_query(); + // qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + // qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); + // }); }); describe('or_where()', () => { - it('should exist', () => { - should.exist(qb.or_where); - }); - it('should be a function', () => { - qb.or_where.should.be.a('function'); - }); - it('should prepend tertiary WHERE clauses with "OR"', () => { - qb.reset_query(); - qb.or_where('planet','Mars'); - qb.or_where('planet','Venus'); - qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_where('planet','Mars').or_where('planet','Venus'); - qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); - }); - it('should be chainable with normal where', () => { - qb.reset_query(); - qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); - qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); - }); + // it('should exist', () => { + // should.exist(qb.or_where); + // }); + // it('should be a function', () => { + // qb.or_where.should.be.a('function'); + // }); + // it('should prepend tertiary WHERE clauses with "OR"', () => { + // qb.reset_query(); + // qb.or_where('planet','Mars'); + // qb.or_where('planet','Venus'); + // qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + // }); + // it('should be chainable', () => { + // qb.reset_query(); + // qb.or_where('planet','Mars').or_where('planet','Venus'); + // qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + // }); + // it('should be chainable with normal where', () => { + // qb.reset_query(); + // qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); + // qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); + // }); }); diff --git a/test/mysql/03-tests-count.js b/test/mysql/03-tests-count.js index 8731815..a50d4e1 100755 --- a/test/mysql/03-tests-count.js +++ b/test/mysql/03-tests-count.js @@ -51,6 +51,6 @@ describe('count()', () => { it('should include any joins that were added in the chain', () => { qb.reset_query(); const sql = qb.join('galaxies g','g.id=s.galaxy_id','left').count('star_systems s'); - sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id`=`s`.`galaxy_id`"); + sql.should.eql("SELECT COUNT(*) AS `numrows` FROM `star_systems` `s` LEFT JOIN `galaxies` `g` ON `g`.`id` = `s`.`galaxy_id`"); }); }); From b8a426a90f5f746d5a24e0623a9767ac611a001c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 18 Jul 2018 17:54:56 -0400 Subject: [PATCH 159/200] Added the rest of the tests for the mssql driver. Added a new set of unit tests for testing response objects. Some are still not working for mssql. Added a new method for mssql. Updated HISTORY and README. Update chai for testing. --- .travis.yml | 2 - HISTORY.md | 4 + Makefile | 4 + README.md | 64 ++++++-- drivers/mssql/adapters/single.js | 3 +- drivers/mssql/query_builder.js | 80 +++++++--- drivers/mssql/query_exec.js | 16 +- drivers/mysql/query_builder.js | 8 +- drivers/mysql/query_exec.js | 6 +- drivers/query_builder.js | 10 +- package.json | 2 +- test/create_mock_dbs.sh | 2 +- test/mssql/00-AA-tests-general.js | 10 +- test/mssql/00-tests-adapter.js | 46 +++--- test/mssql/01-tests-returning.js | 46 ++++++ test/mssql/01-tests-where.js | 74 ++++----- test/mssql/03-tests-insert.js | 145 ++++++++++++++++++ test/mssql/03-tests-insert_batch.js | 75 +++++---- test/mssql/03-tests-truncate.js | 25 +++ test/mssql/03-tests-update_batch.js | 21 +++ test/mssql/04-tests-query-response.js | 102 ++++++++++++ test/mssql/05-tests-multiple-queries.js | 33 ++++ test/mysql/01-tests-where.js | 74 ++++----- test/mysql/04-tests-query-response.js | 112 ++++++++++++++ ...ueries.js => 05-tests-multiple-queries.js} | 0 25 files changed, 776 insertions(+), 188 deletions(-) create mode 100755 test/mssql/01-tests-returning.js create mode 100755 test/mssql/03-tests-insert.js create mode 100755 test/mssql/03-tests-truncate.js create mode 100755 test/mssql/03-tests-update_batch.js create mode 100755 test/mssql/04-tests-query-response.js create mode 100755 test/mssql/05-tests-multiple-queries.js create mode 100755 test/mysql/04-tests-query-response.js rename test/mysql/{04-tests-multiple-queries.js => 05-tests-multiple-queries.js} (100%) diff --git a/.travis.yml b/.travis.yml index ee59b79..a8efdb3 100755 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ node_js: - "10.1.0" - "9.11.1" - "8.11.2" - - "7.10.1" - - "6.14.2" services: mysql test: adapter: mysql2 diff --git a/HISTORY.md b/HISTORY.md index 0d4f044..5fec97a 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,10 @@ you spot any mistakes. * Added new options: ** `pool_min` (minimum number of pooled connections (`mssql` driver only)) ** `acquireTimeout` (milliseconds before a timeout occurs during the connection acquisition) +* Added new query building method: `returning()` to allow for insert IDs to be returned. See docs for more info. +* Added new tests +* Fixed [#26](https://github.com/kylefarris/node-querybuilder/issues/26) + ## v1.2.0 (2018-05-18) diff --git a/Makefile b/Makefile index 57a0da2..f221be0 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ TESTS = test/mysql/*.js test/mssql/*.js +#TESTS = test/mssql/*.js +#TESTS = test/mssql/04-tests-query-response.js +#TESTS = test/mssql/05-tests-multiple-queries.js +#TESTS = test/mssql/03-tests-insert.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/README.md b/README.md index 9e81943..0b385ec 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -[![Build Status](https://travis-ci.org/kylefarris/node-querybuilder.svg?branch=master)](https://travis-ci.org/kylefarris/node-querybuilder) +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Node.js Version][node-image]][node-url] +[![Build Status][travis-image]][travis-url] # Universal QueryBuilder for Node.js Node-QueryBuilder is an ambitious attempt to create a kind of "universal translator" which provides programmers a consistent API to connect to and query _any_ database (traditional and NoSQL) supported by the module. The module is highly extensible and, in theory, can suppport any database provided that the driver has been written for it. @@ -30,10 +33,14 @@ The primary benefits of this module (currently) are: - [Standard Connection Settings](#standard-connection-settings) - [Choosing the Database Type](#choosing-the-database-type) - [Choosing the Connection Type](#choosing-the-connection-type) + - [Managing Connections](#managing-connections) - [API Methods](#api-methods) - [Chainable Methods](#chainable-methods) - [Execution Methods](#execution-methods) + - [What Are Execution Methods?](#what-are-execution-methods) + - [Handling Error Messages and Results](#handling-error-messages-and-results) + - [Response Format Examples](#response-format-examples) - [Other Library-Specific Methods](#other-library-specific-methods) - [Contribute](#contribute) @@ -185,7 +192,7 @@ This library currently supports 3 connection methods: const qb = new require('node-querybuilder')(settings, 'mysql', 'pool'); ``` -## Handling Connections +## Managing Connections It's important to handle your connections properly. When not using a pool, for every connection you make, you'll need to disconnect it when you're done. If you're using a pool (or cluster), it's a similar concept... but you'll be _releasing_ the connection back to the pool so it can be used again later. @@ -253,6 +260,7 @@ API Method | SQL Command | MySQL | MSSQL | Orac [limit()](#limit) | LIMIT | ✓ | ✓ | | | | [offset()](#offset) | OFFSET | ✓ | ✓ | | | | [set()](#set) | SET | ✓ | ✓ | | | | +[returning()](#returning) | OUTPUT | ✗ | ✓ | | | | -------------------------------------------------------------------------------- @@ -1046,7 +1054,7 @@ escape | String/Object | true | If false, keys and values will not be esc **Examples** -Basic single seting of a value +Basic single setting of a value ```javascript // UPDATE `users` SET `birthday` = '2015-02-04' @@ -1063,6 +1071,36 @@ qb.set({birthday: birthday, anniversary: '2010-05-15'}).update('users', callback -------------------------------------------------------------------------------- +### RETURNING / OUTPUT +#### .returning(id) +This method is required for MSSQL when performing INSERT queries to get the IDs of the row(s) that were inserted. You should supply which column(s) should be returned by the INSERT query as the `insert_id` in the response object. If you need multiple values (compound primary key, for instance) you can supply an array of strings representing those columns. If you call this method while using the `mysql` driver, it will be ignored silently. + +Parameter | Type | Default | Description +:-------- | :------------ | :------- | :----------------------------------------------------------------------------------------------------- +key | String/Array | Required | The ID or IDs used to identify the row that you're inserting + +**How This Works** + +Upon a successful `INSERT` query, you will be provided with a `result` object (see: [Response Format Examples](#response-format-examples)). I the `returning()` method is not called when using the MSSQL driver, the `insert_id` property of the result object will be `NULL`. This is not needed for the MySQL driver because its engine already supplies this info to the driver by default. + +**Examples** + +Basic single ID example + +```javascript +// INSERT INTO [users] ([first_name], [last_name]) OUTPUT INSERTED.[id] VALUES ('John', 'Smith') +qb.returning('id').insert('users', {first_name: 'John', last_name: 'Smith'}); +``` + +Return multiple column that should act as the `insert_id` + +```javascript +// INSERT INTO [users] ([position_request_id], [job_id], [name], [title]) OUTPUT INSERTED.[position_request_id], INSERTED.[job_id] VALUES (42, 1337, 'John Smith', 'Hacker') +qb.returning(['position_request_id', 'job_id']).insert('applicants', {position_request_id: 42, job_id: 1337, name: 'John Smith', title: 'Hacker'}); +``` + +-------------------------------------------------------------------------------- + ## Execution Methods API Method | SQL Command | MySQL | MSSQL | Oracle | SQLite | Postgres | Mongo @@ -1075,7 +1113,7 @@ API Method | SQL Command | MySQL | MSSQL | Oracle [update_batch()](#update_batch) | N/A | ✓ | ✓ | | | | [insert()](#insert) | INSERT | ✓ | ✓ | | | | [insert_batch()](#insert_batch) | N/A | ✓ | ✓ | | | | -[insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | | | | | +[insert_ignore()](#insert-ignore) | INSERT IGNORE | ✓ | ✗ | | | | [delete()](#delete) | DELETE | ✓ | ✓ | | | | [truncate()](#truncate) | TRUNCATE | ✓ | ✓ | | | | [empty_table()](#empty_table) | DELETE | ✓ | ✓ | | | | @@ -1764,14 +1802,14 @@ API Method | MySQL | MSSQL | Oracle | [connection](#connection_settings) | ✓ | ✓ | | | | [connection_settings()](#connection_settings) | ✓ | ✓ | | | | [disconnect()](#disconnect) | ✓ | ✓ | | | | -[escape()](#escape) | ✓ | | | | | +[escape()](#escape) | ✓ | ✓ | | | | [get_connection()](#get_connection) | ✓ | ✓ | | | | [last_query()](#last_query) | ✓ | | | | | [release()](#release) | ✓ | ✓ | | | | -[get_compiled_select()](#get_compiled_select) | ✓ | | | | | -[get_compiled_insert()](#get_compiled_insert) | ✓ | | | | | -[get_compiled_update()](#get_compiled_update) | ✓ | | | | | -[get_compiled_delete()](#get_compiled_delete) | ✓ | | | | | +[get_compiled_select()](#get_compiled_select) | ✓ | ✓ | | | | +[get_compiled_insert()](#get_compiled_insert) | ✓ | ✓ | | | | +[get_compiled_update()](#get_compiled_update) | ✓ | ✓ | | | | +[get_compiled_delete()](#get_compiled_delete) | ✓ | ✓ | | | | -------------------------------------------------------------------------------- @@ -2142,3 +2180,11 @@ console.log(sql); # Contribute Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request. + +[npm-version-image]: https://img.shields.io/npm/v/node-querybuilder.svg +[npm-downloads-image]: https://img.shields.io/npm/dm/node-querybuilder.svg +[npm-url]: https://npmjs.org/package/node-querybuilder +[travis-image]: https://img.shields.io/travis/mysqljs/node-querybuilder/master.svg +[travis-url]: https://travis-ci.org/mysqljs/node-querybuilder +[node-image]: https://img.shields.io/node/v/node-querybuilder.svg +[node-url]: https://nodejs.org/en/download diff --git a/drivers/mssql/adapters/single.js b/drivers/mssql/adapters/single.js index 9976ce4..0ac5ad5 100755 --- a/drivers/mssql/adapters/single.js +++ b/drivers/mssql/adapters/single.js @@ -1,6 +1,7 @@ // Load Tedious connection library const Connection = require('tedious').Connection; const Adapter = require('../adapter.js'); +const tsqlstring = require('tsqlstring'); class Single extends Adapter { constructor(settings, pool) { @@ -43,7 +44,7 @@ class Single extends Adapter { } escape_id(str) { - throw new Error("The `escape` method is not supported with the mssql driver!"); + return tsqlstring.escapeId(str); } disconnect(callback) { diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index b4fb0a9..61b6627 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -13,8 +13,6 @@ class QueryBuilder extends GenericQueryBuilder { // ---------------------------------------- SQL ESCAPE FUNCTIONS ------------------------ // _qb_escape(str) { - const mysql = require('mysql'); - if (typeof str === 'boolean') { str = (str === false ? 0 : 1); } else if (typeof str === 'number' || (typeof str === 'string' && /^\d+$/.test(str) && !/^0+/.test(str))) { @@ -57,6 +55,32 @@ class QueryBuilder extends GenericQueryBuilder { return offset_clause.trim(); } + returning(ids) { + ids = ids || null; + if (typeof ids !== 'string' && !Array.isArray(ids)) throw new Error("returning(): Only non-empty strings and arrays of strings are allowed as column names."); + if (Array.isArray(ids) && ids.length === 0) throw new Error("returning(): No columns specified in your array."); + if (typeof ids === 'string' && ids.length <= 0) throw new Error("returning(): An empty string is not a valid column name."); + + if (typeof ids === 'string') { + this.returning_ids.push(`INSERTED.${this._escape_identifiers(ids)}`); + } + + else if (Array.isArray(ids)) { + // Escape each ID + ids = ids.map(v => `INSERTED.${this._escape_identifiers(v)}`); + // Add new IDs without duplicating + this.returning_ids = Array.from(new Set(this.returning_ids.concat(ids))); + } + + return this; + } + + _build_returning_clause() { + if (this.returning_ids.length <= 0) return ''; + let sql = `OUTPUT ${this.returning_ids.join(', ')}`; + return sql.trim(); + } + // ---------------------------- SQL EXEC TOOLS ----------------------------// _compile_delete() { if (this.from_array.length === 0) { @@ -96,8 +120,6 @@ class QueryBuilder extends GenericQueryBuilder { values.push(val); } - const verb = 'INSERT '; - if (this.from_array.length === 1) { const table = this.from_array.toString(); } else { @@ -109,7 +131,12 @@ class QueryBuilder extends GenericQueryBuilder { return ''; } - return verb + `INTO ${this.from_array[0]} (${keys.join(', ')}) VALUES (${values.join(', ')})`; + const returning_clause = this._build_returning_clause(); + + let sql = `INSERT INTO ${this.from_array[0]} (${keys.join(', ')})`; + sql += (returning_clause ? ` ${returning_clause}` : ''); + sql += ` VALUES (${values.join(', ')})`; + return sql.trim(); } _compile_select() { @@ -186,26 +213,28 @@ class QueryBuilder extends GenericQueryBuilder { insert(table='', set='', ignore=false, suffix='') { if (ignore) throw new Error("insert(): INSERT IGNORE is currently unsupported on the MSSQL driver."); if (suffix) throw new Error("insert(): 'on_dupe' string (4th parameter) is currently unsupported on the MSSQL driver."); - super._insert(table, set, false, suffix); + return this._insert(table, set); + } + + insert_batch(table, set=null, ignore=false, suffix='') { + if (ignore) throw new Error("insert_batch(): INSERT IGNORE is currently unsupported on the MSSQL driver."); + if (suffix) throw new Error("insert_batch(): 'on_dupe' string (4th parameter) is currently unsupported on the MSSQL driver."); + return this._insert_batch(table, set); } - _insert_batch(table, set=null, ignore=false, suffix='') { + _insert_batch(table='', set=null) { const orig_table = table = table || ''; - ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); - if (suffix == ' ') suffix = ''; - if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); + if (typeof table !== 'string') throw new Error("insert_batch(): Table parameter must be a string!"); table = table.trim(); if (table !== '' && !/^[a-zA-Z0-9\$_]+(\.[a-zA-Z0-9\$_]+)?$/.test(table)) { - throw new Error("insert(): Invalid table name ('" + table + "') provided!"); + throw new Error(`insert_batch(): Invalid table name ('${table}') provided!`); } - if (table == '') { - if (this.from_array.length === 0) { - throw new Error("insert_batch(): You have not set any tables to insert into."); - } + if (!table) { + if (this.from_array.length === 0) throw new Error("insert_batch(): You have not set any tables to insert into."); + if (this.from_array.length > 1) throw new Error("insert_batch(): Batch inserting into multiple tables is not supported."); table = this.from_array[0]; } else { this._clear_array(this.from_array); @@ -216,6 +245,11 @@ class QueryBuilder extends GenericQueryBuilder { throw new Error('insert_batch(): Array of objects must be provided for batch insert!'); } + if (set.length === 0) { + return this.insert(orig_table, {}); + } + + for (let key in set) { const row = set[key]; const is_object = Object.prototype.toString.call(row) == Object.prototype.toString.call({}); @@ -226,19 +260,15 @@ class QueryBuilder extends GenericQueryBuilder { const v = row[i]; if (!/^(number|string|boolean)$/.test(typeof v) && v !== null) { - throw new Error("set(): Invalid value provided!"); + throw new Error("insert_batch(): Invalid value provided!"); } else if (typeof v === 'number' && (v === Infinity || v !== +v)) { - throw new Error("set(): Infinity and NaN are not valid values in MySQL!"); + throw new Error("insert_batch(): Infinity and NaN are not valid values in MS SQL!"); } } } } - if (set.length == 0) { - return this.insert(orig_table, {}, ignore, (suffix === '' ? null : suffix)); - } - const map = []; const columns = []; @@ -262,12 +292,12 @@ class QueryBuilder extends GenericQueryBuilder { if (row.length != columns.length) { throw new Error(`insert_batch(): Cannot use batch insert into ${table} - fields must match on all rows (${row.join(',')} vs ${columns.join(',')}).`); } - map.push('(' + row.join(', ') + ')'); + map.push(`(${row.join(', ')})`); })(i); } - const verb = 'INSERT ' + (ignore === true ? 'IGNORE ' : ''); - return verb + `INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + const sql = `INSERT INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}`; + return sql.trim(); } _count(table) { diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index e610b1b..d789472 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -10,26 +10,24 @@ class QueryExec extends QueryBuilder { } _exec(sql, cb) { - if (Object.prototype.toString.call(this._connection) == Object.prototype.toString.call({})) { + if (Object.prototype.toString.call(this._connection) === Object.prototype.toString.call({})) { //console.log("Connection: ", conn); const request = new Request(sql, (err, count, results) => { - //console.log("Results:" , results); + // console.log("Results:" , results); + // console.log("Count:" , count); // Standardize some important properties - if (!err && results.length > 0) { + if (!err && results && Array.isArray(results) && results.length === 0) { + results = {insert_id: null, affected_rows: 0, changed_rows: 0}; // Insert ID if (results.hasOwnProperty('insertId')) { results.insert_id = results.insertId; } - // Affected Rows - if (results.hasOwnProperty('rowsAffected')) { + // Affected & Changed Rows + if (count) { results.affected_rows = count; - } - - // Changed Rows - if (results.hasOwnProperty('changedRows')) { results.changed_rows = count; } } diff --git a/drivers/mysql/query_builder.js b/drivers/mysql/query_builder.js index c3b5891..d826c38 100755 --- a/drivers/mysql/query_builder.js +++ b/drivers/mysql/query_builder.js @@ -83,7 +83,7 @@ class QueryBuilder extends GenericQueryBuilder { return ''; } - const sql = `${verb} INTO ${table} (${keys.join(', ')}) VALUES (${values.join(', ')})${suffix}`; + const sql = `${verb} INTO ${table} (${keys.join(', ')}) VALUES (${values.join(', ')}) ${suffix.trim()}`; return sql.trim(); }; @@ -147,10 +147,10 @@ class QueryBuilder extends GenericQueryBuilder { return this._build_limit_clause(sql, limit_to, offset_val); }; - _insert_batch(table,set=null,ignore,suffix) { + _insert_batch(table, set=null, ignore=false, suffix='') { const orig_table = table = table || ''; ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + suffix = (typeof suffix !== 'string' ? '' : suffix); if (suffix == ' ') suffix = ''; if (typeof table !== 'string') throw new Error("insert(): Table parameter must be a string!"); @@ -225,7 +225,7 @@ class QueryBuilder extends GenericQueryBuilder { } const verb = 'INSERT' + (ignore === true ? ' IGNORE' : ''); - const sql = `${verb} INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')}${suffix}`; + const sql = `${verb} INTO ${this.from_array[0]} (${columns.join(', ')}) VALUES ${map.join(', ')} ${suffix.trim()}`; return sql.trim(); } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 855b740..3dea7d9 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -9,14 +9,14 @@ class QueryExec extends QueryBuilder { } _exec(sql, cb) { - if (Object.prototype.toString.call(this._connection) == Object.prototype.toString.call({})) { + if (Object.prototype.toString.call(this._connection) === Object.prototype.toString.call({})) { this._connection.query(sql, (err, results) => { // Standardize some important properties - if (!err && results.length > 0) { + if (!err && results && !Array.isArray(results)) { // Insert ID if (results.hasOwnProperty('insertId')) { - results.insert_id = results.insertId; + results.insert_id = results.insertId || null; } // Affected Rows diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 05ca8b4..0323ce6 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -17,6 +17,7 @@ class GenericQueryBuilder { this.last_query_string = []; // has to be array to work as reference this.distinct_clause = []; // has to be array to work as reference this.aliased_tables = []; + this.returning_ids = []; } // ------------------------------ GENERIC METHODS ------------------------------// @@ -388,6 +389,7 @@ class GenericQueryBuilder { this._clear_array(this.join_clause); this._clear_array(this.distinct_clause); this._clear_array(this.aliased_tables); + this._clear_array(this.returning_ids); this._clear_array(this.last_query_string); if (typeof new_last_query === 'string') { @@ -1021,6 +1023,11 @@ class GenericQueryBuilder { return this; } + returning(ids) { + // By default, this will do nothing. Specific drivers will override as needed. + return this; + } + set(key, value, escape=true) { escape = (typeof escape === 'boolean' ? escape : true); @@ -1106,12 +1113,11 @@ class GenericQueryBuilder { _insert(table='', set='', ignore=false, suffix='') { table = table || ''; // force falsy values to be an empty string ignore = (typeof ignore !== 'boolean' ? false : ignore); - suffix = (typeof suffix !== 'string' ? '' : ' ' + suffix); + suffix = (typeof suffix !== 'string' ? '' : suffix); if (/^(number|boolean)$/.test(typeof set) || (typeof set == 'string' && set !== '') || Object.prototype.toString.call(set) === Object.prototype.toString.call(/test/)) { throw new Error("insert(): Invalid data provided to insert into database!"); } - if (Array.isArray(set)) { return this.insert_batch(table, set, ignore, suffix); } diff --git a/package.json b/package.json index 269908f..c0fad74 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "tsqlstring": "^1.0.0" }, "devDependencies": { - "chai": "~1.4.2", + "chai": "^4.1.2", "mocha": "*" }, "main": "./", diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 758ef5e..30bb61f 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash ./mysql/create_mysql_mock.sh -#./mysql/create_mssql_mock.sh +#./mssql/create_mssql_mock.sh diff --git a/test/mssql/00-AA-tests-general.js b/test/mssql/00-AA-tests-general.js index 84de76b..ac6f502 100755 --- a/test/mssql/00-AA-tests-general.js +++ b/test/mssql/00-AA-tests-general.js @@ -1,9 +1,9 @@ const should = require('chai').should(); describe('QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mssql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mssql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); }); diff --git a/test/mssql/00-tests-adapter.js b/test/mssql/00-tests-adapter.js index 9e11ec2..37bd36e 100755 --- a/test/mssql/00-tests-adapter.js +++ b/test/mssql/00-tests-adapter.js @@ -54,12 +54,12 @@ describe('QueryBuilder() - MS SQL Adapter', () => { done(); }); - const bad_user = Object.assign({},settings, {user: 'foobar'}); - const bad_host = Object.assign({},settings, {host: 'nonlocalhost'}); - const bad_password = Object.assign({},settings, {password: 'password'}); - const bad_database = Object.assign({},settings, {database: 'bad_mock_db'}); - const bad_port = Object.assign({},settings, {port: 1}); - const bad_version = Object.assign({},settings, {version: 12}); + const bad_user = Object.assign({}, settings, {user: 'foobar'}); + const bad_host = Object.assign({}, settings, {host: 'nonlocalhost'}); + const bad_password = Object.assign({}, settings, {password: 'password'}); + const bad_database = Object.assign({}, settings, {database: 'bad_mock_db'}); + const bad_port = Object.assign({}, settings, {port: 1}); + const bad_version = Object.assign({}, settings, {version: 12}); it('should exist', () => { should.exist(QueryBuilder); @@ -72,7 +72,7 @@ describe('QueryBuilder() - MS SQL Adapter', () => { const children = [ 'reset_query','where','or_where','_where','where_in','or_where_in','where_not_in','or_where_not_in','_where_in','like', 'not_like','or_like','or_not_like','_like','from','join','select','select_min','select_max','select_avg','select_sum', - '_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set', + '_min_max_avg_sum','distinct','group_by','having','or_having','_having','order_by','limit','offset','set','returning' ]; children.forEach(v => { expect(qb).to.respondTo(v); @@ -80,7 +80,11 @@ describe('QueryBuilder() - MS SQL Adapter', () => { }); it('should have all the QueryExec methods', () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); - const children = ['insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete','get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select','compile_delete','compile_update','compile_insert']; + const children = [ + 'insert','insert_ignore','insert_batch','get','get_where','count','update','update_batch','delete', + 'get_compiled_select','get_compiled_delete','get_compiled_update','get_compiled_insert','compile_select', + 'compile_delete','compile_update','compile_insert' + ]; children.forEach(v => { expect(qb).to.respondTo(v); }); @@ -225,19 +229,19 @@ describe('QueryBuilder() - MS SQL Adapter', () => { }); }); }); - // it('should allow us to escape identifiers the MS SQL way', done => { - // const qb = new QueryBuilder(Object.assign({}, settings), driver); - // qb.connect(err => { - // check(done, () => { - // should.exist(qb.escape_id); - // qb.escape_id.should.be.a('function'); - // expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('`foo`'); - // expect(qb.escape_id('`foo`'), 'pre-escaped').to.be.eql('```foo```'); - // expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('`foo`.`bar`'); - // qb.disconnect(); - // }); - // }); - // }); + it('should allow us to escape identifiers the MS SQL way', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + check(done, () => { + should.exist(qb.escape_id); + qb.escape_id.should.be.a('function'); + expect(qb.escape_id('foo'), 'not pre-escaped').to.be.eql('[foo]'); + expect(qb.escape_id('[foo]'), 'pre-escaped').to.be.eql('[[foo]]]'); + expect(qb.escape_id('foo.bar'), 'with qualifier').to.be.eql('[foo].[bar]'); + qb.disconnect(); + }); + }); + }); it('should allow us to execute a query', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { diff --git a/test/mssql/01-tests-returning.js b/test/mssql/01-tests-returning.js new file mode 100755 index 0000000..cd1fa74 --- /dev/null +++ b/test/mssql/01-tests-returning.js @@ -0,0 +1,46 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('returning()', () => { + it('should exist', () => { + should.exist(qb.returning); + }); + it('should be a function', () => { + qb.returning.should.be.a('function'); + }); + it('should have an array to put ids into', () => { + qb.should.have.property('returning_ids'); + }); + it('should have an empty array to put ids into at the beginning', () => { + qb.returning_ids.should.be.empty; + }); + it('should not accept anything but a non-empty string or a non-empty array', () => { + qb.reset_query(); + expect(() => qb.returning(), 'nothing provided').to.throw(Error); + expect(() => qb.returning(null), 'null provided').to.throw(Error); + expect(() => qb.returning(false), 'false provided').to.throw(Error); + expect(() => qb.returning(true), 'true provided').to.throw(Error); + expect(() => qb.returning({}), 'empty object provided').to.throw(Error); + expect(() => qb.returning(3), 'integer provided').to.throw(Error); + expect(() => qb.returning(3.5), 'float provided').to.throw(Error); + expect(() => qb.returning([]), 'empty array provided').to.throw(Error); + expect(() => qb.returning([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.returning(''), 'empty string provided').to.throw(Error); + + expect(() => qb.returning('id'), 'valid string provided').to.not.throw(Error); + expect(() => qb.returning(['id', 'other_id']), 'valid array provided').to.not.throw(Error); + + }); + it('should accept a column name in the form of a string as the first parameter', () => { + qb.reset_query(); + qb.returning('planet'); + qb.returning_ids.should.eql(['INSERTED.[planet]']); + }); + it('should accept an array of column names in the form of strings', () => { + qb.reset_query(); + qb.returning(['planet', 'id']); + qb.returning_ids.should.eql(['INSERTED.[planet]', 'INSERTED.[id]']); + }); +}); diff --git a/test/mssql/01-tests-where.js b/test/mssql/01-tests-where.js index 4aef22a..33647c6 100755 --- a/test/mssql/01-tests-where.js +++ b/test/mssql/01-tests-where.js @@ -131,44 +131,44 @@ describe('where()', () => { qb.where({star_system: 'Solar', planet: ['Earth', 'Mars']}, false); qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); - // it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { - // qb.reset_query(); - // qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); - // qb.where_array.should.eql(['[planet_id] = 3', 'AND [galaxy_id] > 21645', "OR [planet] = 'Earth'"]); - // }); - // it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { - // qb.reset_query(); - // qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - // qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); - // }); - // it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { - // qb.reset_query(); - // qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - // qb.where_array.should.eql(["[galaxy_id] > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); - // }); + it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { + qb.reset_query(); + qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); + qb.where_array.should.eql(['[planet_id] = 3', 'AND [galaxy_id] > 21645', "OR [planet] = 'Earth'"]); + }); + it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { + qb.reset_query(); + qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + }); + it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { + qb.reset_query(); + qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.where_array.should.eql([`[galaxy_id] > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(''Milky Way'',''Andromeda''))'`]); + }); }); describe('or_where()', () => { - // it('should exist', () => { - // should.exist(qb.or_where); - // }); - // it('should be a function', () => { - // qb.or_where.should.be.a('function'); - // }); - // it('should prepend tertiary WHERE clauses with "OR"', () => { - // qb.reset_query(); - // qb.or_where('planet','Mars'); - // qb.or_where('planet','Venus'); - // qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); - // }); - // it('should be chainable', () => { - // qb.reset_query(); - // qb.or_where('planet','Mars').or_where('planet','Venus'); - // qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); - // }); - // it('should be chainable with normal where', () => { - // qb.reset_query(); - // qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); - // qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "OR [planet] = 'Venus'"]); - // }); + it('should exist', () => { + should.exist(qb.or_where); + }); + it('should be a function', () => { + qb.or_where.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where('planet','Mars'); + qb.or_where('planet','Venus'); + qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where('planet','Mars').or_where('planet','Venus'); + qb.where_array.should.eql(["[planet] = 'Mars'", "OR [planet] = 'Venus'"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); + qb.where_array.should.eql(["[planet] = 'Mars'", "AND [galaxy] = 'Milky Way'", "OR [planet] = 'Venus'"]); + }); }); diff --git a/test/mssql/03-tests-insert.js b/test/mssql/03-tests-insert.js new file mode 100755 index 0000000..5781c58 --- /dev/null +++ b/test/mssql/03-tests-insert.js @@ -0,0 +1,145 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +const test_data = {id:3, name:'Milky Way', type: 'spiral'}; +const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +// table, data, callback, ignore, suffix + +describe('insert()', () => { + it('should exist', () => { + should.exist(qb.insert); + }); + it('should be a function', () => { + qb.insert.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.insert('galaxies', test_data); + qb.from_array.should.eql(['[galaxies]']); + }); + it('should only accept nothing or a string for the table (first) parameter', () => { + qb.reset_query(); + + // Doing these to prevent other errors + qb.from('galaxies'); + + expect(() => qb.insert([], test_data), 'empty array provided').to.throw(Error); + expect(() => qb.insert({}, test_data), 'empty object provided').to.throw(Error); + expect(() => qb.insert(3, test_data), 'integer provided').to.throw(Error); + expect(() => qb.insert(3.5, test_data), 'float provided').to.throw(Error); + expect(() => qb.insert(true, test_data), 'true provided').to.throw(Error); + expect(() => qb.insert(Infinity, test_data), 'Infinity provided').to.throw(Error); + expect(() => qb.insert([1,2], test_data), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert(/foobar/, test_data), 'regex provided').to.throw(Error); + + expect(() => qb.insert(NaN, test_data), 'NaN provided').to.not.throw(Error); + expect(() => qb.insert(false, test_data), 'false provided').to.not.throw(Error); + expect(() => qb.insert('', test_data), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.insert(null, test_data), 'null provided').to.not.throw(Error); + expect(() => qb.insert(undefined, test_data),'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { + qb.reset_query(); + + expect(() => qb.insert('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',[]), 'empty array provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',[test_data,test_data]), 'array of non-empty standard objects provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',{}), 'empty object provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',null), 'null provided').to.not.throw(Error); + expect(() => qb.insert('galaxies',undefined), 'undefined provided').to.not.throw(Error); + expect(() => qb.insert('galaxies'), 'nothing provided').to.not.throw(Error); + + expect(() => qb.insert('galaxies',3), 'integer provided').to.throw(Error); + expect(() => qb.insert('galaxies',3.5), 'float provided').to.throw(Error); + expect(() => qb.insert('galaxies',true), 'true provided').to.throw(Error); + expect(() => qb.insert('galaxies',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert('galaxies',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert('galaxies',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert('galaxies',false), 'false provided').to.throw(Error); + expect(() => qb.insert('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', () => { + qb.reset_query(); + const sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', () => { + qb.reset_query(); + qb.from('galaxies'); + const sql = qb.insert(); + sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); + }); + it('should utilize pre-existing values set in in set_array', () => { + qb.reset_query(); + qb.set(test_data); + const sql = qb.insert('galaxies'); + sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { + qb.reset_query(); + qb.from('galaxies').set(test_data); + const sql = qb.insert(); + sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should accept a non-empty object for the data parameter', () => { + qb.reset_query(); + const sql = qb.insert('galaxies', test_data); + sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should convert call to insert_batch() if an array of non-emtpy objects is passed in the data parameter', () => { + qb.reset_query(); + const sql = qb.insert('galaxies', test_data_set); + const sql_b = qb.insert_batch('galaxies', test_data_set); + sql.should.eql(sql_b); + }); + it('should fail if any invalid values are passed in the data object.', () => { + qb.reset_query(); + const func = () => console.log("foo"); + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; + + expect(() => qb.insert('galaxies',{id: 1}), 'number in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: 'foo'}), 'string in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: false}), 'boolean in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: null}), 'null in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: undefined}), 'undefined in data').to.not.throw(Error); + expect(() => qb.insert('galaxies',{id: func}), 'function in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: regex}), 'regex in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: arr}), 'array in data').to.throw(Error); + expect(() => qb.insert('galaxies',{id: obj}), 'object in data').to.throw(Error); + }); + it('should include the OUTPUT directive when the return() method is called in the chain', () => { + qb.reset_query(); + const sql = qb.returning('id').insert('galaxies', test_data); + sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) OUTPUT INSERTED.[id] VALUES (3, 'Milky Way', 'spiral')"); + }); + it('should include the OUTPUT directive with multiple outputs when the return() method is called in the chain with an array', () => { + qb.reset_query(); + const sql = qb.returning(['id', 'name']).insert('galaxies', {name:'Milky Way', type: 'spiral'}); + sql.should.eql("INSERT INTO [galaxies] ([name], [type]) OUTPUT INSERTED.[id], INSERTED.[name] VALUES ('Milky Way', 'spiral')"); + }); +}); + +describe('insert_ignore()', () => { + it('should exist', () => { + should.exist(qb.insert_ignore); + }); + it('should be a function', () => { + qb.insert_ignore.should.be.a('function'); + }); + it('should not allow for insert ignore statements', () => { + qb.reset_query(); + expect(() => qb.insert_ignore('galaxies', test_data)).to.throw(Error); + }); +}); diff --git a/test/mssql/03-tests-insert_batch.js b/test/mssql/03-tests-insert_batch.js index dd46c82..36a4b29 100755 --- a/test/mssql/03-tests-insert_batch.js +++ b/test/mssql/03-tests-insert_batch.js @@ -48,37 +48,37 @@ describe('insert_batch()', () => { it('should only accept an array as the second parameter', () => { qb.reset_query(); - expect(() => qb.insert_batch('galaxies',test_data), 'array of objects provided').to.not.throw(Error); - expect(() => qb.insert_batch('galaxies',[]), 'empty array provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies', test_data), 'array of objects provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies', []), 'empty array provided').to.not.throw(Error); - expect(() => qb.insert_batch('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[test_data,test_data]), 'array of arrays provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',{}), 'empty object provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',''), 'empty string provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',null), 'null provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',undefined), 'undefined provided').to.throw(Error); expect(() => qb.insert_batch('galaxies'), 'nothing provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',3), 'integer provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',3.5), 'float provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',true), 'true provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',NaN), 'NaN provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',false), 'false provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [test_data,test_data]), 'array of arrays provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', {}), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', ''), 'empty string provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', null), 'null provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', undefined), 'undefined provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', 3), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', 3.5), 'float provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', true), 'true provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', ['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', /foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', false), 'false provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies', ' '), 'string full of spaces provided').to.throw(Error); }); it('should allow for an empty data parameter', () => { qb.reset_query(); - const sql = qb.insert_batch('galaxies',[]); + const sql = qb.insert_batch('galaxies', []); sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); }); it('should utilize pre-existing tables set in from_array', () => { qb.reset_query(); qb.from('galaxies'); - const sql = qb.insert_batch(null,[]); + const sql = qb.insert_batch(null, []); sql.should.eql("INSERT INTO [galaxies] () VALUES ()"); }); it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { @@ -88,17 +88,30 @@ describe('insert_batch()', () => { const arr = [1,2,3]; const obj = {foo: 'bar'}; - expect(() => qb.insert_batch('galaxies',[{id: func}]), 'function in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: regex}]), 'regex in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: Infinity}]), 'Infinity in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: undefined}]), 'undefined in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: NaN}]), 'NaN in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: arr}]), 'array in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: obj}]), 'object in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: func}]), 'function in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: regex}]), 'regex in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: Infinity}]), 'Infinity in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: undefined}]), 'undefined in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: NaN}]), 'NaN in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: arr}]), 'array in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies', [{id: obj}]), 'object in data').to.throw(Error); }); - it('should support insert ignore statements', () => { + it('should not support insert ignore statements', () => { qb.reset_query(); - const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO [galaxies] ([id], [name], [type]) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + expect(() => qb.insert_batch('galaxies', test_data, true)).to.throw(Error); }); + it('should not support insert statement suffixes', () => { + qb.reset_query(); + expect(() => qb.insert_batch('galaxies', test_data, false, 'ON DUPLICATE KEY UPDATE')).to.throw(Error); + }); + // it('should include the OUTPUT directive when the return() method is called in the chain', () => { + // qb.reset_query(); + // const sql = qb.returning('id').insert_batch('galaxies', test_data); + // sql.should.eql("INSERT INTO [galaxies] ([id], [name], [type]) OUTPUT INSERTED.[id] VALUES (3, 'Milky Way', 'spiral')"); + // }); + // it('should include the OUTPUT directive with multiple outputs when the return() method is called in the chain with an array', () => { + // qb.reset_query(); + // const sql = qb.returning(['id', 'name']).insert_batch('galaxies', test_data); + // sql.should.eql("INSERT INTO [galaxies] ([name], [type]) OUTPUT INSERTED.[id], INSERTED.[name] VALUES ('Milky Way', 'spiral')"); + // }); }); diff --git a/test/mssql/03-tests-truncate.js b/test/mssql/03-tests-truncate.js new file mode 100755 index 0000000..5b1592e --- /dev/null +++ b/test/mssql/03-tests-truncate.js @@ -0,0 +1,25 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('truncate()', () => { + it('should exist', () => { + should.exist(qb.truncate); + }); + it('should be a function', () => { + qb.truncate.should.be.a('function'); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.truncate('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper truncate statement', () => { + qb.reset_query(); + const sql = qb.truncate('galaxies'); + sql.should.eql('TRUNCATE [galaxies]'); + }); +}); diff --git a/test/mssql/03-tests-update_batch.js b/test/mssql/03-tests-update_batch.js new file mode 100755 index 0000000..50aaeb5 --- /dev/null +++ b/test/mssql/03-tests-update_batch.js @@ -0,0 +1,21 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +const test_where = {id:3}; +const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; + +describe('update_batch()', () => { + it('should exist', () => { + should.exist(qb.update_batch); + }); + it('should be a function', () => { + qb.update_batch.should.be.a('function'); + }); + it('should build a proper batch UPDATE string', () => { + qb.reset_query(); + const sql = qb.update_batch('galaxies', test_data, 'id'); + sql.should.eql(["UPDATE ([galaxies]) SET [name] = CASE WHEN [id] = 3 THEN 'Milky Way' WHEN [id] = 4 THEN 'Andromeda' ELSE [name] END, [type] = CASE WHEN [id] = 3 THEN 'spiral' WHEN [id] = 4 THEN 'spiral' ELSE [type] END WHERE [id] IN (3,4)"]); + }); +}); diff --git a/test/mssql/04-tests-query-response.js b/test/mssql/04-tests-query-response.js new file mode 100755 index 0000000..03b8b79 --- /dev/null +++ b/test/mssql/04-tests-query-response.js @@ -0,0 +1,102 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../index.js'); + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +describe('Query Responses', () => { + const driver = 'mssql'; + const settings = { + host: 'localhost', + database: 'mock_db', + user: 'travis', + password: 'Password123', + version: '4.1.0', + port: 1433, + options: { + encrypt: false + } + }; + + it('should allow us to execute a simple SELECT query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.like('city', 'Z', 'right').get_where('cities', {state_code: 'FL'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + }); + }); + }); + }); + it('should have a javascript Standard Error object when running an invalid query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.query("select * = 'FL'", (err, res) => { + check(done, () => { + expect(err, 'there should be an error when the query is invalid').to.be.instanceof(Error); + }); + }); + }); + }); + it('should respond with an object explaining the results of an INSERT query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.insert('cities', {city: 'Node QueryBuilder', state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + }); + }); + }); + }); + it('should respond with an object explaining the results of an UPDATE query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.update('cities', {city: 'Node Query Builder'}, {state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 1').to.gte(1); + }); + }); + }); + }); + it('should respond with an object explaining the results of a DELETE query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.delete('cities', {state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + }); + }); + }); + }); +}); diff --git a/test/mssql/05-tests-multiple-queries.js b/test/mssql/05-tests-multiple-queries.js new file mode 100755 index 0000000..ab0933b --- /dev/null +++ b/test/mssql/05-tests-multiple-queries.js @@ -0,0 +1,33 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = { + host: 'localhost', + database: 'mock_db', + user: 'travis', + password: 'Password123', + version: '4.1.0', + port: 1433, + options: { + encrypt: false + } +}; +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mssql', 'pool'); + +describe('Multiple Queries', () => { + it('should not get confused about table after deleting records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + //console.log("Error: ", err); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); diff --git a/test/mysql/01-tests-where.js b/test/mysql/01-tests-where.js index ae79e01..05d855a 100755 --- a/test/mysql/01-tests-where.js +++ b/test/mysql/01-tests-where.js @@ -131,44 +131,44 @@ describe('where()', () => { qb.where({star_system: 'Solar', planet: ['Earth', 'Mars']}, false); qb.where_array.should.eql(["star_system = 'Solar'", "AND planet IN ('Earth', 'Mars')"]); }); - // it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { - // qb.reset_query(); - // qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); - // qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); - // }); - // it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { - // qb.reset_query(); - // qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - // qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); - // }); - // it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { - // qb.reset_query(); - // qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); - // qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); - // }); + it("should split out and escape custom WHERE strings when that is the only thing provided (except when string contains parenthesis)", () => { + qb.reset_query(); + qb.where("planet_id = 3 AND galaxy_id > 21645 OR planet = 'Earth'"); + qb.where_array.should.eql(['`planet_id` = 3', 'AND `galaxy_id` > 21645', "OR `planet` = 'Earth'"]); + }); + it("should not try to escape where clauses utilizing functions or subqueries when provided as a string in the first and only parameter", () => { + qb.reset_query(); + qb.where("planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.where_array.should.eql(["planet_id = 3 AND galaxy_id > (SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"]); + }); + it("should escape (quote) functions and subqueries as strings when provided as second parameter", () => { + qb.reset_query(); + qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); + qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); + }); }); describe('or_where()', () => { - // it('should exist', () => { - // should.exist(qb.or_where); - // }); - // it('should be a function', () => { - // qb.or_where.should.be.a('function'); - // }); - // it('should prepend tertiary WHERE clauses with "OR"', () => { - // qb.reset_query(); - // qb.or_where('planet','Mars'); - // qb.or_where('planet','Venus'); - // qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); - // }); - // it('should be chainable', () => { - // qb.reset_query(); - // qb.or_where('planet','Mars').or_where('planet','Venus'); - // qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); - // }); - // it('should be chainable with normal where', () => { - // qb.reset_query(); - // qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); - // qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); - // }); + it('should exist', () => { + should.exist(qb.or_where); + }); + it('should be a function', () => { + qb.or_where.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where('planet','Mars'); + qb.or_where('planet','Venus'); + qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where('planet','Mars').or_where('planet','Venus'); + qb.where_array.should.eql(["`planet` = 'Mars'", "OR `planet` = 'Venus'"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where('planet','Venus'); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet` = 'Venus'"]); + }); }); diff --git a/test/mysql/04-tests-query-response.js b/test/mysql/04-tests-query-response.js new file mode 100755 index 0000000..d0a40d5 --- /dev/null +++ b/test/mysql/04-tests-query-response.js @@ -0,0 +1,112 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../index.js'); + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +describe('Query Responses', () => { + const driver = 'mysql'; + const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306 + }; + + it('should allow us to execute a simple SELECT query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'", (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + }); + }); + }); + }); + it('should have a non-empty array of objects as a response value when there should be results from a SELECT query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.like('city', 'Z', 'right').get_where('cities', {state_code: 'FL'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + }); + }); + }); + }); + it('should have a javascript Standard Error object when running an invalid query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.query("select * = 'FL'", (err, res) => { + check(done, () => { + expect(err, 'there should be an error when the query is invalid').to.be.instanceof(Error); + }); + }); + }); + }); + it('should respond with an object explaining the results of an INSERT query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.insert('cities', {city: 'Node QueryBuilder', state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + }); + }); + }); + }); + it('should respond with an object explaining the results of an UPDATE query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.update('cities', {city: 'Node Query Builder'}, {state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 1').to.gte(1); + }); + }); + }); + }); + it('should respond with an object explaining the results of a DELETE query', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.delete('cities', {state_code: 'NQ'}, (err, res) => { + check(done, () => { + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + }); + }); + }); + }); +}); diff --git a/test/mysql/04-tests-multiple-queries.js b/test/mysql/05-tests-multiple-queries.js similarity index 100% rename from test/mysql/04-tests-multiple-queries.js rename to test/mysql/05-tests-multiple-queries.js From 9290f4c47818bc6f1c31c9f1e8b0cf68238ba613 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 19 Jul 2018 18:02:51 -0400 Subject: [PATCH 160/200] Updated README. Updated minimum Node version in package file to >= v8.0. Fixed how MSSQL driver responds with data in callback. Only one test still failing. --- README.md | 16 +++++++++++++-- drivers/mssql/query_exec.js | 29 ++++++++++++++++----------- package.json | 2 +- test/mssql/04-tests-query-response.js | 20 ++++++++++++++++++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0b385ec..d483c71 100755 --- a/README.md +++ b/README.md @@ -1133,6 +1133,18 @@ count() | Integer (ex. `578`) insert(), update(), delete() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, changed_rows: 0 [,and others per DB driver]}` +**NOTE** + +When using the [returning()](#returning) method with compatible drivers, the `insert_id` property of the response object will be an array of objects containing key value pairs representing the requested "returned" columns along with their values. + +Example: + +```javascript +qb.returning('id').insert('users', {firstName: 'John', lastName: 'Smith'}, (err, results) => { + // Results: {insert_id: [{id: 12345}], affected_rows: 1, changed_rows: 0} +}); +``` + #### Callback Example ```javascript @@ -2184,7 +2196,7 @@ Got a missing feature you'd like to use? Found a bug? Go ahead and fork this rep [npm-version-image]: https://img.shields.io/npm/v/node-querybuilder.svg [npm-downloads-image]: https://img.shields.io/npm/dm/node-querybuilder.svg [npm-url]: https://npmjs.org/package/node-querybuilder -[travis-image]: https://img.shields.io/travis/mysqljs/node-querybuilder/master.svg -[travis-url]: https://travis-ci.org/mysqljs/node-querybuilder +[travis-image]: https://img.shields.io/travis/kylefarris/node-querybuilder/master.svg +[travis-url]: https://travis-ci.org/kylefarris/node-querybuilder [node-image]: https://img.shields.io/node/v/node-querybuilder.svg [node-url]: https://nodejs.org/en/download diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index d789472..ddc725c 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -17,18 +17,23 @@ class QueryExec extends QueryBuilder { // console.log("Count:" , count); // Standardize some important properties - if (!err && results && Array.isArray(results) && results.length === 0) { - results = {insert_id: null, affected_rows: 0, changed_rows: 0}; - - // Insert ID - if (results.hasOwnProperty('insertId')) { - results.insert_id = results.insertId; - } - - // Affected & Changed Rows - if (count) { - results.affected_rows = count; - results.changed_rows = count; + if (!err && results && Array.isArray(results)) { + if (results.length === 0) { + results = {insert_id: null, affected_rows: 0, changed_rows: 0}; + + // Affected & Changed Rows + if (count) { + results.affected_rows = count; + // Only set changed rows on an update + if (/^update\s/i.test(sql)) results.changed_rows = count; + } + } else { + const data = results.map(row => row.map(col => ({[col.metadata.colName]: col.value})).reduce((l,r) => Object.assign(l,r))) + if (/^insert\s/i.test(sql)) { + results = {insert_id: data, affected_rows: (count ? count : 0), changed_rows: 0}; + } else { + results = data; + } } } diff --git a/package.json b/package.json index c0fad74..01a3f90 100755 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ }, "main": "./", "engines": { - "node": "*" + "node": ">=8.0" } } diff --git a/test/mssql/04-tests-query-response.js b/test/mssql/04-tests-query-response.js index 03b8b79..df5f428 100755 --- a/test/mssql/04-tests-query-response.js +++ b/test/mssql/04-tests-query-response.js @@ -35,6 +35,9 @@ describe('Query Responses', () => { expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); expect(res, 'results should not be empty').to.not.be.empty; expect(res, 'should have 3 results').to.have.length(3); + + const expected_result = [{city: 'Zellwood', state_code: 'FL'},{city: 'Zephyrhills', state_code: 'FL'},{city: 'Zolfo Springs', state_code: 'FL'}]; + expect(res, 'should be just an array of objects representing the desired rows and columns').to.eql(expected_result); }); }); }); @@ -67,6 +70,23 @@ describe('Query Responses', () => { }); }); }); + it('should respond with the requested IDs (from the `returning()` method) after insert', done => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + qb.connect(err => { + expect(err).to.not.be.instanceof(Error); + + qb.returning(['city', 'state_code']).insert('cities', {city: 'Node QB Returns', state_code: 'NQ'}, (err, res) => { + check(done, () => { + console.log("Result: ", JSON.stringify(res)); + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be the values of the ids requested').to.not.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + }); + }); + }); + }); it('should respond with an object explaining the results of an UPDATE query', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { From f48556e14fe846350d89ce1aab611c762562278c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 23 Jul 2018 16:01:46 -0400 Subject: [PATCH 161/200] All tests are passing now. --- Makefile | 4 ++-- drivers/mssql/query_builder.js | 6 +++--- drivers/mssql/query_exec.js | 2 +- test/mssql/00-tests-adapter.js | 15 --------------- test/mssql/05-tests-multiple-queries.js | 24 +++++++++++++++++------- test/mysql/04-tests-query-response.js | 2 +- 6 files changed, 24 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index f221be0..3535624 100755 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -TESTS = test/mysql/*.js test/mssql/*.js +#TESTS = test/mysql/*.js test/mssql/*.js #TESTS = test/mssql/*.js #TESTS = test/mssql/04-tests-query-response.js -#TESTS = test/mssql/05-tests-multiple-queries.js +TESTS = test/mssql/05-tests-multiple-queries.js #TESTS = test/mssql/03-tests-insert.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/mssql/query_builder.js b/drivers/mssql/query_builder.js index 61b6627..cf4d62f 100755 --- a/drivers/mssql/query_builder.js +++ b/drivers/mssql/query_builder.js @@ -33,7 +33,7 @@ class QueryBuilder extends GenericQueryBuilder { // provided, skip building limit clause if (!limit_to || offset_val) return ''; - return `TOP ${limit_to}`; + return `TOP (${limit_to})`; } _build_offset_clause() { @@ -91,8 +91,8 @@ class QueryBuilder extends GenericQueryBuilder { // Make sure we're only deleting from one table this.from_array = this.from_array.slice(0, 1); - const limit_clause = this._build_limit_clause(); - const offset_clause = this._build_offset_clause(); + const limit_clause = this._build_limit_clause().trim(); + const offset_clause = this._build_offset_clause().trim(); const from_clause = this._build_from_clause().trim(); const where_clause = this._build_where_clause().trim(); diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index ddc725c..ace6fe5 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -11,7 +11,7 @@ class QueryExec extends QueryBuilder { _exec(sql, cb) { if (Object.prototype.toString.call(this._connection) === Object.prototype.toString.call({})) { - //console.log("Connection: ", conn); + console.log("The Query: ", sql); const request = new Request(sql, (err, count, results) => { // console.log("Results:" , results); // console.log("Count:" , count); diff --git a/test/mssql/00-tests-adapter.js b/test/mssql/00-tests-adapter.js index 37bd36e..ab07417 100755 --- a/test/mssql/00-tests-adapter.js +++ b/test/mssql/00-tests-adapter.js @@ -293,19 +293,4 @@ describe('QueryBuilder() - MS SQL Adapter', () => { check(done, () => connection_released(qb)); }); }); - // it('should allow one use the same connection pool connection for multiple queries', done => { - // const pool = new QueryBuilder(Object.assign({}, settings), driver, 'pool'); - // - // pool.get_connection(qb => { - // qb.query('select * from `cities` where `city` = "Gainesville"', (err, res) => { - // if (res.length > 0) { - // qb.query('select * from `cities` where `state_code` = "' + res[0].state_code + '"', (err, res) => { - // check(done, () => connection_released(qb)); - // }); - // } else { - // check(done, () => connection_released(qb)); - // } - // }); - // }); - // }); }); diff --git a/test/mssql/05-tests-multiple-queries.js b/test/mssql/05-tests-multiple-queries.js index ab0933b..b5ae760 100755 --- a/test/mssql/05-tests-multiple-queries.js +++ b/test/mssql/05-tests-multiple-queries.js @@ -11,21 +11,31 @@ const settings = { encrypt: false } }; + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + const QueryBuilder = require('../../index.js'); const pool = new QueryBuilder(settings, 'mssql', 'pool'); -describe('Multiple Queries', () => { +describe('MSSQL: Multiple Queries', () => { it('should not get confused about table after deleting records', done => { pool.get_connection(qb => { qb.limit(1).delete('cities', (err, result) => { qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { qb.release(); - //console.log("Error: ", err); - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - done(); + check(done, () => { + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affected_rows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + }); }); }); }); diff --git a/test/mysql/04-tests-query-response.js b/test/mysql/04-tests-query-response.js index d0a40d5..7fabade 100755 --- a/test/mysql/04-tests-query-response.js +++ b/test/mysql/04-tests-query-response.js @@ -11,7 +11,7 @@ const check = (done, f) => { } }; -describe('Query Responses', () => { +describe('MySQL: Query Responses', () => { const driver = 'mysql'; const settings = { host: '127.0.0.1', From f9082517e0648f1ecec9ba3473cb44923cc4c77f Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 25 Jul 2018 11:29:07 -0400 Subject: [PATCH 162/200] Added driver prefix to all tests. Normalized whitespace characters in tests. --- Makefile | 4 +- drivers/mssql/query_exec.js | 4 - test/05-multiple-drivers.js | 0 test/mssql/00-AA-tests-general.js | 2 +- test/mssql/00-tests-adapter.js | 2 +- test/mssql/01-tests-distinct.js | 2 +- test/mssql/01-tests-from.js | 2 +- test/mssql/01-tests-get.js | 6 +- test/mssql/01-tests-groupby.js | 2 +- test/mssql/01-tests-having.js | 4 +- test/mssql/01-tests-join.js | 2 +- test/mssql/01-tests-like.js | 8 +- test/mssql/01-tests-limit.js | 2 +- test/mssql/01-tests-offset.js | 2 +- test/mssql/01-tests-orderby.js | 2 +- test/mssql/01-tests-returning.js | 2 +- test/mssql/01-tests-select.js | 4 +- test/mssql/01-tests-set.js | 2 +- test/mssql/01-tests-where.js | 4 +- test/mssql/01-tests-where_in.js | 8 +- test/mssql/02-tests-compilation_methods.js | 8 +- test/mssql/03-tests-count.js | 2 +- test/mssql/03-tests-delete.js | 4 +- test/mssql/03-tests-empty_table.js | 2 +- test/mssql/03-tests-insert.js | 4 +- test/mssql/03-tests-insert_batch.js | 2 +- test/mssql/03-tests-truncate.js | 2 +- test/mssql/03-tests-update.js | 4 +- test/mssql/03-tests-update_batch.js | 2 +- test/mssql/04-tests-query-response.js | 2 +- test/mysql/00-AA-tests-general.js | 12 +- test/mysql/01-tests-distinct.js | 24 +- test/mysql/01-tests-from.js | 2 +- test/mysql/01-tests-get.js | 4 +- test/mysql/01-tests-groupby.js | 2 +- test/mysql/01-tests-having.js | 194 +++++------ test/mysql/01-tests-join.js | 2 +- test/mysql/01-tests-like.js | 342 +++++++++---------- test/mysql/01-tests-limit.js | 162 ++++----- test/mysql/01-tests-offset.js | 108 +++--- test/mysql/01-tests-orderby.js | 236 ++++++------- test/mysql/01-tests-select.js | 372 ++++++++++----------- test/mysql/01-tests-set.js | 2 +- test/mysql/01-tests-where.js | 4 +- test/mysql/01-tests-where_in.js | 326 +++++++++--------- test/mysql/02-tests-compilation_methods.js | 116 +++---- test/mysql/03-tests-count.js | 2 +- test/mysql/03-tests-delete.js | 164 ++++----- test/mysql/03-tests-empty_table.js | 86 ++--- test/mysql/03-tests-insert.js | 4 +- test/mysql/03-tests-insert_batch.js | 178 +++++----- test/mysql/03-tests-truncate.js | 38 +-- test/mysql/03-tests-update.js | 208 ++++++------ test/mysql/03-tests-update_batch.js | 24 +- test/mysql/05-tests-multiple-queries.js | 58 ++-- 55 files changed, 1378 insertions(+), 1388 deletions(-) create mode 100755 test/05-multiple-drivers.js diff --git a/Makefile b/Makefile index 3535624..f221be0 100755 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -#TESTS = test/mysql/*.js test/mssql/*.js +TESTS = test/mysql/*.js test/mssql/*.js #TESTS = test/mssql/*.js #TESTS = test/mssql/04-tests-query-response.js -TESTS = test/mssql/05-tests-multiple-queries.js +#TESTS = test/mssql/05-tests-multiple-queries.js #TESTS = test/mssql/03-tests-insert.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index ace6fe5..5d972e3 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -11,11 +11,7 @@ class QueryExec extends QueryBuilder { _exec(sql, cb) { if (Object.prototype.toString.call(this._connection) === Object.prototype.toString.call({})) { - console.log("The Query: ", sql); const request = new Request(sql, (err, count, results) => { - // console.log("Results:" , results); - // console.log("Count:" , count); - // Standardize some important properties if (!err && results && Array.isArray(results)) { if (results.length === 0) { diff --git a/test/05-multiple-drivers.js b/test/05-multiple-drivers.js new file mode 100755 index 0000000..e69de29 diff --git a/test/mssql/00-AA-tests-general.js b/test/mssql/00-AA-tests-general.js index ac6f502..ac5ad6d 100755 --- a/test/mssql/00-AA-tests-general.js +++ b/test/mssql/00-AA-tests-general.js @@ -1,6 +1,6 @@ const should = require('chai').should(); -describe('QueryBuilder', () => { +describe('MSSQL: QueryBuilder', () => { it('actually exists and can be initialized', () => { const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); diff --git a/test/mssql/00-tests-adapter.js b/test/mssql/00-tests-adapter.js index ab07417..e242004 100755 --- a/test/mssql/00-tests-adapter.js +++ b/test/mssql/00-tests-adapter.js @@ -34,7 +34,7 @@ const connection_released = qb => { }, 100); }; -describe('QueryBuilder() - MS SQL Adapter', () => { +describe('MSSQL: QueryBuilder() - Adapter', () => { const driver = 'mssql'; const settings = { host: 'localhost', diff --git a/test/mssql/01-tests-distinct.js b/test/mssql/01-tests-distinct.js index 8733cc5..60bc83b 100755 --- a/test/mssql/01-tests-distinct.js +++ b/test/mssql/01-tests-distinct.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('distinct()', () => { +describe('MSSQL: distinct()', () => { it('should exist', () => { should.exist(qb.distinct); }); diff --git a/test/mssql/01-tests-from.js b/test/mssql/01-tests-from.js index 1c7a200..e05120c 100755 --- a/test/mssql/01-tests-from.js +++ b/test/mssql/01-tests-from.js @@ -2,7 +2,7 @@ const should = require('chai').should(); const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('from()', () => { +describe('MSSQL: from()', () => { it('should exist', () => { should.exist(qb.from); }); diff --git a/test/mssql/01-tests-get.js b/test/mssql/01-tests-get.js index 7507ad7..850a8d6 100755 --- a/test/mssql/01-tests-get.js +++ b/test/mssql/01-tests-get.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('get()', () => { +describe('MSSQL: get()', () => { it('should exist', () => { should.exist(qb.get); }); @@ -90,7 +90,7 @@ describe('get()', () => { it('should accept a limit on the number of rows selected', () => { qb.reset_query(); const sql = qb.limit(20).get('galaxies'); - sql.should.eql("SELECT TOP 20 * FROM [galaxies]"); + sql.should.eql("SELECT TOP (20) * FROM [galaxies]"); }); it('should accept a LIMIT on the number of rows to select and an OFFSET at which to start selecting the rows', () => { qb.reset_query(); @@ -153,7 +153,7 @@ describe('get()', () => { }); }); -describe('get_where()', () => { +describe('MSSQL: get_where()', () => { it('should exist', () => { should.exist(qb.get_where); }); diff --git a/test/mssql/01-tests-groupby.js b/test/mssql/01-tests-groupby.js index b5553f6..0785ed3 100755 --- a/test/mssql/01-tests-groupby.js +++ b/test/mssql/01-tests-groupby.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('group_by()', () => { +describe('MSSQL: group_by()', () => { it('should exist', () => { should.exist(qb.group_by); }); diff --git a/test/mssql/01-tests-having.js b/test/mssql/01-tests-having.js index 93f13ef..f5d6464 100755 --- a/test/mssql/01-tests-having.js +++ b/test/mssql/01-tests-having.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('having()', () => { +describe('MSSQL: having()', () => { it('should exist', () => { should.exist(qb.having); }); @@ -92,7 +92,7 @@ describe('having()', () => { }); }); -describe('or_having()', () => { +describe('MSSQL: or_having()', () => { it('should exist', () => { should.exist(qb.or_having); }); diff --git a/test/mssql/01-tests-join.js b/test/mssql/01-tests-join.js index c73dcae..e537f2d 100755 --- a/test/mssql/01-tests-join.js +++ b/test/mssql/01-tests-join.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('join()', () => { +describe('MSSQL: join()', () => { it('should exist', () => { should.exist(qb.join); }); diff --git a/test/mssql/01-tests-like.js b/test/mssql/01-tests-like.js index e2f2846..b23de2d 100755 --- a/test/mssql/01-tests-like.js +++ b/test/mssql/01-tests-like.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('like()', () => { +describe('MSSQL: like()', () => { it('should exist', () => { should.exist(qb.like); }); @@ -103,7 +103,7 @@ describe('like()', () => { }); }); -describe('or_like()', () => { +describe('MSSQL: or_like()', () => { it('should exist', () => { should.exist(qb.or_like); }); @@ -128,7 +128,7 @@ describe('or_like()', () => { }); }); -describe('not_like()', () => { +describe('MSSQL: not_like()', () => { it('should exist', () => { should.exist(qb.not_like); }); @@ -158,7 +158,7 @@ describe('not_like()', () => { }); }); -describe('or_not_like()', () => { +describe('MSSQL: or_not_like()', () => { it('should exist', () => { should.exist(qb.or_not_like); }); diff --git a/test/mssql/01-tests-limit.js b/test/mssql/01-tests-limit.js index 3a5fe8c..ade72a7 100755 --- a/test/mssql/01-tests-limit.js +++ b/test/mssql/01-tests-limit.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('limit()', () => { +describe('MSSQL: limit()', () => { it('should exist', () => { should.exist(qb.limit); }); diff --git a/test/mssql/01-tests-offset.js b/test/mssql/01-tests-offset.js index 3a5d2b7..9a0c9d3 100755 --- a/test/mssql/01-tests-offset.js +++ b/test/mssql/01-tests-offset.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('offset()', () => { +describe('MSSQL: offset()', () => { it('should exist', () => { should.exist(qb.offset); }); diff --git a/test/mssql/01-tests-orderby.js b/test/mssql/01-tests-orderby.js index 21fc46f..6c2799d 100755 --- a/test/mssql/01-tests-orderby.js +++ b/test/mssql/01-tests-orderby.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('order_by()', () => { +describe('MSSQL: order_by()', () => { it('should exist', () => { should.exist(qb.order_by); }); diff --git a/test/mssql/01-tests-returning.js b/test/mssql/01-tests-returning.js index cd1fa74..ad69f41 100755 --- a/test/mssql/01-tests-returning.js +++ b/test/mssql/01-tests-returning.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('returning()', () => { +describe('MSSQL: returning()', () => { it('should exist', () => { should.exist(qb.returning); }); diff --git a/test/mssql/01-tests-select.js b/test/mssql/01-tests-select.js index ccd99ab..8c87621 100755 --- a/test/mssql/01-tests-select.js +++ b/test/mssql/01-tests-select.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('select()', () => { +describe('MSSQL: select()', () => { it('should exist', () => { should.exist(qb.select); }); @@ -191,7 +191,7 @@ describe('select()', () => { const prefixes = ['min','max','avg','sum']; for (const i in prefixes) { const type = prefixes[i]; - describe('select_' + type+'()', () => { + describe('MSSQL: select_' + type+'()', () => { it('should exist', () => { should.exist(qb['select_' + type]); }); diff --git a/test/mssql/01-tests-set.js b/test/mssql/01-tests-set.js index 3895973..5896110 100755 --- a/test/mssql/01-tests-set.js +++ b/test/mssql/01-tests-set.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('set()', () => { +describe('MSSQL: set()', () => { it('should exist', () => { should.exist(qb.set); }); diff --git a/test/mssql/01-tests-where.js b/test/mssql/01-tests-where.js index 33647c6..b232908 100755 --- a/test/mssql/01-tests-where.js +++ b/test/mssql/01-tests-where.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('where()', () => { +describe('MSSQL: where()', () => { it('should exist', () => { should.exist(qb.where); }); @@ -148,7 +148,7 @@ describe('where()', () => { }); }); -describe('or_where()', () => { +describe('MSSQL: or_where()', () => { it('should exist', () => { should.exist(qb.or_where); }); diff --git a/test/mssql/01-tests-where_in.js b/test/mssql/01-tests-where_in.js index d4a745a..39696cc 100755 --- a/test/mssql/01-tests-where_in.js +++ b/test/mssql/01-tests-where_in.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('where_in()', () => { +describe('MSSQL: where_in()', () => { it('should exist', () => { should.exist(qb.where_in); }); @@ -76,7 +76,7 @@ describe('where_in()', () => { }); }); -describe('where_not_in()', () => { +describe('MSSQL: where_not_in()', () => { it('should exist', () => { should.exist(qb.where_not_in); }); @@ -111,7 +111,7 @@ describe('where_not_in()', () => { }); }); -describe('or_where_in()', () => { +describe('MSSQL: or_where_in()', () => { it('should exist', () => { should.exist(qb.or_where_in); }); @@ -141,7 +141,7 @@ describe('or_where_in()', () => { }); }); -describe('or_where_not_in()', () => { +describe('MSSQL: or_where_not_in()', () => { it('should exist', () => { should.exist(qb.or_where_in); }); diff --git a/test/mssql/02-tests-compilation_methods.js b/test/mssql/02-tests-compilation_methods.js index 2a0ad4f..ff1e5c7 100755 --- a/test/mssql/02-tests-compilation_methods.js +++ b/test/mssql/02-tests-compilation_methods.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('get_compiled_select()', () => { +describe('MSSQL: get_compiled_select()', () => { it('should exist', () => { should.exist(qb.get_compiled_select); }); @@ -27,7 +27,7 @@ describe('get_compiled_select()', () => { }); }); -describe('get_compiled_insert()', () => { +describe('MSSQL: get_compiled_insert()', () => { it('should exist', () => { should.exist(qb.get_compiled_insert); }); @@ -41,7 +41,7 @@ describe('get_compiled_insert()', () => { }); }); -describe('get_compiled_update()', () => { +describe('MSSQL: get_compiled_update()', () => { it('should exist', () => { should.exist(qb.get_compiled_update); }); @@ -55,7 +55,7 @@ describe('get_compiled_update()', () => { }); }); -describe('get_compiled_delete()', () => { +describe('MSSQL: get_compiled_delete()', () => { it('should exist', () => { should.exist(qb.get_compiled_delete); }); diff --git a/test/mssql/03-tests-count.js b/test/mssql/03-tests-count.js index 69de821..f6c576a 100755 --- a/test/mssql/03-tests-count.js +++ b/test/mssql/03-tests-count.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('count()', () => { +describe('MSSQL: count()', () => { it('should exist', () => { should.exist(qb.count); }); diff --git a/test/mssql/03-tests-delete.js b/test/mssql/03-tests-delete.js index dbb4c76..bbd6976 100755 --- a/test/mssql/03-tests-delete.js +++ b/test/mssql/03-tests-delete.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('delete()', () => { +describe('MSSQL: delete()', () => { it('should exist', () => { should.exist(qb.delete); }); @@ -81,7 +81,7 @@ describe('delete()', () => { it('should accept a limit on the number of rows deleted', () => { qb.reset_query(); const sql = qb.limit(20).delete('galaxies'); - sql.should.eql("DELETE TOP 20 FROM [galaxies]"); + sql.should.eql("DELETE TOP (20) FROM [galaxies]"); }); it('should not a allow an offset delete', () => { qb.reset_query(); diff --git a/test/mssql/03-tests-empty_table.js b/test/mssql/03-tests-empty_table.js index a1d10b2..60697f9 100755 --- a/test/mssql/03-tests-empty_table.js +++ b/test/mssql/03-tests-empty_table.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('emtpy_table()', () => { +describe('MSSQL: emtpy_table()', () => { it('should exist', () => { should.exist(qb.empty_table); }); diff --git a/test/mssql/03-tests-insert.js b/test/mssql/03-tests-insert.js index 5781c58..5bc4c8b 100755 --- a/test/mssql/03-tests-insert.js +++ b/test/mssql/03-tests-insert.js @@ -8,7 +8,7 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('insert()', () => { +describe('MSSQL: insert()', () => { it('should exist', () => { should.exist(qb.insert); }); @@ -131,7 +131,7 @@ describe('insert()', () => { }); }); -describe('insert_ignore()', () => { +describe('MSSQL: insert_ignore()', () => { it('should exist', () => { should.exist(qb.insert_ignore); }); diff --git a/test/mssql/03-tests-insert_batch.js b/test/mssql/03-tests-insert_batch.js index 36a4b29..fe2b9b6 100755 --- a/test/mssql/03-tests-insert_batch.js +++ b/test/mssql/03-tests-insert_batch.js @@ -6,7 +6,7 @@ const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('insert_batch()', () => { +describe('MSSQL: insert_batch()', () => { it('should exist', () => { should.exist(qb.insert_batch); }); diff --git a/test/mssql/03-tests-truncate.js b/test/mssql/03-tests-truncate.js index 5b1592e..bd4a6d1 100755 --- a/test/mssql/03-tests-truncate.js +++ b/test/mssql/03-tests-truncate.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -describe('truncate()', () => { +describe('MSSQL: truncate()', () => { it('should exist', () => { should.exist(qb.truncate); }); diff --git a/test/mssql/03-tests-update.js b/test/mssql/03-tests-update.js index 01c4808..06446c7 100755 --- a/test/mssql/03-tests-update.js +++ b/test/mssql/03-tests-update.js @@ -9,7 +9,7 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('update()', () => { +describe('MSSQL: update()', () => { it('should exist', () => { should.exist(qb.update); }); @@ -121,6 +121,6 @@ describe('update()', () => { it('should allow for a LIMITed update', () => { qb.reset_query(); const sql = qb.limit(10).update('galaxies', test_data, test_where); - sql.should.eql("UPDATE [t] SET [name] = 'Milky Way', [type] = 'spiral' FROM (SELECT TOP 10 * FROM [galaxies] WHERE [id] = 3) [t]"); + sql.should.eql("UPDATE [t] SET [name] = 'Milky Way', [type] = 'spiral' FROM (SELECT TOP (10) * FROM [galaxies] WHERE [id] = 3) [t]"); }); }); diff --git a/test/mssql/03-tests-update_batch.js b/test/mssql/03-tests-update_batch.js index 50aaeb5..9e8f731 100755 --- a/test/mssql/03-tests-update_batch.js +++ b/test/mssql/03-tests-update_batch.js @@ -6,7 +6,7 @@ const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('update_batch()', () => { +describe('MSSQL: update_batch()', () => { it('should exist', () => { should.exist(qb.update_batch); }); diff --git a/test/mssql/04-tests-query-response.js b/test/mssql/04-tests-query-response.js index df5f428..60eccd0 100755 --- a/test/mssql/04-tests-query-response.js +++ b/test/mssql/04-tests-query-response.js @@ -11,7 +11,7 @@ const check = (done, f) => { } }; -describe('Query Responses', () => { +describe('MSSQL: Query Responses', () => { const driver = 'mssql'; const settings = { host: 'localhost', diff --git a/test/mysql/00-AA-tests-general.js b/test/mysql/00-AA-tests-general.js index 7c8ce90..b8add19 100755 --- a/test/mysql/00-AA-tests-general.js +++ b/test/mysql/00-AA-tests-general.js @@ -1,9 +1,9 @@ const should = require('chai').should(); -describe('QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mysql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); +describe('MySQL: QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mysql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); }); diff --git a/test/mysql/01-tests-distinct.js b/test/mysql/01-tests-distinct.js index 0cb4afd..5d88134 100755 --- a/test/mysql/01-tests-distinct.js +++ b/test/mysql/01-tests-distinct.js @@ -3,16 +3,16 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('distinct()', () => { - it('should exist', () => { - should.exist(qb.distinct); - }); - it('should be a function', () => { - qb.distinct.should.be.a('function'); - }); - it('should override the default distinct_clause with the "DISTINCT " keyword', () => { - qb.reset_query(); - qb.distinct(); - qb.distinct_clause.should.eql(['DISTINCT ']); - }); +describe('MySQL: distinct()', () => { + it('should exist', () => { + should.exist(qb.distinct); + }); + it('should be a function', () => { + qb.distinct.should.be.a('function'); + }); + it('should override the default distinct_clause with the "DISTINCT " keyword', () => { + qb.reset_query(); + qb.distinct(); + qb.distinct_clause.should.eql(['DISTINCT ']); + }); }); diff --git a/test/mysql/01-tests-from.js b/test/mysql/01-tests-from.js index 412e4b2..0ec0bcd 100755 --- a/test/mysql/01-tests-from.js +++ b/test/mysql/01-tests-from.js @@ -2,7 +2,7 @@ const should = require('chai').should(); const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('from()', () => { +describe('MySQL: from()', () => { it('should exist', () => { should.exist(qb.from); }); diff --git a/test/mysql/01-tests-get.js b/test/mysql/01-tests-get.js index a22a31d..6e9645e 100755 --- a/test/mysql/01-tests-get.js +++ b/test/mysql/01-tests-get.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('get()', () => { +describe('MySQL: get()', () => { it('should exist', () => { should.exist(qb.get); }); @@ -148,7 +148,7 @@ describe('get()', () => { }); }); -describe('get_where()', () => { +describe('MySQL: get_where()', () => { it('should exist', () => { should.exist(qb.get_where); }); diff --git a/test/mysql/01-tests-groupby.js b/test/mysql/01-tests-groupby.js index ca633d5..cb6c0ad 100755 --- a/test/mysql/01-tests-groupby.js +++ b/test/mysql/01-tests-groupby.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('group_by()', () => { +describe('MySQL: group_by()', () => { it('should exist', () => { should.exist(qb.group_by); }); diff --git a/test/mysql/01-tests-having.js b/test/mysql/01-tests-having.js index 1a4da12..0c8df2a 100755 --- a/test/mysql/01-tests-having.js +++ b/test/mysql/01-tests-having.js @@ -3,110 +3,110 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('having()', () => { - it('should exist', () => { - should.exist(qb.having); - }); - it('should be a function', () => { - qb.having.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('having_array'); - }); - it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', () => { - qb.reset_query(); - qb.having('planet_class > "M"'); - qb.having_array.should.eql(["`planet_class` > 'M'"]); +describe('MySQL: having()', () => { + it('should exist', () => { + should.exist(qb.having); + }); + it('should be a function', () => { + qb.having.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('having_array'); + }); + it('should accept a string only in this format: a [>|<|<>|>=|<=|=|!=] b for the first parameter', () => { + qb.reset_query(); + qb.having('planet_class > "M"'); + qb.having_array.should.eql(["`planet_class` > 'M'"]); - qb.reset_query(); - qb.having('planet_class < "M"'); - qb.having_array.should.eql(["`planet_class` < 'M'"]); + qb.reset_query(); + qb.having('planet_class < "M"'); + qb.having_array.should.eql(["`planet_class` < 'M'"]); - qb.reset_query(); - qb.having('planet_class <> "M"'); - qb.having_array.should.eql(["`planet_class` <> 'M'"]); + qb.reset_query(); + qb.having('planet_class <> "M"'); + qb.having_array.should.eql(["`planet_class` <> 'M'"]); - qb.reset_query(); - qb.having('planet_class >= "M"'); - qb.having_array.should.eql(["`planet_class` >= 'M'"]); + qb.reset_query(); + qb.having('planet_class >= "M"'); + qb.having_array.should.eql(["`planet_class` >= 'M'"]); - qb.reset_query(); - qb.having('planet_class <= "M"'); - qb.having_array.should.eql(["`planet_class` <= 'M'"]); + qb.reset_query(); + qb.having('planet_class <= "M"'); + qb.having_array.should.eql(["`planet_class` <= 'M'"]); - qb.reset_query(); - qb.having('planet_class = "M"'); - qb.having_array.should.eql(["`planet_class` = 'M'"]); + qb.reset_query(); + qb.having('planet_class = "M"'); + qb.having_array.should.eql(["`planet_class` = 'M'"]); - qb.reset_query(); - qb.having('planet_class != "M"'); - qb.having_array.should.eql(["`planet_class` != 'M'"]); - }); - it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', () => { - qb.reset_query(); - expect(() => qb.having('planet_class = "M", sentient_life = 1'), 'two conditions provided').to.throw(Error); - }); - it('should accept an array of conditions and prepend AND to each condition following the first one', () => { - qb.reset_query(); - qb.having(["planet_class = 'M'", 'sentient_life = 1']); - qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); - }); - it('should accept an object of conditions and prepend AND to each condition following the first one', () => { - qb.reset_query(); - const object = {planet_class: 'M', sentient_life: 1}; - object['planet_order <='] = 3; - qb.having(object); - qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); - }); - it('should not accept anything but a non-empty array, object, or string', () => { - qb.reset_query(); - expect(() => qb.group_by(), 'nothing provided').to.throw(Error); - expect(() => qb.group_by(null), 'null provided').to.throw(Error); - expect(() => qb.group_by(false), 'false provided').to.throw(Error); - expect(() => qb.group_by(true), 'true provided').to.throw(Error); - expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); - expect(() => qb.group_by(3), 'integer provided').to.throw(Error); - expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); - expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); - expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); + qb.reset_query(); + qb.having('planet_class != "M"'); + qb.having_array.should.eql(["`planet_class` != 'M'"]); + }); + it('should not accept compound conditions in this format: a [>|<|<>|>=|<=|=|!=] b[, repeat[, etc...]]', () => { + qb.reset_query(); + expect(() => qb.having('planet_class = "M", sentient_life = 1'), 'two conditions provided').to.throw(Error); + }); + it('should accept an array of conditions and prepend AND to each condition following the first one', () => { + qb.reset_query(); + qb.having(["planet_class = 'M'", 'sentient_life = 1']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1']); + }); + it('should accept an object of conditions and prepend AND to each condition following the first one', () => { + qb.reset_query(); + const object = {planet_class: 'M', sentient_life: 1}; + object['planet_order <='] = 3; + qb.having(object); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + }); + it('should not accept anything but a non-empty array, object, or string', () => { + qb.reset_query(); + expect(() => qb.group_by(), 'nothing provided').to.throw(Error); + expect(() => qb.group_by(null), 'null provided').to.throw(Error); + expect(() => qb.group_by(false), 'false provided').to.throw(Error); + expect(() => qb.group_by(true), 'true provided').to.throw(Error); + expect(() => qb.group_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.group_by(3), 'integer provided').to.throw(Error); + expect(() => qb.group_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.group_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.group_by([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.group_by(''), 'empty string provided').to.throw(Error); - // valid string - expect(() => qb.group_by('planet_type = "M"'), 'valid string provided').to.not.throw(Error); - expect(() => qb.group_by(['planet_type = "M"']), 'array of string(s) provided').to.not.throw(Error); - }); - it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', () => { - qb.reset_query(); - qb.having('planet_class','M'); - qb.having_array.should.eql(["`planet_class` = 'M'"]); - }); - it('should not escape conditions if asked not to', () => { - qb.reset_query(); - qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); - qb.having_array.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); - qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); - }); + // valid string + expect(() => qb.group_by('planet_type = "M"'), 'valid string provided').to.not.throw(Error); + expect(() => qb.group_by(['planet_type = "M"']), 'array of string(s) provided').to.not.throw(Error); + }); + it('should accept 2 parameters where the first one is the field with optional condition and the second one is the value', () => { + qb.reset_query(); + qb.having('planet_class','M'); + qb.having_array.should.eql(["`planet_class` = 'M'"]); + }); + it('should not escape conditions if asked not to', () => { + qb.reset_query(); + qb.having(["planet_class = 'M'", 'sentient_life = 1'], null, false); + qb.having_array.should.eql(["planet_class = 'M'", 'AND sentient_life = 1']); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.having('planet_class','M').having('sentient_life',true).having('planet_order <=',3); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','AND `planet_order` <= 3']); + }); }); -describe('or_having()', () => { - it('should exist', () => { - should.exist(qb.or_having); - }); - it('should be a function', () => { - qb.or_having.should.be.a('function'); - }); - it('should accept an array of conditions and prepend OR to each condition following the first one', () => { - qb.reset_query(); - qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); - qb.having_array.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); - }); - it('should be chainable with normal having', () => { - qb.reset_query(); - qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); - qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); - }); +describe('MySQL: or_having()', () => { + it('should exist', () => { + should.exist(qb.or_having); + }); + it('should be a function', () => { + qb.or_having.should.be.a('function'); + }); + it('should accept an array of conditions and prepend OR to each condition following the first one', () => { + qb.reset_query(); + qb.or_having(["planet_class = 'M'", 'sentient_life = 1']); + qb.having_array.should.eql(["`planet_class` = 'M'", 'OR `sentient_life` = 1']); + }); + it('should be chainable with normal having', () => { + qb.reset_query(); + qb.having('planet_class','M').having('sentient_life',true).or_having('planet_order <=',3); + qb.having_array.should.eql(["`planet_class` = 'M'", 'AND `sentient_life` = 1','OR `planet_order` <= 3']); + }); }); diff --git a/test/mysql/01-tests-join.js b/test/mysql/01-tests-join.js index d05fbe0..13d85d8 100755 --- a/test/mysql/01-tests-join.js +++ b/test/mysql/01-tests-join.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('join()', () => { +describe('MySQL: join()', () => { it('should exist', () => { should.exist(qb.join); }); diff --git a/test/mysql/01-tests-like.js b/test/mysql/01-tests-like.js index c0d698d..0cc339e 100755 --- a/test/mysql/01-tests-like.js +++ b/test/mysql/01-tests-like.js @@ -3,187 +3,187 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('like()', () => { - it('should exist', () => { - should.exist(qb.like); - }); - it('should be a function', () => { - qb.like.should.be.a('function'); - }); - it('should require first parameter to be a valid string or object with key value pairs', () => { - expect(() => qb.like(), 'nothing provided').to.throw(Error); - expect(() => qb.like(null), 'null provided').to.throw(Error); - expect(() => qb.like(false), 'false provided').to.throw(Error); - expect(() => qb.like(true), 'true provided').to.throw(Error); - expect(() => qb.like({}), 'empty object provided').to.throw(Error); - expect(() => qb.like(3), 'integer provided').to.throw(Error); - expect(() => qb.like(3.5), 'float provided').to.throw(Error); - expect(() => qb.like([]), 'empty array provided').to.throw(Error); - expect(() => qb.like(''), 'empty string provided').to.throw(Error); +describe('MySQL: like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); - expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', () => { - expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); - expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', () => { - expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); - expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); - expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); - expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); - expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { - expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); - }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'after'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'before'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'left'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'both'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - }); - it('should put AND in between multiple LIKE clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); }); -describe('or_like()', () => { - it('should exist', () => { - should.exist(qb.or_like); - }); - it('should be a function', () => { - qb.or_like.should.be.a('function'); - }); - it('should put OR in between multiple OR LIKE clauses', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky'); - qb.or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); - }); +describe('MySQL: or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + }); }); -describe('not_like()', () => { - it('should exist', () => { - should.exist(qb.not_like); - }); - it('should be a function', () => { - qb.not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put AND in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); +describe('MySQL: not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); }); -describe('or_not_like()', () => { - it('should exist', () => { - should.exist(qb.or_not_like); - }); - it('should be a function', () => { - qb.or_not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put OR in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); +describe('MySQL: or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); }); diff --git a/test/mysql/01-tests-limit.js b/test/mysql/01-tests-limit.js index 1616b35..719450b 100755 --- a/test/mysql/01-tests-limit.js +++ b/test/mysql/01-tests-limit.js @@ -3,85 +3,85 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('limit()', () => { - it('should exist', () => { - should.exist(qb.limit); - }); - it('should be a function', () => { - qb.limit.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('limit_to'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.limit_to.should.be.empty; - }); - it('should require an integer (or integer in string form) in first parameter', () => { - qb.reset_query(); - expect(() => qb.limit(5), 'integer provided').to.not.throw(Error); - expect(() => qb.limit('5'), '5 in string form provided').to.not.throw(Error); - expect(() => qb.limit(5.7), 'float provided').to.throw(Error); - expect(() => qb.limit('5.7'), 'float provided').to.throw(Error); - expect(() => qb.limit('abc'), 'alpha provided').to.throw(Error); - expect(() => qb.limit('abc7'), 'alpha numerics provided').to.throw(Error); - expect(() => qb.limit(), 'nothing provided').to.throw(Error); - expect(() => qb.limit(null), 'null provided').to.throw(Error); - expect(() => qb.limit(true), 'true provided').to.throw(Error); - expect(() => qb.limit(false), 'false provided').to.throw(Error); - expect(() => qb.limit(''), 'empty string provided').to.throw(Error); - expect(() => qb.limit({}), 'empty object provided').to.throw(Error); - expect(() => qb.limit([]), 'empty array provided').to.throw(Error); - expect(() => qb.limit([5]), 'array with integer in it provided').to.throw(Error); - }); - it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', () => { - qb.reset_query(); - expect(() => qb.limit(10,5), 'integer provided').to.not.throw(Error); - expect(() => qb.limit(10,'5'), '5 in string form provided').to.not.throw(Error); - expect(() => qb.limit(10,5.7), 'float provided').to.throw(Error); - expect(() => qb.limit(10,'5.7'), 'float provided').to.throw(Error); - expect(() => qb.limit(10,'abc'), 'alpha provided').to.throw(Error); - expect(() => qb.limit(10,'abc7'), 'alphanumerics provided').to.throw(Error); - expect(() => qb.limit(10,null), 'null provided').to.throw(Error); - expect(() => qb.limit(10,true), 'true provided').to.throw(Error); - expect(() => qb.limit(10,false), 'false provided').to.throw(Error); - expect(() => qb.limit(10,''), 'empty string provided').to.throw(Error); - expect(() => qb.limit(10,{}), 'empty object provided').to.throw(Error); - expect(() => qb.limit(10,[]), 'empty array provided').to.throw(Error); - expect(() => qb.limit(10,[5]), 'array with integer in it provided').to.throw(Error); - }); - it('should override the default limit_to value when a limit is provided', () => { - qb.reset_query(); - qb.limit(10); - qb.limit_to.should.eql([10]); - }); - it('should override the default limit_to and offset_val values when a limit and an offset are provided', () => { - qb.reset_query(); - qb.limit(10,20); - qb.limit_to.should.eql([10]); - qb.offset_val.should.eql([20]); - }); - it('should trim string values that are provided', () => { - qb.reset_query(); - qb.limit('10 '); - qb.limit_to.should.eql([10]); - }); - it('should trim string values that are provided', () => { - qb.reset_query(); - qb.limit(' 10 ',' 12'); - qb.limit_to.should.eql([10]); - qb.offset_val.should.eql([12]); - }); - it('should override values set by any previous calls to itself', () => { - qb.reset_query(); - qb.limit(10); - qb.limit_to.should.eql([10]); - qb.limit(20); - qb.limit_to.should.eql([20]); - }); - it('should be chainable whereby the last call to the method will contain the value(s) used', () => { - qb.reset_query(); - qb.limit(10,5).limit(20).limit(100,30); - qb.limit_to.should.eql([100]); - qb.offset_val.should.eql([30]); - }); +describe('MySQL: limit()', () => { + it('should exist', () => { + should.exist(qb.limit); + }); + it('should be a function', () => { + qb.limit.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('limit_to'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.limit_to.should.be.empty; + }); + it('should require an integer (or integer in string form) in first parameter', () => { + qb.reset_query(); + expect(() => qb.limit(5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(5.7), 'float provided').to.throw(Error); + expect(() => qb.limit('5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.limit(), 'nothing provided').to.throw(Error); + expect(() => qb.limit(null), 'null provided').to.throw(Error); + expect(() => qb.limit(true), 'true provided').to.throw(Error); + expect(() => qb.limit(false), 'false provided').to.throw(Error); + expect(() => qb.limit(''), 'empty string provided').to.throw(Error); + expect(() => qb.limit({}), 'empty object provided').to.throw(Error); + expect(() => qb.limit([]), 'empty array provided').to.throw(Error); + expect(() => qb.limit([5]), 'array with integer in it provided').to.throw(Error); + }); + it('should allow an integer (or integer in string form) in second parameter. Nothing else is allowed.', () => { + qb.reset_query(); + expect(() => qb.limit(10,5), 'integer provided').to.not.throw(Error); + expect(() => qb.limit(10,'5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.limit(10,5.7), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'5.7'), 'float provided').to.throw(Error); + expect(() => qb.limit(10,'abc'), 'alpha provided').to.throw(Error); + expect(() => qb.limit(10,'abc7'), 'alphanumerics provided').to.throw(Error); + expect(() => qb.limit(10,null), 'null provided').to.throw(Error); + expect(() => qb.limit(10,true), 'true provided').to.throw(Error); + expect(() => qb.limit(10,false), 'false provided').to.throw(Error); + expect(() => qb.limit(10,''), 'empty string provided').to.throw(Error); + expect(() => qb.limit(10,{}), 'empty object provided').to.throw(Error); + expect(() => qb.limit(10,[]), 'empty array provided').to.throw(Error); + expect(() => qb.limit(10,[5]), 'array with integer in it provided').to.throw(Error); + }); + it('should override the default limit_to value when a limit is provided', () => { + qb.reset_query(); + qb.limit(10); + qb.limit_to.should.eql([10]); + }); + it('should override the default limit_to and offset_val values when a limit and an offset are provided', () => { + qb.reset_query(); + qb.limit(10,20); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([20]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.limit('10 '); + qb.limit_to.should.eql([10]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.limit(' 10 ',' 12'); + qb.limit_to.should.eql([10]); + qb.offset_val.should.eql([12]); + }); + it('should override values set by any previous calls to itself', () => { + qb.reset_query(); + qb.limit(10); + qb.limit_to.should.eql([10]); + qb.limit(20); + qb.limit_to.should.eql([20]); + }); + it('should be chainable whereby the last call to the method will contain the value(s) used', () => { + qb.reset_query(); + qb.limit(10,5).limit(20).limit(100,30); + qb.limit_to.should.eql([100]); + qb.offset_val.should.eql([30]); + }); }); diff --git a/test/mysql/01-tests-offset.js b/test/mysql/01-tests-offset.js index 3857573..2e6bc7e 100755 --- a/test/mysql/01-tests-offset.js +++ b/test/mysql/01-tests-offset.js @@ -3,58 +3,58 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('offset()', () => { - it('should exist', () => { - should.exist(qb.offset); - }); - it('should be a function', () => { - qb.offset.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('offset_val'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.offset_val.should.be.empty; - }); - it('should require an integer (or integer in string form) in first parameter', () => { - qb.reset_query(); - expect(() => qb.offset(5), 'integer provided').to.not.throw(Error); - expect(() => qb.offset('5'), '5 in string form provided').to.not.throw(Error); - expect(() => qb.offset(5.7), 'float provided').to.throw(Error); - expect(() => qb.offset('5.7'), 'float provided').to.throw(Error); - expect(() => qb.offset('abc'), 'alpha provided').to.throw(Error); - expect(() => qb.offset('abc7'), 'alpha numerics provided').to.throw(Error); - expect(() => qb.offset(), 'nothing provided').to.throw(Error); - expect(() => qb.offset(null), 'null provided').to.throw(Error); - expect(() => qb.offset(true), 'true provided').to.throw(Error); - expect(() => qb.offset(false), 'false provided').to.throw(Error); - expect(() => qb.offset(''), 'empty string provided').to.throw(Error); - expect(() => qb.offset({}), 'empty object provided').to.throw(Error); - expect(() => qb.offset([]), 'empty array provided').to.throw(Error); - expect(() => qb.offset([5]), 'array with integer in it provided').to.throw(Error); - }); - it('should override the default offset_val value when a offset is provided', () => { - qb.reset_query(); - qb.offset(10); - qb.offset_val.should.eql([10]); - }); - it('should trim string values that are provided', () => { - qb.reset_query(); - qb.offset('10 '); - qb.offset(' 10'); - qb.offset(' 10 '); - qb.offset_val.should.eql([10]); - }); - it('should override values set by any previous calls to itself', () => { - qb.reset_query(); - qb.offset(10); - qb.offset_val.should.eql([10]); - qb.offset(20); - qb.offset_val.should.eql([20]); - }); - it('should be chainable whereby the last call to the method will contain the value used', () => { - qb.reset_query(); - qb.offset(10).offset(20).offset(100); - qb.offset_val.should.eql([100]); - }); +describe('MySQL: offset()', () => { + it('should exist', () => { + should.exist(qb.offset); + }); + it('should be a function', () => { + qb.offset.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('offset_val'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.offset_val.should.be.empty; + }); + it('should require an integer (or integer in string form) in first parameter', () => { + qb.reset_query(); + expect(() => qb.offset(5), 'integer provided').to.not.throw(Error); + expect(() => qb.offset('5'), '5 in string form provided').to.not.throw(Error); + expect(() => qb.offset(5.7), 'float provided').to.throw(Error); + expect(() => qb.offset('5.7'), 'float provided').to.throw(Error); + expect(() => qb.offset('abc'), 'alpha provided').to.throw(Error); + expect(() => qb.offset('abc7'), 'alpha numerics provided').to.throw(Error); + expect(() => qb.offset(), 'nothing provided').to.throw(Error); + expect(() => qb.offset(null), 'null provided').to.throw(Error); + expect(() => qb.offset(true), 'true provided').to.throw(Error); + expect(() => qb.offset(false), 'false provided').to.throw(Error); + expect(() => qb.offset(''), 'empty string provided').to.throw(Error); + expect(() => qb.offset({}), 'empty object provided').to.throw(Error); + expect(() => qb.offset([]), 'empty array provided').to.throw(Error); + expect(() => qb.offset([5]), 'array with integer in it provided').to.throw(Error); + }); + it('should override the default offset_val value when a offset is provided', () => { + qb.reset_query(); + qb.offset(10); + qb.offset_val.should.eql([10]); + }); + it('should trim string values that are provided', () => { + qb.reset_query(); + qb.offset('10 '); + qb.offset(' 10'); + qb.offset(' 10 '); + qb.offset_val.should.eql([10]); + }); + it('should override values set by any previous calls to itself', () => { + qb.reset_query(); + qb.offset(10); + qb.offset_val.should.eql([10]); + qb.offset(20); + qb.offset_val.should.eql([20]); + }); + it('should be chainable whereby the last call to the method will contain the value used', () => { + qb.reset_query(); + qb.offset(10).offset(20).offset(100); + qb.offset_val.should.eql([100]); + }); }); diff --git a/test/mysql/01-tests-orderby.js b/test/mysql/01-tests-orderby.js index 470a4c2..e8e6a9b 100755 --- a/test/mysql/01-tests-orderby.js +++ b/test/mysql/01-tests-orderby.js @@ -3,129 +3,129 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('order_by()', () => { - it('should exist', () => { - should.exist(qb.order_by); - }); - it('should be a function', () => { - qb.order_by.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('order_by_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.order_by_array.should.be.empty; - }); - it('should require non-empty string or array as first param unless random is provided as second parameter', () => { - expect(() => qb.order_by(), 'nothing provided').to.throw(Error); - expect(() => qb.order_by(null), 'null provided').to.throw(Error); - expect(() => qb.order_by(false), 'false provided').to.throw(Error); - expect(() => qb.order_by(true), 'true provided').to.throw(Error); - expect(() => qb.order_by({}), 'empty object provided').to.throw(Error); - expect(() => qb.order_by(3), 'integer provided').to.throw(Error); - expect(() => qb.order_by(3.5), 'float provided').to.throw(Error); - expect(() => qb.order_by([]), 'empty array provided').to.throw(Error); - expect(() => qb.order_by(''), 'empty string provided').to.throw(Error); - // If random - expect(() => qb.order_by('','rand'), 'empty string and random direction provided').to.not.throw(Error); - expect(() => qb.order_by(undefined,'rand'), 'undefined and random direction provided').to.not.throw(Error); - expect(() => qb.order_by(null,'rand'), 'null and random direction provided').to.not.throw(Error); - expect(() => qb.order_by(false,'rand'), 'false and random direction provided').to.not.throw(Error); - expect(() => qb.order_by([],'rand'), 'empty array and random direction provided').to.not.throw(Error); - }); - it('should accept a field and direction separated by a space as first parameter and escape the field', () => { - qb.reset_query(); - qb.order_by('planet_position asc'); - qb.order_by_array.should.eql(['`planet_position` ASC']); - }); - it('should accept a field and direction as seperate parameters and escape the field', () => { - qb.reset_query(); - qb.order_by('planet_position', 'asc'); - qb.order_by_array.should.eql(['`planet_position` ASC']); - }); - it('should add additional order_by calls to teh order by array', () => { - qb.reset_query(); - qb.order_by('planet_position', 'asc'); - qb.order_by('planet_size', 'desc'); - qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); - qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); - }); - it('should assume ASC when no direction is provided', () => { - qb.reset_query(); - qb.order_by('planet_position'); - qb.order_by_array.should.eql(['`planet_position` ASC']); - }); - it('should only accept valid ordering directions (ASC, DESC, random)', () => { - qb.reset_query(); - expect(() => qb.order_by('planet_position')).to.not.throw(Error); - expect(() => qb.order_by('planet_position','ASC')).to.not.throw(Error); - expect(() => qb.order_by('planet_position','DESC')).to.not.throw(Error); - expect(() => qb.order_by('planet_position','random')).to.not.throw(Error); - expect(() => qb.order_by('planet_position',null)).to.not.throw(Error); - expect(() => qb.order_by('planet_position',undefined)).to.not.throw(Error); - expect(() => qb.order_by('planet_position',false)).to.not.throw(Error); - expect(() => qb.order_by('planet_position',3)).to.not.throw(Error); - expect(() => qb.order_by('planet_position',true)).to.not.throw(Error); - expect(() => qb.order_by('planet_position',[])).to.not.throw(Error); - expect(() => qb.order_by('planet_position',{})).to.not.throw(Error); - expect(() => qb.order_by('planet_position','')).to.not.throw(Error); +describe('MySQL: order_by()', () => { + it('should exist', () => { + should.exist(qb.order_by); + }); + it('should be a function', () => { + qb.order_by.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('order_by_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.order_by_array.should.be.empty; + }); + it('should require non-empty string or array as first param unless random is provided as second parameter', () => { + expect(() => qb.order_by(), 'nothing provided').to.throw(Error); + expect(() => qb.order_by(null), 'null provided').to.throw(Error); + expect(() => qb.order_by(false), 'false provided').to.throw(Error); + expect(() => qb.order_by(true), 'true provided').to.throw(Error); + expect(() => qb.order_by({}), 'empty object provided').to.throw(Error); + expect(() => qb.order_by(3), 'integer provided').to.throw(Error); + expect(() => qb.order_by(3.5), 'float provided').to.throw(Error); + expect(() => qb.order_by([]), 'empty array provided').to.throw(Error); + expect(() => qb.order_by(''), 'empty string provided').to.throw(Error); + // If random + expect(() => qb.order_by('','rand'), 'empty string and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(undefined,'rand'), 'undefined and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(null,'rand'), 'null and random direction provided').to.not.throw(Error); + expect(() => qb.order_by(false,'rand'), 'false and random direction provided').to.not.throw(Error); + expect(() => qb.order_by([],'rand'), 'empty array and random direction provided').to.not.throw(Error); + }); + it('should accept a field and direction separated by a space as first parameter and escape the field', () => { + qb.reset_query(); + qb.order_by('planet_position asc'); + qb.order_by_array.should.eql(['`planet_position` ASC']); + }); + it('should accept a field and direction as seperate parameters and escape the field', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc'); + qb.order_by_array.should.eql(['`planet_position` ASC']); + }); + it('should add additional order_by calls to teh order by array', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc'); + qb.order_by('planet_size', 'desc'); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.order_by('planet_position', 'asc').order_by('planet_size', 'desc'); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` DESC']); + }); + it('should assume ASC when no direction is provided', () => { + qb.reset_query(); + qb.order_by('planet_position'); + qb.order_by_array.should.eql(['`planet_position` ASC']); + }); + it('should only accept valid ordering directions (ASC, DESC, random)', () => { + qb.reset_query(); + expect(() => qb.order_by('planet_position')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','ASC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','DESC')).to.not.throw(Error); + expect(() => qb.order_by('planet_position','random')).to.not.throw(Error); + expect(() => qb.order_by('planet_position',null)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',undefined)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',false)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',3)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',true)).to.not.throw(Error); + expect(() => qb.order_by('planet_position',[])).to.not.throw(Error); + expect(() => qb.order_by('planet_position',{})).to.not.throw(Error); + expect(() => qb.order_by('planet_position','')).to.not.throw(Error); - // Only an invalid string will throw an error - expect(() => qb.order_by('planet_position','FAKE')).to.throw(Error); - }); - it('should accept a comma-separated list of fields to order by with a single direction at the end', () => { - qb.reset_query(); - qb.order_by('planet_position, planet_size asc'); - qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); - }); - it('should accept a comma-separated list of field + direction pairs', () => { - qb.reset_query(); - qb.order_by('planet_position desc, planet_size asc'); - qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); - }); - it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', () => { - qb.reset_query(); - qb.order_by('planet_position', 'random'); - qb.order_by_array.should.eql(['RAND()']); + // Only an invalid string will throw an error + expect(() => qb.order_by('planet_position','FAKE')).to.throw(Error); + }); + it('should accept a comma-separated list of fields to order by with a single direction at the end', () => { + qb.reset_query(); + qb.order_by('planet_position, planet_size asc'); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + }); + it('should accept a comma-separated list of field + direction pairs', () => { + qb.reset_query(); + qb.order_by('planet_position desc, planet_size asc'); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + }); + it('should accept a random direction in three forms: "random", "RAND", "RAND()" (case-insensitively) and remove field(s) from statement', () => { + qb.reset_query(); + qb.order_by('planet_position', 'random'); + qb.order_by_array.should.eql(['RAND()']); - qb.reset_query(); - qb.order_by('planet_size', 'RAND'); - qb.order_by_array.should.eql(['RAND()']); + qb.reset_query(); + qb.order_by('planet_size', 'RAND'); + qb.order_by_array.should.eql(['RAND()']); - qb.reset_query(); - qb.order_by('planet_position, planet_size', 'rand'); - qb.order_by_array.should.eql(['RAND()']); + qb.reset_query(); + qb.order_by('planet_position, planet_size', 'rand'); + qb.order_by_array.should.eql(['RAND()']); - qb.reset_query(); - qb.order_by(null, 'RAND()'); - qb.order_by_array.should.eql(['RAND()']); + qb.reset_query(); + qb.order_by(null, 'RAND()'); + qb.order_by_array.should.eql(['RAND()']); - qb.reset_query(); - qb.order_by('', 'rand'); - qb.order_by_array.should.eql(['RAND()']); - }); - it('should accept an array of field + direction pairs', () => { - qb.reset_query(); - qb.order_by(['planet_position DESC', 'planet_size ASC']); - qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); - }); - it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', () => { - qb.reset_query(); - qb.order_by(['planet_position', 'planet_size'], 'desc'); - qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + qb.reset_query(); + qb.order_by('', 'rand'); + qb.order_by_array.should.eql(['RAND()']); + }); + it('should accept an array of field + direction pairs', () => { + qb.reset_query(); + qb.order_by(['planet_position DESC', 'planet_size ASC']); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` ASC']); + }); + it('should use direction parameter as default when an array of field + direction pairs is provided (when a pair does not contain a direction)', () => { + qb.reset_query(); + qb.order_by(['planet_position', 'planet_size'], 'desc'); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); - qb.reset_query(); - qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); - qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); - }); - it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', () => { - qb.reset_query(); - qb.order_by(['planet_position', 'planet_size']); - qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); + qb.reset_query(); + qb.order_by(['planet_position DESC', 'planet_size'], 'desc'); + qb.order_by_array.should.eql(['`planet_position` DESC', '`planet_size` DESC']); + }); + it('should accept a simple array of fields as first param and default to ASC for the direction if none is provided', () => { + qb.reset_query(); + qb.order_by(['planet_position', 'planet_size']); + qb.order_by_array.should.eql(['`planet_position` ASC', '`planet_size` ASC']); - }); + }); }); diff --git a/test/mysql/01-tests-select.js b/test/mysql/01-tests-select.js index 2fe8e96..5c1af94 100755 --- a/test/mysql/01-tests-select.js +++ b/test/mysql/01-tests-select.js @@ -3,205 +3,205 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('select()', () => { - it('should exist', () => { - should.exist(qb.select); - }); - it('should be a function', () => { - qb.select.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('select_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.select_array.should.be.empty; - }); - it('should require an array or string to be passed as first parameter', () => { - const invalid_match = /requires a string or array/; - const empty_str_match = /string is empty/; - const empty_arr_match = /array is empty/; - expect(() => qb.select(), 'nothing provided').to.throw(Error, invalid_match); - expect(() => qb.select(true), 'true provided').to.throw(Error, invalid_match); - expect(() => qb.select(null), 'null provided').to.throw(Error, invalid_match); - expect(() => qb.select(false), 'false provided').to.throw(Error, invalid_match); - expect(() => qb.select({}), 'object provided').to.throw(Error, invalid_match); - expect(() => qb.select([]), 'empty array provided').to.throw(Error, empty_arr_match); - expect(() => qb.select(''), 'empty string provided').to.throw(Error, empty_str_match); - expect(() => qb.select(' '), 'string of spaces provided').to.throw(Error, empty_str_match); - expect(() => qb.select('blah'), 'valid string provided').to.not.throw(Error); - }); - it('should add field to array and escape it properly', () => { - qb.reset_query(); - qb.select('notes'); - qb.select_array.should.eql(['`notes`']); - }); - it('should trim fields properly before placing them into the select array', () => { - qb.reset_query(); - qb.select(' notes '); - qb.select_array.should.eql(['`notes`']); - }); - it('should have an empty array after resetting', () => { - qb.reset_query(); - qb.select_array.should.be.empty; - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.select('foo',false); - qb.select_array.should.eql(['foo']); - }); - it('should accept a comma-delimited string of field names and trim and escape each properly', () => { - qb.reset_query(); - qb.select('do,re , mi, fa'); - qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); - }); - it('should be allowed to be called multiple times to add multiple fields to the select array', () => { - qb.reset_query(); - qb.select('do').select('re').select('mi').select('fa'); - qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); - }); - it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', () => { - qb.reset_query(); - qb.select('do').select('re',false).select('mi',false).select('fa'); - qb.select_array.should.eql(['`do`','re','mi','`fa`']); - }); - it('should accept an array of fields and add them individually to the select array', () => { - qb.reset_query(); - qb.select(['sol','la','ti','do']); - qb.select_array.should.eql(['`sol`','`la`','`ti`','`do`']); - }); - it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', () => { - qb.reset_query(); - qb.select(['sol','la','ti','do'],false); - qb.select_array.should.eql(['sol','la','ti','do']); - }); - it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', () => { - qb.reset_query(); - qb.select(['`sol`','la','ti','`do`'],false); - qb.select_array.should.eql(['`sol`','la','ti','`do`']); - }); - it('should not double-escape a field', () => { - qb.reset_query(); - qb.select('`do`'); - qb.select_array.should.eql(['`do`']); - }); - it('should not double-escape fields when provided with an array of pre-escaped fields', () => { - qb.reset_query(); - qb.select(['`do`','`re`','`mi`']); - qb.select_array.should.eql(['`do`','`re`','`mi`']); - }); - it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', () => { - qb.reset_query(); - qb.select(['`do`','re','`mi`']); - qb.select_array.should.eql(['`do`','`re`','`mi`']); - }); - it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', () => { - qb.reset_query(); - qb.select('foo as bar'); - qb.select_array.should.eql(['`foo` as `bar`']); - }); - it('should not double-escape aliases', () => { - qb.reset_query(); - qb.select(['foo as `bar`']); - qb.select_array.should.eql(['`foo` as `bar`']); - }); - it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', () => { - qb.reset_query(); - qb.select(['foo as bar','bar as foo']); - qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`']); - }); - it('should allow for field aliases with spaces in them', () => { - qb.reset_query(); - qb.select('notes as The Notes'); - qb.select_array.should.eql(['`notes` as `The Notes`']); - }); - it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', () => { - qb.reset_query(); - qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); - qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); - }); - it('should allow for namespacing in field name (host.db.table.field)', () => { - qb.reset_query(); - qb.select('star_system.planet'); - qb.select_array.should.eql(['`star_system`.`planet`']); +describe('MySQL: select()', () => { + it('should exist', () => { + should.exist(qb.select); + }); + it('should be a function', () => { + qb.select.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('select_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.select_array.should.be.empty; + }); + it('should require an array or string to be passed as first parameter', () => { + const invalid_match = /requires a string or array/; + const empty_str_match = /string is empty/; + const empty_arr_match = /array is empty/; + expect(() => qb.select(), 'nothing provided').to.throw(Error, invalid_match); + expect(() => qb.select(true), 'true provided').to.throw(Error, invalid_match); + expect(() => qb.select(null), 'null provided').to.throw(Error, invalid_match); + expect(() => qb.select(false), 'false provided').to.throw(Error, invalid_match); + expect(() => qb.select({}), 'object provided').to.throw(Error, invalid_match); + expect(() => qb.select([]), 'empty array provided').to.throw(Error, empty_arr_match); + expect(() => qb.select(''), 'empty string provided').to.throw(Error, empty_str_match); + expect(() => qb.select(' '), 'string of spaces provided').to.throw(Error, empty_str_match); + expect(() => qb.select('blah'), 'valid string provided').to.not.throw(Error); + }); + it('should add field to array and escape it properly', () => { + qb.reset_query(); + qb.select('notes'); + qb.select_array.should.eql(['`notes`']); + }); + it('should trim fields properly before placing them into the select array', () => { + qb.reset_query(); + qb.select(' notes '); + qb.select_array.should.eql(['`notes`']); + }); + it('should have an empty array after resetting', () => { + qb.reset_query(); + qb.select_array.should.be.empty; + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.select('foo',false); + qb.select_array.should.eql(['foo']); + }); + it('should accept a comma-delimited string of field names and trim and escape each properly', () => { + qb.reset_query(); + qb.select('do,re , mi, fa'); + qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); + }); + it('should be allowed to be called multiple times to add multiple fields to the select array', () => { + qb.reset_query(); + qb.select('do').select('re').select('mi').select('fa'); + qb.select_array.should.eql(['`do`','`re`','`mi`','`fa`']); + }); + it('should be allowed to be called multiple times to add multiple escaped and/or non-escaped fields to the select array', () => { + qb.reset_query(); + qb.select('do').select('re',false).select('mi',false).select('fa'); + qb.select_array.should.eql(['`do`','re','mi','`fa`']); + }); + it('should accept an array of fields and add them individually to the select array', () => { + qb.reset_query(); + qb.select(['sol','la','ti','do']); + qb.select_array.should.eql(['`sol`','`la`','`ti`','`do`']); + }); + it('should accept an array of fields and add them individually to the select array without escaping, if asked not to', () => { + qb.reset_query(); + qb.select(['sol','la','ti','do'],false); + qb.select_array.should.eql(['sol','la','ti','do']); + }); + it('should accept an array of fields (some manually escaped) and add them individually to the select array without auto-escaping, if asked not to', () => { + qb.reset_query(); + qb.select(['`sol`','la','ti','`do`'],false); + qb.select_array.should.eql(['`sol`','la','ti','`do`']); + }); + it('should not double-escape a field', () => { + qb.reset_query(); + qb.select('`do`'); + qb.select_array.should.eql(['`do`']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields', () => { + qb.reset_query(); + qb.select(['`do`','`re`','`mi`']); + qb.select_array.should.eql(['`do`','`re`','`mi`']); + }); + it('should not double-escape fields when provided with an array of pre-escaped fields but should escpae non-pre-escaped fields', () => { + qb.reset_query(); + qb.select(['`do`','re','`mi`']); + qb.select_array.should.eql(['`do`','`re`','`mi`']); + }); + it('should allow for field aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select('foo as bar'); + qb.select_array.should.eql(['`foo` as `bar`']); + }); + it('should not double-escape aliases', () => { + qb.reset_query(); + qb.select(['foo as `bar`']); + qb.select_array.should.eql(['`foo` as `bar`']); + }); + it('should allow for multiple fields with aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select(['foo as bar','bar as foo']); + qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`']); + }); + it('should allow for field aliases with spaces in them', () => { + qb.reset_query(); + qb.select('notes as The Notes'); + qb.select_array.should.eql(['`notes` as `The Notes`']); + }); + it('should allow for a comma-delimited list of fields with aliases to be provided and those fields and aliases should be properly escaped', () => { + qb.reset_query(); + qb.select('foo as bar, bar as foo, foobar as `Foo Bar`'); + qb.select_array.should.eql(['`foo` as `bar`','`bar` as `foo`','`foobar` as `Foo Bar`']); + }); + it('should allow for namespacing in field name (host.db.table.field)', () => { + qb.reset_query(); + qb.select('star_system.planet'); + qb.select_array.should.eql(['`star_system`.`planet`']); - qb.reset_query(); - qb.select('galaxy.star_system.planet'); - qb.select_array.should.eql(['`galaxy`.`star_system`.`planet`']); + qb.reset_query(); + qb.select('galaxy.star_system.planet'); + qb.select_array.should.eql(['`galaxy`.`star_system`.`planet`']); - qb.reset_query(); - qb.select('universe.galaxy.star_system.planet'); - qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); - }); - it('should allow for namespacing in field name (host.db.table.column) + alias', () => { - qb.reset_query(); - qb.select('universe.galaxy.star_system.planet as planet'); - qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); - }); - it('should not allow subqueries without the second parameter being false', () => { - qb.reset_query(); - expect( - () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') - ).to.throw(Error); + qb.reset_query(); + qb.select('universe.galaxy.star_system.planet'); + qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet`']); + }); + it('should allow for namespacing in field name (host.db.table.column) + alias', () => { + qb.reset_query(); + qb.select('universe.galaxy.star_system.planet as planet'); + qb.select_array.should.eql(['`universe`.`galaxy`.`star_system`.`planet` as `planet`']); + }); + it('should not allow subqueries without the second parameter being false', () => { + qb.reset_query(); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets') + ).to.throw(Error); - expect( - () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = 42) as num_planets') - ).to.throw(Error); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = 42) as num_planets') + ).to.throw(Error); - expect( - () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets', false) - ).to.not.throw(Error); - }); - it('should not allow functions without the second paramter being false', () => { - expect( - () => qb.select('s.star_systems, count(planets) as num_planets') - ).to.throw(Error); + expect( + () => qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id IN(2,3,5)) as num_planets', false) + ).to.not.throw(Error); + }); + it('should not allow functions without the second paramter being false', () => { + expect( + () => qb.select('s.star_systems, count(planets) as num_planets') + ).to.throw(Error); - expect( - () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets') - ).to.throw(Error); + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets') + ).to.throw(Error); - expect( - () => qb.select('s.star_systems, count(planets) as num_planets', false) - ).to.not.throw(Error); + expect( + () => qb.select('s.star_systems, count(planets) as num_planets', false) + ).to.not.throw(Error); - expect( - () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets', false) - ).to.not.throw(Error); - }); - it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', () => { - qb.reset_query(); - qb.select('count(*) as count', false); - qb.select_array.should.eql(['count(*) AS `count`']); + expect( + () => qb.select('s.star_systems, if(num_planets > 0, true, false) as has_planets', false) + ).to.not.throw(Error); + }); + it('should allow for functions and subqueries in statement without escaping them (aliases at the end will still be escaped)', () => { + qb.reset_query(); + qb.select('count(*) as count', false); + qb.select_array.should.eql(['count(*) AS `count`']); - qb.reset_query(); - qb.select('count(*) as count, m.*, MIN(id) as min', false); - qb.select_array.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); + qb.reset_query(); + qb.select('count(*) as count, m.*, MIN(id) as min', false); + qb.select_array.should.eql(['count(*) as count, m.*, MIN(id) AS `min`']); - qb.reset_query(); - qb.select('(select count(p.*) as count from planets p) as num_planets', false); - qb.select_array.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); + qb.reset_query(); + qb.select('(select count(p.*) as count from planets p) as num_planets', false); + qb.select_array.should.eql(['(select count(p.*) as count from planets p) AS `num_planets`']); - qb.reset_query(); - qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); - qb.select_array.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); + qb.reset_query(); + qb.select('s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) as num_planets', false); + qb.select_array.should.eql(['s.star_systems, (select count(p.*) as count from planets p where p.star_system_id = s.id) AS `num_planets`']); - }); + }); }); const prefixes = ['min','max','avg','sum']; for (const i in prefixes) { - const type = prefixes[i]; - describe('select_' + type+'()', () => { - it('should exist', () => { - should.exist(qb['select_' + type]); - }); - it('should be a function', () => { - qb['select_' + type].should.be.a('function'); - }); - it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', () => { - qb.reset_query(); - qb['select_' + type]('s.star_systems'); - qb.select_array.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); - }); - }); + const type = prefixes[i]; + describe('MySQL: select_' + type+'()', () => { + it('should exist', () => { + should.exist(qb['select_' + type]); + }); + it('should be a function', () => { + qb['select_' + type].should.be.a('function'); + }); + it('should place given field as parameter in a ' + type.toUpperCase() + '() MySQL function and alias the result with the original field name', () => { + qb.reset_query(); + qb['select_' + type]('s.star_systems'); + qb.select_array.should.eql([type.toUpperCase() + '(`s`.`star_systems`) AS star_systems']); + }); + }); } diff --git a/test/mysql/01-tests-set.js b/test/mysql/01-tests-set.js index eb70c76..2ec5f11 100755 --- a/test/mysql/01-tests-set.js +++ b/test/mysql/01-tests-set.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('set()', () => { +describe('MySQL: set()', () => { it('should exist', () => { should.exist(qb.set); }); diff --git a/test/mysql/01-tests-where.js b/test/mysql/01-tests-where.js index 05d855a..9a84dd4 100755 --- a/test/mysql/01-tests-where.js +++ b/test/mysql/01-tests-where.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('where()', () => { +describe('MySQL: where()', () => { it('should exist', () => { should.exist(qb.where); }); @@ -148,7 +148,7 @@ describe('where()', () => { }); }); -describe('or_where()', () => { +describe('MySQL: or_where()', () => { it('should exist', () => { should.exist(qb.or_where); }); diff --git a/test/mysql/01-tests-where_in.js b/test/mysql/01-tests-where_in.js index 1abe16f..4e70aa8 100755 --- a/test/mysql/01-tests-where_in.js +++ b/test/mysql/01-tests-where_in.js @@ -3,175 +3,175 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('where_in()', () => { - it('should exist', () => { - should.exist(qb.where_in); - }); - it('should be a function', () => { - qb.where_in.should.be.a('function'); - }); - it('should have an array to put fields into', () => { - qb.should.have.property('where_in_array'); - }); - it('should have an empty array to put fields into at the beginning', () => { - qb.where_in_array.should.be.empty; - }); - it('should not accept anything but a non-empty string as first parameter', () => { - qb.reset_query(); - expect(() => qb.where_in(), 'nothing provided').to.throw(Error); - expect(() => qb.where_in(null), 'null provided').to.throw(Error); - expect(() => qb.where_in(false), 'false provided').to.throw(Error); - expect(() => qb.where_in(true), 'true provided').to.throw(Error); - expect(() => qb.where_in({}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in({foo:'bar'}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in(3), 'integer provided').to.throw(Error); - expect(() => qb.where_in(3.5), 'float provided').to.throw(Error); - expect(() => qb.where_in(NaN), 'NaN provided').to.throw(Error); - expect(() => qb.where_in(Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.where_in([]), 'empty array provided').to.throw(Error); - expect(() => qb.where_in([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.where_in(''), 'empty string provided').to.throw(Error); - expect(() => qb.where_in(/foobar/), 'regex provided').to.throw(Error); +describe('MySQL: where_in()', () => { + it('should exist', () => { + should.exist(qb.where_in); + }); + it('should be a function', () => { + qb.where_in.should.be.a('function'); + }); + it('should have an array to put fields into', () => { + qb.should.have.property('where_in_array'); + }); + it('should have an empty array to put fields into at the beginning', () => { + qb.where_in_array.should.be.empty; + }); + it('should not accept anything but a non-empty string as first parameter', () => { + qb.reset_query(); + expect(() => qb.where_in(), 'nothing provided').to.throw(Error); + expect(() => qb.where_in(null), 'null provided').to.throw(Error); + expect(() => qb.where_in(false), 'false provided').to.throw(Error); + expect(() => qb.where_in(true), 'true provided').to.throw(Error); + expect(() => qb.where_in({}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in({foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in(3), 'integer provided').to.throw(Error); + expect(() => qb.where_in(3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in([]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.where_in(''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in(/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.where_in('planet_position',[1,2,3]), 'valid string provided').to.not.throw(Error); - }); - it('should not accept anything but a non-empty array of values as second parameter', () => { - qb.reset_query(); - expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); - expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); - expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); - expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); - expect(() => qb.where_in('planet',{}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in('planet',{foo:'bar'}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in('planet',3), 'integer provided').to.throw(Error); - expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); - expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); - expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); - expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); - expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.where_in('planet_position',[1,2,3]), 'valid string provided').to.not.throw(Error); + }); + it('should not accept anything but a non-empty array of values as second parameter', () => { + qb.reset_query(); + expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); + expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); + expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); + expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); + expect(() => qb.where_in('planet',{}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',{foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet',3), 'integer provided').to.throw(Error); + expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); + expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); - }); - it('should require both a field name an array of values as first and second parameters, respectively', () => { - qb.reset_query(); - qb.where_in('planet_position',[1,2,3]); - qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)']); - }); - it('should concatenate multiple WHERE IN clauses with AND ', () => { - qb.reset_query(); - qb.where_in('planet',['Mercury','Venus','Earth','Mars']); - qb.where_in('galaxy_id',[123,456,789,0110]); - qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); - qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.where_in('planet_position',[1,2,3],false); - qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); - }); + expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); + }); + it('should require both a field name an array of values as first and second parameters, respectively', () => { + qb.reset_query(); + qb.where_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)']); + }); + it('should concatenate multiple WHERE IN clauses with AND ', () => { + qb.reset_query(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']); + qb.where_in('galaxy_id',[123,456,789,0110]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); + qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); + }); }); -describe('where_not_in()', () => { - it('should exist', () => { - should.exist(qb.where_not_in); - }); - it('should be a function', () => { - qb.where_not_in.should.be.a('function'); - }); - it('should prepend "NOT " to "IN"', () => { - qb.reset_query(); - qb.where_not_in('planet_position',[1,2,3]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); - }); - it('should prepend tertiary WHERE clauses with "AND"', () => { - qb.reset_query(); - qb.where_not_in('planet_position',[1,2,3]); - qb.where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should be chainable with normal where', () => { - qb.reset_query(); - qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.where_not_in('planet_position',[1,2,3],false); - qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); - }); +describe('MySQL: where_not_in()', () => { + it('should exist', () => { + should.exist(qb.where_not_in); + }); + it('should be a function', () => { + qb.where_not_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "AND"', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]); + qb.where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3]).where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "AND `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.where_not_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); }); -describe('or_where_in()', () => { - it('should exist', () => { - should.exist(qb.or_where_in); - }); - it('should be a function', () => { - qb.or_where_in.should.be.a('function'); - }); - it('should prepend tertiary WHERE clauses with "OR"', () => { - qb.reset_query(); - qb.or_where_in('planet_position',[1,2,3]); - qb.or_where_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); - }); - it('should be chainable with normal where', () => { - qb.reset_query(); - qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); - qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.or_where_in('planet_position',[1,2,3],false); - qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); - }); +describe('MySQL: or_where_in()', () => { + it('should exist', () => { + should.exist(qb.or_where_in); + }); + it('should be a function', () => { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3]); + qb.or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3]).or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)', "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.or_where_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); + }); }); -describe('or_where_not_in()', () => { - it('should exist', () => { - should.exist(qb.or_where_in); - }); - it('should be a function', () => { - qb.or_where_in.should.be.a('function'); - }); - it('should prepend "NOT " to "IN"', () => { - qb.reset_query(); - qb.or_where_not_in('planet_position',[1,2,3]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); - }); - it('should prepend tertiary WHERE clauses with "OR"', () => { - qb.reset_query(); - qb.or_where_not_in('planet_position',[1,2,3]); - qb.or_where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should be chainable with normal where', () => { - qb.reset_query(); - qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); - qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); - }); - it('should not escape fields if asked not to', () => { - qb.reset_query(); - qb.or_where_not_in('planet_position',[1,2,3],false); - qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); - }); +describe('MySQL: or_where_not_in()', () => { + it('should exist', () => { + should.exist(qb.or_where_in); + }); + it('should be a function', () => { + qb.or_where_in.should.be.a('function'); + }); + it('should prepend "NOT " to "IN"', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)']); + }); + it('should prepend tertiary WHERE clauses with "OR"', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]); + qb.or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3]).or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(['`planet_position` NOT IN (1, 2, 3)', "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should be chainable with normal where', () => { + qb.reset_query(); + qb.where('planet','Mars').where('galaxy','Milky Way').or_where_not_in('planet_position',[5,6,7]); + qb.where_array.should.eql(["`planet` = 'Mars'", "AND `galaxy` = 'Milky Way'", "OR `planet_position` NOT IN (5, 6, 7)"]); + }); + it('should not escape fields if asked not to', () => { + qb.reset_query(); + qb.or_where_not_in('planet_position',[1,2,3],false); + qb.where_array.should.eql(['planet_position NOT IN (1, 2, 3)']); + }); }); diff --git a/test/mysql/02-tests-compilation_methods.js b/test/mysql/02-tests-compilation_methods.js index 7e02e3a..a0547fc 100755 --- a/test/mysql/02-tests-compilation_methods.js +++ b/test/mysql/02-tests-compilation_methods.js @@ -3,68 +3,68 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('get_compiled_select()', () => { - it('should exist', () => { - should.exist(qb.get_compiled_select); - }); - it('should be a function', () => { - qb.get_compiled_select.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - qb.get_compiled_select('galaxies'); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should add a set of tables to from_array when an array of tables is supplied', () => { - qb.reset_query(); - qb.get_compiled_select(['galaxies','star_systems','planets']); - qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); - }); - it('should return a SQL string', () => { - qb.reset_query(); - const sql = qb.get_compiled_select('galaxies'); - sql.should.eql('SELECT * FROM `galaxies`'); - }); +describe('MySQL: get_compiled_select()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_select); + }); + it('should be a function', () => { + qb.get_compiled_select.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.get_compiled_select('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should add a set of tables to from_array when an array of tables is supplied', () => { + qb.reset_query(); + qb.get_compiled_select(['galaxies','star_systems','planets']); + qb.from_array.should.eql(['`galaxies`','`star_systems`','`planets`']); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.get_compiled_select('galaxies'); + sql.should.eql('SELECT * FROM `galaxies`'); + }); }); -describe('get_compiled_insert()', () => { - it('should exist', () => { - should.exist(qb.get_compiled_insert); - }); - it('should be a function', () => { - qb.get_compiled_insert.should.be.a('function'); - }); - it('should return a SQL string', () => { - qb.reset_query(); - const sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); - sql.should.eql("INSERT INTO `galaxies` (`foo`) VALUES ('bar')"); - }); +describe('MySQL: get_compiled_insert()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_insert); + }); + it('should be a function', () => { + qb.get_compiled_insert.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.set({foo:'bar'}).get_compiled_insert('galaxies'); + sql.should.eql("INSERT INTO `galaxies` (`foo`) VALUES ('bar')"); + }); }); -describe('get_compiled_update()', () => { - it('should exist', () => { - should.exist(qb.get_compiled_update); - }); - it('should be a function', () => { - qb.get_compiled_update.should.be.a('function'); - }); - it('should return a SQL string', () => { - qb.reset_query(); - const sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); - sql.should.eql("UPDATE (`galaxies`) SET `foo` = 'bar' WHERE `id` = 45"); - }); +describe('MySQL: get_compiled_update()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_update); + }); + it('should be a function', () => { + qb.get_compiled_update.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.set({foo:'bar'}).where('id',45).get_compiled_update('galaxies'); + sql.should.eql("UPDATE (`galaxies`) SET `foo` = 'bar' WHERE `id` = 45"); + }); }); -describe('get_compiled_delete()', () => { - it('should exist', () => { - should.exist(qb.get_compiled_delete); - }); - it('should be a function', () => { - qb.get_compiled_delete.should.be.a('function'); - }); - it('should return a SQL string', () => { - qb.reset_query(); - const sql = qb.where('id',45).get_compiled_delete('galaxies'); - sql.should.eql("DELETE FROM `galaxies` WHERE `id` = 45"); - }); +describe('MySQL: get_compiled_delete()', () => { + it('should exist', () => { + should.exist(qb.get_compiled_delete); + }); + it('should be a function', () => { + qb.get_compiled_delete.should.be.a('function'); + }); + it('should return a SQL string', () => { + qb.reset_query(); + const sql = qb.where('id',45).get_compiled_delete('galaxies'); + sql.should.eql("DELETE FROM `galaxies` WHERE `id` = 45"); + }); }); diff --git a/test/mysql/03-tests-count.js b/test/mysql/03-tests-count.js index a50d4e1..1a8efe2 100755 --- a/test/mysql/03-tests-count.js +++ b/test/mysql/03-tests-count.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('count()', () => { +describe('MySQL: count()', () => { it('should exist', () => { should.exist(qb.count); }); diff --git a/test/mysql/03-tests-delete.js b/test/mysql/03-tests-delete.js index 4744e41..392fd8a 100755 --- a/test/mysql/03-tests-delete.js +++ b/test/mysql/03-tests-delete.js @@ -3,89 +3,89 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('delete()', () => { - it('should exist', () => { - should.exist(qb.delete); - }); - it('should be a function', () => { - qb.delete.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - qb.delete('galaxies'); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { - qb.reset_query(); +describe('MySQL: delete()', () => { + it('should exist', () => { + should.exist(qb.delete); + }); + it('should be a function', () => { + qb.delete.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.delete('galaxies'); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { + qb.reset_query(); - expect(() => qb.delete([]), 'empty array provided').to.throw(Error); - expect(() => qb.delete({}), 'empty object provided').to.throw(Error); - expect(() => qb.delete(3), 'integer provided').to.throw(Error); - expect(() => qb.delete(3.5), 'float provided').to.throw(Error); - expect(() => qb.delete(true), 'true provided').to.throw(Error); - expect(() => qb.delete(Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.delete([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.delete(/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.delete(NaN), 'NaN provided').to.throw(Error); - expect(() => qb.delete(false), 'false provided').to.throw(Error); - expect(() => qb.delete(''), 'empty string provided').to.throw(Error); - expect(() => qb.delete(' '), 'string full of spaces provided').to.throw(Error); - expect(() => qb.delete(null), 'null provided').to.throw(Error); + expect(() => qb.delete([]), 'empty array provided').to.throw(Error); + expect(() => qb.delete({}), 'empty object provided').to.throw(Error); + expect(() => qb.delete(3), 'integer provided').to.throw(Error); + expect(() => qb.delete(3.5), 'float provided').to.throw(Error); + expect(() => qb.delete(true), 'true provided').to.throw(Error); + expect(() => qb.delete(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.delete([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.delete(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.delete(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.delete(false), 'false provided').to.throw(Error); + expect(() => qb.delete(''), 'empty string provided').to.throw(Error); + expect(() => qb.delete(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.delete(null), 'null provided').to.throw(Error); - // An undefined/nothing option will only work if a table has already been provided - qb.from('galaxies'); expect(() => qb.delete(undefined),'undefined provided').to.not.throw(Error); - qb.from('galaxies'); expect(() => qb.delete(),'nothing provided').to.not.throw(Error); - }); - it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { - qb.reset_query(); - const sql = qb.from(['galaxies','star_systems','planets']).delete(); - sql.should.eql("DELETE FROM `galaxies`"); - }); - it('should add where conditions to where_array when conditions are supplied', () => { - qb.reset_query(); - qb.delete('planets', {continents: 7, star_system: 'Solar'}); - qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', () => { - qb.reset_query(); - const sql = qb.delete('galaxies'); - sql.should.eql('DELETE FROM `galaxies`'); - }); - it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', () => { - qb.reset_query(); - const sql = qb.delete('galaxies', {class: 'M'}); - sql.should.eql("DELETE FROM `galaxies` WHERE `class` = 'M'"); - }); - it('should use ONLY the FIRST table added previously via the from() method', () => { - qb.reset_query(); - qb.from('galaxies'); - let sql = qb.delete(); - sql.should.eql('DELETE FROM `galaxies`'); + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(() => qb.delete(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.delete(),'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { + qb.reset_query(); + const sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql("DELETE FROM `galaxies`"); + }); + it('should add where conditions to where_array when conditions are supplied', () => { + qb.reset_query(); + qb.delete('planets', {continents: 7, star_system: 'Solar'}); + qb.where_array.should.eql(["`continents` = 7", "AND `star_system` = 'Solar'"]); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.delete('galaxies', {continents: 7, star_system: 'Solar'}); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a properly-escaped delete statement that deletes all records in a table if only a table is given', () => { + qb.reset_query(); + const sql = qb.delete('galaxies'); + sql.should.eql('DELETE FROM `galaxies`'); + }); + it('should build a properly-escaped delete statement that deletes all records in a table that matched passed conditions', () => { + qb.reset_query(); + const sql = qb.delete('galaxies', {class: 'M'}); + sql.should.eql("DELETE FROM `galaxies` WHERE `class` = 'M'"); + }); + it('should use ONLY the FIRST table added previously via the from() method', () => { + qb.reset_query(); + qb.from('galaxies'); + let sql = qb.delete(); + sql.should.eql('DELETE FROM `galaxies`'); - qb.reset_query(); - sql = qb.from(['galaxies','star_systems','planets']).delete(); - sql.should.eql('DELETE FROM `galaxies`'); - }); - it('should accept where conditions added previously via the where() method', () => { - qb.reset_query(); - const sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); - sql.should.eql("DELETE FROM `galaxies` WHERE `created` >= 4600000000 AND `class` = 'M'"); - }); - it('should accept a limit on the number of rows deleted', () => { - qb.reset_query(); - const sql = qb.limit(20).delete('galaxies'); - sql.should.eql("DELETE FROM `galaxies` LIMIT 20"); - }); - it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', () => { - qb.reset_query(); - const sql = qb.limit(20,10).delete('galaxies'); - sql.should.eql("DELETE FROM `galaxies` LIMIT 10, 20"); - }); + qb.reset_query(); + sql = qb.from(['galaxies','star_systems','planets']).delete(); + sql.should.eql('DELETE FROM `galaxies`'); + }); + it('should accept where conditions added previously via the where() method', () => { + qb.reset_query(); + const sql = qb.where('created >=',4.6E9).where({class: 'M'}).delete('galaxies'); + sql.should.eql("DELETE FROM `galaxies` WHERE `created` >= 4600000000 AND `class` = 'M'"); + }); + it('should accept a limit on the number of rows deleted', () => { + qb.reset_query(); + const sql = qb.limit(20).delete('galaxies'); + sql.should.eql("DELETE FROM `galaxies` LIMIT 20"); + }); + it('should accept a LIMIT on the number of rows to delete and an OFFSET at which to start deleting the rows', () => { + qb.reset_query(); + const sql = qb.limit(20,10).delete('galaxies'); + sql.should.eql("DELETE FROM `galaxies` LIMIT 10, 20"); + }); }); diff --git a/test/mysql/03-tests-empty_table.js b/test/mysql/03-tests-empty_table.js index 80895fa..af383cf 100755 --- a/test/mysql/03-tests-empty_table.js +++ b/test/mysql/03-tests-empty_table.js @@ -3,49 +3,49 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('emtpy_table()', () => { - it('should exist', () => { - should.exist(qb.empty_table); - }); - it('should be a function', () => { - qb.empty_table.should.be.a('function'); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.empty_table('galaxies'); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should build a proper DELETE statement', () => { - qb.reset_query(); - const sql = qb.empty_table('galaxies'); - sql.should.eql('DELETE FROM `galaxies`'); - }); - it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { - qb.reset_query(); +describe('MySQL: empty_table()', () => { + it('should exist', () => { + should.exist(qb.empty_table); + }); + it('should be a function', () => { + qb.empty_table.should.be.a('function'); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.empty_table('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper DELETE statement', () => { + qb.reset_query(); + const sql = qb.empty_table('galaxies'); + sql.should.eql('DELETE FROM `galaxies`'); + }); + it('should only accept nothing or a non-empty-string for the table (first) parameter', () => { + qb.reset_query(); - expect(() => qb.empty_table([]), 'empty array provided').to.throw(Error); - expect(() => qb.empty_table({}), 'empty object provided').to.throw(Error); - expect(() => qb.empty_table(3), 'integer provided').to.throw(Error); - expect(() => qb.empty_table(3.5), 'float provided').to.throw(Error); - expect(() => qb.empty_table(true), 'true provided').to.throw(Error); - expect(() => qb.empty_table(Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.empty_table([1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.empty_table(/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.empty_table(NaN), 'NaN provided').to.throw(Error); - expect(() => qb.empty_table(false), 'false provided').to.throw(Error); - expect(() => qb.empty_table(''), 'empty string provided').to.throw(Error); - expect(() => qb.empty_table(' '), 'string full of spaces provided').to.throw(Error); - expect(() => qb.empty_table(null), 'null provided').to.throw(Error); + expect(() => qb.empty_table([]), 'empty array provided').to.throw(Error); + expect(() => qb.empty_table({}), 'empty object provided').to.throw(Error); + expect(() => qb.empty_table(3), 'integer provided').to.throw(Error); + expect(() => qb.empty_table(3.5), 'float provided').to.throw(Error); + expect(() => qb.empty_table(true), 'true provided').to.throw(Error); + expect(() => qb.empty_table(Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.empty_table([1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.empty_table(/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.empty_table(NaN), 'NaN provided').to.throw(Error); + expect(() => qb.empty_table(false), 'false provided').to.throw(Error); + expect(() => qb.empty_table(''), 'empty string provided').to.throw(Error); + expect(() => qb.empty_table(' '), 'string full of spaces provided').to.throw(Error); + expect(() => qb.empty_table(null), 'null provided').to.throw(Error); - // An undefined/nothing option will only work if a table has already been provided - qb.from('galaxies'); expect(() => qb.empty_table(undefined),'undefined provided').to.not.throw(Error); - qb.from('galaxies'); expect(() => qb.empty_table(),'nothing provided').to.not.throw(Error); - }); - it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { - qb.reset_query(); - const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); - sql.should.eql("DELETE FROM `galaxies`"); - }); + // An undefined/nothing option will only work if a table has already been provided + qb.from('galaxies'); expect(() => qb.empty_table(undefined),'undefined provided').to.not.throw(Error); + qb.from('galaxies'); expect(() => qb.empty_table(),'nothing provided').to.not.throw(Error); + }); + it('should only use the first table supplied in a list if an array of table is supplied with the from() method.', () => { + qb.reset_query(); + const sql = qb.from(['galaxies','star_systems','planets']).empty_table(); + sql.should.eql("DELETE FROM `galaxies`"); + }); }); diff --git a/test/mysql/03-tests-insert.js b/test/mysql/03-tests-insert.js index 7fb93eb..5731d54 100755 --- a/test/mysql/03-tests-insert.js +++ b/test/mysql/03-tests-insert.js @@ -8,7 +8,7 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('insert()', () => { +describe('MySQL: insert()', () => { it('should exist', () => { should.exist(qb.insert); }); @@ -122,7 +122,7 @@ describe('insert()', () => { }); }); -describe('insert_ignore()', () => { +describe('MySQL: insert_ignore()', () => { it('should exist', () => { should.exist(qb.insert_ignore); }); diff --git a/test/mysql/03-tests-insert_batch.js b/test/mysql/03-tests-insert_batch.js index 966ae59..c3a55a3 100755 --- a/test/mysql/03-tests-insert_batch.js +++ b/test/mysql/03-tests-insert_batch.js @@ -6,99 +6,99 @@ const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('insert_batch()', () => { - it('should exist', () => { - should.exist(qb.insert_batch); - }); - it('should be a function', () => { - qb.insert_batch.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - qb.insert_batch('galaxies', test_data); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should only accept nothing or a string for the table (first) parameter', () => { - qb.reset_query(); +describe('MySQL: insert_batch()', () => { + it('should exist', () => { + should.exist(qb.insert_batch); + }); + it('should be a function', () => { + qb.insert_batch.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.insert_batch('galaxies', test_data); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should only accept nothing or a string for the table (first) parameter', () => { + qb.reset_query(); - // Doing these to prevent other errors - qb.from('galaxies'); + // Doing these to prevent other errors + qb.from('galaxies'); - expect(() => qb.insert_batch([], test_data), 'empty array provided').to.throw(Error); - expect(() => qb.insert_batch({}, test_data), 'empty object provided').to.throw(Error); - expect(() => qb.insert_batch(3, test_data), 'integer provided').to.throw(Error); - expect(() => qb.insert_batch(3.5, test_data), 'float provided').to.throw(Error); - expect(() => qb.insert_batch(true, test_data), 'true provided').to.throw(Error); - expect(() => qb.insert_batch(Infinity, test_data), 'Infinity provided').to.throw(Error); - expect(() => qb.insert_batch([1,2], test_data), 'array of numbers provided').to.throw(Error); - expect(() => qb.insert_batch(/foobar/, test_data), 'regex provided').to.throw(Error); + expect(() => qb.insert_batch([], test_data), 'empty array provided').to.throw(Error); + expect(() => qb.insert_batch({}, test_data), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch(3, test_data), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch(3.5, test_data), 'float provided').to.throw(Error); + expect(() => qb.insert_batch(true, test_data), 'true provided').to.throw(Error); + expect(() => qb.insert_batch(Infinity, test_data), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch([1,2], test_data), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch(/foobar/, test_data), 'regex provided').to.throw(Error); - expect(() => qb.insert_batch(NaN, test_data), 'NaN provided').to.not.throw(Error); - expect(() => qb.insert_batch(false, test_data), 'false provided').to.not.throw(Error); - expect(() => qb.insert_batch('', test_data), 'empty string provided').to.not.throw(Error); - expect(() => qb.insert_batch(' ', test_data), 'string full of spaces provided').to.not.throw(Error); - expect(() => qb.insert_batch(null, test_data), 'null provided').to.not.throw(Error); - expect(() => qb.insert_batch(undefined, test_data), 'undefined provided').to.not.throw(Error); - }); - it('should build a proper batch INSERT string', () => { - qb.reset_query(); - const sql = qb.insert_batch('galaxies', test_data); - sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); - }); - it('should only accept an array as the second parameter', () => { - qb.reset_query(); + expect(() => qb.insert_batch(NaN, test_data), 'NaN provided').to.not.throw(Error); + expect(() => qb.insert_batch(false, test_data), 'false provided').to.not.throw(Error); + expect(() => qb.insert_batch('', test_data), 'empty string provided').to.not.throw(Error); + expect(() => qb.insert_batch(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + expect(() => qb.insert_batch(null, test_data), 'null provided').to.not.throw(Error); + expect(() => qb.insert_batch(undefined, test_data), 'undefined provided').to.not.throw(Error); + }); + it('should build a proper batch INSERT string', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies', test_data); + sql.should.eql("INSERT INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral')"); + }); + it('should only accept an array as the second parameter', () => { + qb.reset_query(); - expect(() => qb.insert_batch('galaxies',test_data), 'array of objects provided').to.not.throw(Error); - expect(() => qb.insert_batch('galaxies',[]), 'empty array provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies',test_data), 'array of objects provided').to.not.throw(Error); + expect(() => qb.insert_batch('galaxies',[]), 'empty array provided').to.not.throw(Error); - expect(() => qb.insert_batch('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[test_data,test_data]), 'array of arrays provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',{}), 'empty object provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',''), 'empty string provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',null), 'null provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',undefined), 'undefined provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies'), 'nothing provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',3), 'integer provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',3.5), 'float provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',true), 'true provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',NaN), 'NaN provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',false), 'false provided').to.throw(Error); - expect(() => qb.insert_batch('galaxies',' '), 'string full of spaces provided').to.throw(Error); - }); - it('should allow for an empty data parameter', () => { - qb.reset_query(); - const sql = qb.insert_batch('galaxies',[]); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); - }); - it('should utilize pre-existing tables set in from_array', () => { - qb.reset_query(); - qb.from('galaxies'); - const sql = qb.insert_batch(null,[]); - sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); - }); - it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { - qb.reset_query(); - const func = () => console.log("foo"); - const regex = /foobar/; - const arr = [1,2,3]; - const obj = {foo: 'bar'}; + expect(() => qb.insert_batch('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[test_data,test_data]), 'array of arrays provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',{}), 'empty object provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',''), 'empty string provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',null), 'null provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',undefined), 'undefined provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies'), 'nothing provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3), 'integer provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',3.5), 'float provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',true), 'true provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[Date, /foobar/, null]), 'array of non-standard objects provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',NaN), 'NaN provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',false), 'false provided').to.throw(Error); + expect(() => qb.insert_batch('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should allow for an empty data parameter', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies',[]); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should utilize pre-existing tables set in from_array', () => { + qb.reset_query(); + qb.from('galaxies'); + const sql = qb.insert_batch(null,[]); + sql.should.eql("INSERT INTO `galaxies` () VALUES ()"); + }); + it('should fail if any invalid values are passed into one of the data objects in the dataset', () => { + qb.reset_query(); + const func = () => console.log("foo"); + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; - expect(() => qb.insert_batch('galaxies',[{id: func}]), 'function in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: regex}]), 'regex in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: Infinity}]), 'Infinity in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: undefined}]), 'undefined in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: NaN}]), 'NaN in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: arr}]), 'array in data').to.throw(Error); - expect(() => qb.insert_batch('galaxies',[{id: obj}]), 'object in data').to.throw(Error); - }); - it('should support insert ignore statements', () => { - qb.reset_query(); - const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); - }); + expect(() => qb.insert_batch('galaxies',[{id: func}]), 'function in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: regex}]), 'regex in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: Infinity}]), 'Infinity in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: undefined}]), 'undefined in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: NaN}]), 'NaN in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: arr}]), 'array in data').to.throw(Error); + expect(() => qb.insert_batch('galaxies',[{id: obj}]), 'object in data').to.throw(Error); + }); + it('should support insert ignore statements', () => { + qb.reset_query(); + const sql = qb.insert_batch('galaxies', test_data, true, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); + sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral'), (4, 'Andromeda', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); + }); }); diff --git a/test/mysql/03-tests-truncate.js b/test/mysql/03-tests-truncate.js index 939abe5..a257bee 100755 --- a/test/mysql/03-tests-truncate.js +++ b/test/mysql/03-tests-truncate.js @@ -3,23 +3,23 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -describe('truncate()', () => { - it('should exist', () => { - should.exist(qb.truncate); - }); - it('should be a function', () => { - qb.truncate.should.be.a('function'); - }); - it('should return a string', () => { - qb.reset_query(); - const sql = qb.truncate('galaxies'); - expect(sql).to.be.a('string'); - expect(sql).to.exist; - expect(sql).to.not.eql(''); - }); - it('should build a proper truncate statement', () => { - qb.reset_query(); - const sql = qb.truncate('galaxies'); - sql.should.eql('TRUNCATE `galaxies`'); - }); +describe('MySQL: truncate()', () => { + it('should exist', () => { + should.exist(qb.truncate); + }); + it('should be a function', () => { + qb.truncate.should.be.a('function'); + }); + it('should return a string', () => { + qb.reset_query(); + const sql = qb.truncate('galaxies'); + expect(sql).to.be.a('string'); + expect(sql).to.exist; + expect(sql).to.not.eql(''); + }); + it('should build a proper truncate statement', () => { + qb.reset_query(); + const sql = qb.truncate('galaxies'); + sql.should.eql('TRUNCATE `galaxies`'); + }); }); diff --git a/test/mysql/03-tests-update.js b/test/mysql/03-tests-update.js index 7992a80..49c7864 100755 --- a/test/mysql/03-tests-update.js +++ b/test/mysql/03-tests-update.js @@ -9,119 +9,113 @@ const test_data_set = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'A // table, data, callback, ignore, suffix -describe('update()', () => { - it('should exist', () => { - should.exist(qb.update); - }); - it('should be a function', () => { - qb.update.should.be.a('function'); - }); - it('should add a table to from_array when a table is supplied', () => { - qb.reset_query(); - qb.update('galaxies', test_data, test_where); - qb.from_array.should.eql(['`galaxies`']); - }); - it('should accept a string or falsy value for the table (first) parameter', () => { - qb.reset_query(); +describe('MySQL: update()', () => { + it('should exist', () => { + should.exist(qb.update); + }); + it('should be a function', () => { + qb.update.should.be.a('function'); + }); + it('should add a table to from_array when a table is supplied', () => { + qb.reset_query(); + qb.update('galaxies', test_data, test_where); + qb.from_array.should.eql(['`galaxies`']); + }); + it('should accept a string or falsy value for the table (first) parameter', () => { + qb.reset_query(); - // One could choose to pass a falsy value to the first param because they have or will - // supply it with the from() method instead. + // One could choose to pass a falsy value to the first param because they have or will + // supply it with the from() method instead. - qb.reset_query(); expect(() => qb.from('galaxies').update([], test_data), 'empty array provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update({}, test_data), 'empty object provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(3, test_data), 'integer provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(3.5, test_data), 'float provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(true, test_data), 'true provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(Infinity, test_data), 'Infinity provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update([1,2], test_data), 'array of numbers provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(/foobar/, test_data), 'regex provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update([], test_data), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update({}, test_data), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3, test_data), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(3.5, test_data), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(true, test_data), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(Infinity, test_data), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update([1,2], test_data), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(/foobar/, test_data), 'regex provided').to.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(NaN, test_data), 'NaN provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(false, test_data), 'false provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update('', test_data), 'empty string provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(' ', test_data), 'string full of spaces provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(null, test_data), 'null provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.from('galaxies').update(undefined, test_data),'undefined provided').to.not.throw(Error); - }); - it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { - // One could choose to pass a falsy value to the second param because they have or will - // supply data with the set() method instead. + qb.reset_query(); expect(() => qb.from('galaxies').update(NaN, test_data), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(false, test_data), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update('', test_data), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(' ', test_data), 'string full of spaces provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(null, test_data), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.from('galaxies').update(undefined, test_data),'undefined provided').to.not.throw(Error); + }); + it('should fail if a number, non-standard object, regex, boolean, array of non-objects, or non-empty string is provided in data parameter', () => { + // One could choose to pass a falsy value to the second param because they have or will + // supply data with the set() method instead. - qb.reset_query(); expect(() => qb.update('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',test_data_set), 'array of non-empty standard objects provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',test_data), 'non-empty array provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',test_data_set), 'array of non-empty standard objects provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',NaN), 'NaN provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',false), 'false provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',''), 'empty string provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',null), 'null provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',undefined), 'undefined provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies'), 'nothing provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',NaN), 'NaN provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',false), 'false provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',''), 'empty string provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',null), 'null provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',undefined), 'undefined provided').to.not.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies'), 'nothing provided').to.not.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3), 'integer provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3.5), 'float provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',true), 'true provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',Infinity), 'Infinity provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('foobar',{}), 'empty object provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[]), 'empty array provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',/foobar/), 'regex provided').to.throw(Error); - qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',' '), 'string full of spaces provided').to.throw(Error); - }); - it('should require that there is at least something being updated', () => { - // @todo - }); - it('should utilize pre-existing tables set in from_array', () => { - qb.reset_query(); - qb.from('galaxies'); - const sql = qb.update(null, test_data, test_where); - sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); - }); - it('should utilize pre-existing value set in in set_array', () => { - qb.reset_query(); - qb.set(test_data); - const sql = qb.update('galaxies'); - sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); - }); - it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { - qb.reset_query(); - qb.from('galaxies').set(test_data); - const sql = qb.update(); - sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); - }); - it('should accept a non-empty object for the data parameter', () => { - qb.reset_query(); - const sql = qb.update('galaxies', test_data); - sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); - }); - it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', () => { - qb.reset_query(); - const sql = qb.update('galaxies', test_data_set); - qb.reset_query(); - const sql_b = qb.update_batch('galaxies', test_data_set, 'id'); - sql.should.eql(sql_b); - }); - it('should fail if any invalid values are passed in the data object.', () => { - qb.reset_query(); - const func = () => console.log("foo"); - const regex = /foobar/; - const arr = [1,2,3]; - const obj = {foo: 'bar'}; + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3), 'integer provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',3.5), 'float provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',true), 'true provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',Infinity), 'Infinity provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('foobar',{}), 'empty object provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[{},{}]), 'array of empty objects provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[]), 'empty array provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',[1,2]), 'array of numbers provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',['abc',2,{foo:'bar'}]), 'array of mixed values provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',/foobar/), 'regex provided').to.throw(Error); + qb.reset_query(); expect(() => qb.set({id:2}).update('galaxies',' '), 'string full of spaces provided').to.throw(Error); + }); + it('should require that there is at least something being updated', () => { + // @todo + }); + it('should utilize pre-existing tables set in from_array', () => { + qb.reset_query(); + qb.from('galaxies'); + const sql = qb.update(null, test_data, test_where); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral' WHERE `id` = 3"); + }); + it('should utilize pre-existing value set in in set_array', () => { + qb.reset_query(); + qb.set(test_data); + const sql = qb.update('galaxies'); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); + }); + it('should utilize pre-existing tables and values from from_aray and set_array, respectively', () => { + qb.reset_query(); + qb.from('galaxies').set(test_data); + const sql = qb.update(); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); + }); + it('should accept a non-empty object for the data parameter', () => { + qb.reset_query(); + const sql = qb.update('galaxies', test_data); + sql.should.eql("UPDATE (`galaxies`) SET `name` = 'Milky Way', `type` = 'spiral'"); + }); + it('should convert call to update_batch() if an array of non-emtpy objects is passed in the data parameter', () => { + qb.reset_query(); + const sql = qb.update('galaxies', test_data_set); + qb.reset_query(); + const sql_b = qb.update_batch('galaxies', test_data_set, 'id'); + sql.should.eql(sql_b); + }); + it('should fail if any invalid values are passed in the data object.', () => { + qb.reset_query(); + const func = () => console.log("foo"); + const regex = /foobar/; + const arr = [1,2,3]; + const obj = {foo: 'bar'}; - qb.reset_query(); expect(() => qb.update('galaxies',{id: func}), 'function in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: regex}), 'regex in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: undefined}), 'undefined in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: arr}), 'array in data').to.throw(Error); - qb.reset_query(); expect(() => qb.update('galaxies',{id: obj}), 'object in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: func}), 'function in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: regex}), 'regex in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: Infinity}), 'Infinity in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: undefined}), 'undefined in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: NaN}), 'NaN in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: arr}), 'array in data').to.throw(Error); + qb.reset_query(); expect(() => qb.update('galaxies',{id: obj}), 'object in data').to.throw(Error); - }); - /* - it('should support insert ignore statements', () => { - qb.reset_query(); - const sql = qb.insert_ignore('galaxies', test_data, 'ON DUPLICATE KEY UPDATE last_update = NOW()'); - sql.should.eql("INSERT IGNORE INTO `galaxies` (`id`, `name`, `type`) VALUES (3, 'Milky Way', 'spiral') ON DUPLICATE KEY UPDATE last_update = NOW()"); - }); */ + }); }); diff --git a/test/mysql/03-tests-update_batch.js b/test/mysql/03-tests-update_batch.js index b81d4c0..be4ddea 100755 --- a/test/mysql/03-tests-update_batch.js +++ b/test/mysql/03-tests-update_batch.js @@ -6,16 +6,16 @@ const qb = new QueryBuilder(); const test_where = {id:3}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; -describe('update_batch()', () => { - it('should exist', () => { - should.exist(qb.update_batch); - }); - it('should be a function', () => { - qb.update_batch.should.be.a('function'); - }); - it('should build a proper batch UPDATE string', () => { - qb.reset_query(); - const sql = qb.update_batch('galaxies', test_data, 'id'); - sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); - }); +describe('MySQL: update_batch()', () => { + it('should exist', () => { + should.exist(qb.update_batch); + }); + it('should be a function', () => { + qb.update_batch.should.be.a('function'); + }); + it('should build a proper batch UPDATE string', () => { + qb.reset_query(); + const sql = qb.update_batch('galaxies', test_data, 'id'); + sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); + }); }); diff --git a/test/mysql/05-tests-multiple-queries.js b/test/mysql/05-tests-multiple-queries.js index 0fcfb4f..c678b70 100755 --- a/test/mysql/05-tests-multiple-queries.js +++ b/test/mysql/05-tests-multiple-queries.js @@ -1,29 +1,29 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306, - debug: false, -}; -const QueryBuilder = require('../../index.js'); -const pool = new QueryBuilder(settings, 'mysql', 'pool'); - -describe('Multiple Queries', () => { - it('should not get confused about table after delete records', done => { - pool.get_connection(qb => { - qb.limit(1).delete('cities', (err, result) => { - qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { - qb.release(); - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - done(); - }); - }); - }); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306, + debug: false, +}; +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); + +describe('MySQL: Multiple Queries', () => { + it('should not get confused about table after delete records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); From a41f9d9e64bbbe0f330e73057a9fe33051d5030e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Wed, 25 Jul 2018 16:13:10 -0400 Subject: [PATCH 163/200] Fixed #18. Added a generic config file for test tests containing mock credentials and updated all files using credentials to use that instead. --- HISTORY.md | 1 + Makefile | 6 +-- test/05-multiple-drivers.js | 24 ++++++++++++ test/configs.js | 22 +++++++++++ test/mssql/00-tests-adapter.js | 15 +------- test/mssql/04-tests-query-response.js | 15 +------- test/mssql/05-tests-multiple-pools.js | 35 ++++++++++++++++++ test/mssql/05-tests-multiple-queries.js | 12 +----- test/mssql/create_mssql_mock.sh | 13 ++++++- test/mssql/mock_data2.sql | 1 + test/mysql/00-tests-adapter.js | 8 +--- test/mysql/04-tests-query-response.js | 11 +----- test/mysql/05-tests-multiple-pools.js | 35 ++++++++++++++++++ test/mysql/05-tests-multiple-queries.js | 9 +---- test/mysql/create_mysql_mock.sh | 13 +++++-- test/mysql/mock_data2.sql | 49 +++++++++++++++++++++++++ 16 files changed, 198 insertions(+), 71 deletions(-) create mode 100755 test/configs.js create mode 100755 test/mssql/05-tests-multiple-pools.js create mode 100755 test/mssql/mock_data2.sql create mode 100755 test/mysql/05-tests-multiple-pools.js create mode 100755 test/mysql/mock_data2.sql diff --git a/HISTORY.md b/HISTORY.md index 5fec97a..3aa2c7d 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -14,6 +14,7 @@ you spot any mistakes. ** `acquireTimeout` (milliseconds before a timeout occurs during the connection acquisition) * Added new query building method: `returning()` to allow for insert IDs to be returned. See docs for more info. * Added new tests +* Fixed [#18](https://github.com/kylefarris/node-querybuilder/issues/18) * Fixed [#26](https://github.com/kylefarris/node-querybuilder/issues/26) diff --git a/Makefile b/Makefile index f221be0..7544859 100755 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ TESTS = test/mysql/*.js test/mssql/*.js -#TESTS = test/mssql/*.js -#TESTS = test/mssql/04-tests-query-response.js -#TESTS = test/mssql/05-tests-multiple-queries.js -#TESTS = test/mssql/03-tests-insert.js +#TESTS = test/mssql/05-tests-multiple-pools.js +#TESTS = test/05-multiple-drivers.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/test/05-multiple-drivers.js b/test/05-multiple-drivers.js index e69de29..4e8f3a5 100755 --- a/test/05-multiple-drivers.js +++ b/test/05-multiple-drivers.js @@ -0,0 +1,24 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const configs = require('configs'); + +const QueryBuilder = require('../../index.js'); +const my_pool = new QueryBuilder(configs.mysql, 'mysql', 'pool'); +const ms_pool = new QueryBuilder(configs.mssql, 'mssql', 'pool'); + +describe('Multiple Drivers', () => { + it('should not get confused between pools from different drivers', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + done(); + }); + }); + }); + }); +}); diff --git a/test/configs.js b/test/configs.js new file mode 100755 index 0000000..986f974 --- /dev/null +++ b/test/configs.js @@ -0,0 +1,22 @@ +const configs = { + mysql: { + host: '127.0.0.1', + database: 'mock_db', + user: 'travis', + version: '2.5.4', + port: 3306, + debug: false, + }, + mssql: { + host: 'localhost', + database: 'mock_db', + user: 'travis', + password: 'Password123', + version: '4.1.0', + port: 1433, + options: { + encrypt: false + } + }, +}; +module.exports = configs; diff --git a/test/mssql/00-tests-adapter.js b/test/mssql/00-tests-adapter.js index e242004..4726338 100755 --- a/test/mssql/00-tests-adapter.js +++ b/test/mssql/00-tests-adapter.js @@ -1,6 +1,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const QueryBuilder = require('../../index.js'); +const settings = require('../configs').mssql; +const driver = 'mssql'; const check = (done, f) => { try { @@ -35,19 +37,6 @@ const connection_released = qb => { }; describe('MSSQL: QueryBuilder() - Adapter', () => { - const driver = 'mssql'; - const settings = { - host: 'localhost', - database: 'mock_db', - user: 'travis', - password: 'Password123', - version: '4.1.0', - port: 1433, - options: { - encrypt: false - } - }; - afterEach(done => { // const qb = new QueryBuilder(Object.assign({}, settings), driver); // qb.disconnect(); diff --git a/test/mssql/04-tests-query-response.js b/test/mssql/04-tests-query-response.js index 60eccd0..16f9f2a 100755 --- a/test/mssql/04-tests-query-response.js +++ b/test/mssql/04-tests-query-response.js @@ -1,6 +1,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const QueryBuilder = require('../../index.js'); +const settings = require('../configs').mssql; +const driver = 'mssql'; const check = (done, f) => { try { @@ -12,19 +14,6 @@ const check = (done, f) => { }; describe('MSSQL: Query Responses', () => { - const driver = 'mssql'; - const settings = { - host: 'localhost', - database: 'mock_db', - user: 'travis', - password: 'Password123', - version: '4.1.0', - port: 1433, - options: { - encrypt: false - } - }; - it('should allow us to execute a simple SELECT query', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { diff --git a/test/mssql/05-tests-multiple-pools.js b/test/mssql/05-tests-multiple-pools.js new file mode 100755 index 0000000..ba906a8 --- /dev/null +++ b/test/mssql/05-tests-multiple-pools.js @@ -0,0 +1,35 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = require('../configs').mssql; +const QueryBuilder = require('../../index.js'); +const pool1 = new QueryBuilder(settings, 'mssql', 'pool'); +const pool2 = new QueryBuilder(Object.assign({}, settings, {database: 'mock_db2'}), 'mssql', 'pool'); +let pool1_settings, pool2_settings; + +const compare_connections = (done) => { + try { + const db1 = pool1_settings.connection_settings.options.database; + const db2 = pool2_settings.connection_settings.options.database; + db1.should.not.be.eql(db2); + done(); + } catch(e) { + done(e); + } +}; + +describe('MSSQL: Multiple Pools', () => { + it('should not get confused by what pool settings to use', done => { + let connections_established = 0; + + pool1.get_connection(qb1 => { + pool1_settings = qb1.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); + }); + pool2.get_connection(qb2 => { + pool2_settings = qb2.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); + }); + }); +}); diff --git a/test/mssql/05-tests-multiple-queries.js b/test/mssql/05-tests-multiple-queries.js index b5ae760..bff4885 100755 --- a/test/mssql/05-tests-multiple-queries.js +++ b/test/mssql/05-tests-multiple-queries.js @@ -1,16 +1,6 @@ const should = require('chai').should(); const expect = require('chai').expect; -const settings = { - host: 'localhost', - database: 'mock_db', - user: 'travis', - password: 'Password123', - version: '4.1.0', - port: 1433, - options: { - encrypt: false - } -}; +const settings = require('../configs').mssql; const check = (done, f) => { try { diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh index ebcad18..01cb6a6 100755 --- a/test/mssql/create_mssql_mock.sh +++ b/test/mssql/create_mssql_mock.sh @@ -6,20 +6,29 @@ db_user=travis password=Password123 db_name=mock_db db_table=cities +db2_name=mock_db2 +db2_table=cities2 # Initial Admin Stuff sqlcmd -S $server -U $admin_user -P $admin_pass -Q "If not Exists (SELECT loginname FROM [master].[dbo].[syslogins] WHERE name = '$db_user') CREATE LOGIN $db_user WITH PASSWORD='$password';" sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db_name') IS NULL) CREATE DATABASE $db_name;" -sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "IF NOT EXISTS (SELECT * FROM [sys].[server_principals] WHERE [name] = '$db_user') CREATE USER $db_user FOR LOGIN $db_user;" +sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db2_name') IS NULL) CREATE DATABASE $db2_name;" +sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 +sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "GRANT CONTROL ON DATABASE:: $db_name TO $db_user;" +sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "GRANT CONTROL ON DATABASE:: $db2_name TO $db_user;" # User stuff sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "IF OBJECT_ID('dbo.$db_table', 'U') IS NOT NULL DROP TABLE dbo.$db_table;" +sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "IF OBJECT_ID('dbo.$db2_table', 'U') IS NOT NULL DROP TABLE dbo.$db2_table;" sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" +sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "CREATE TABLE $db2_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql +sqlcmd -S $server -U $db_user -P $password -d $db2_name -i mock_data2.sql # Check to see if table exists now... #sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" # Check to see if data exists in $db_name -sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [cities];" +sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [$db_table];" +sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "SELECT TOP 5 * FROM [$db2_table];" diff --git a/test/mssql/mock_data2.sql b/test/mssql/mock_data2.sql new file mode 100755 index 0000000..001a494 --- /dev/null +++ b/test/mssql/mock_data2.sql @@ -0,0 +1 @@ +INSERT INTO [cities2] VALUES ('Aaronsburg','PA'),('Abbeville','AL'),('Abbeville','GA'); diff --git a/test/mysql/00-tests-adapter.js b/test/mysql/00-tests-adapter.js index 3a761d9..91868f5 100755 --- a/test/mysql/00-tests-adapter.js +++ b/test/mysql/00-tests-adapter.js @@ -2,6 +2,7 @@ const chai = require('chai'); chai.Assertion.includeStack = true; const should = chai.should(); const expect = chai.expect; +const settings = require('../configs').mysql; const QueryBuilder = require('../../index.js'); const check = (done, f) => { @@ -26,13 +27,6 @@ describe('QueryBuilder() - MySQL Adapter', () => { console.log("connected!"); }; const driver = 'mysql'; - const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306 - }; const bad_user = Object.assign({},settings, {user: 'foobar'}); const bad_host = Object.assign({},settings, {host: 'nonlocalhost'}); const bad_password = Object.assign({},settings, {password: 'password'}); diff --git a/test/mysql/04-tests-query-response.js b/test/mysql/04-tests-query-response.js index 7fabade..eb27cce 100755 --- a/test/mysql/04-tests-query-response.js +++ b/test/mysql/04-tests-query-response.js @@ -1,6 +1,8 @@ const should = require('chai').should(); const expect = require('chai').expect; const QueryBuilder = require('../../index.js'); +const settings = require('../configs').mysql; +const driver = 'mysql'; const check = (done, f) => { try { @@ -12,15 +14,6 @@ const check = (done, f) => { }; describe('MySQL: Query Responses', () => { - const driver = 'mysql'; - const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306 - }; - it('should allow us to execute a simple SELECT query', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { diff --git a/test/mysql/05-tests-multiple-pools.js b/test/mysql/05-tests-multiple-pools.js new file mode 100755 index 0000000..3dad127 --- /dev/null +++ b/test/mysql/05-tests-multiple-pools.js @@ -0,0 +1,35 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = require('../configs').mysql; +const QueryBuilder = require('../../index.js'); +const pool1 = new QueryBuilder(settings, 'mysql', 'pool'); +const pool2 = new QueryBuilder(Object.assign({}, settings, {database: 'mock_db2'}), 'mysql', 'pool'); +let pool1_settings, pool2_settings; + +const compare_connections = (done) => { + try { + const db1 = pool1_settings.database; + const db2 = pool2_settings.database; + db1.should.not.be.eql(db2); + done(); + } catch(e) { + done(e); + } +}; + +describe('MySQL: Multiple Pools', () => { + it('should not get confused by what pool settings to use', done => { + let connections_established = 0; + + pool1.get_connection(qb1 => { + pool1_settings = qb1.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); + }); + pool2.get_connection(qb2 => { + pool2_settings = qb2.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); + }); + }); +}); diff --git a/test/mysql/05-tests-multiple-queries.js b/test/mysql/05-tests-multiple-queries.js index c678b70..2845bf4 100755 --- a/test/mysql/05-tests-multiple-queries.js +++ b/test/mysql/05-tests-multiple-queries.js @@ -1,13 +1,6 @@ const should = require('chai').should(); const expect = require('chai').expect; -const settings = { - host: '127.0.0.1', - database: 'mock_db', - user: 'travis', - version: '2.5.4', - port: 3306, - debug: false, -}; +const settings = require('../configs').mysql; const QueryBuilder = require('../../index.js'); const pool = new QueryBuilder(settings, 'mysql', 'pool'); diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 0501c13..7b1bc3a 100755 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash -mysql -uroot -e "CREATE DATABSE IF NOT EXISTS mock_db;" -mysql -uroot -e "CREATE USER IF NOT EXISTS 'travis'@'localhost';" -mysql -uroot -e "GRANT ALL ON `mock_db`.* TO 'travis'@'localhost';" -mysql -utravis mock_db < ./test/mysql/mock_data.sql +mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS mock_db;" +mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS mock_db2;" +mysql -uroot -p -e "CREATE USER IF NOT EXISTS 'travis'@'localhost';" +mysql -uroot -p -e "GRANT ALL ON mock_db.* TO 'travis'@'localhost';" +mysql -uroot -p -e "GRANT ALL ON mock_db2.* TO 'travis'@'localhost';" +mysql -uroot -p mock_db -e "DROP TABLE IF EXISTS cities;" +mysql -uroot -p mock_db2 -e "DROP TABLE IF EXISTS cities2;" +mysql -utravis mock_db < ./mock_data.sql +mysql -utravis mock_db2 < ./mock_data2.sql diff --git a/test/mysql/mock_data2.sql b/test/mysql/mock_data2.sql new file mode 100755 index 0000000..46314f0 --- /dev/null +++ b/test/mysql/mock_data2.sql @@ -0,0 +1,49 @@ +-- MySQL dump 10.14 Distrib 5.5.40-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: mock_db2 +-- ------------------------------------------------------ +-- Server version 5.5.40-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `cities` +-- + +DROP TABLE IF EXISTS `cities2`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cities2` ( + `city` varchar(50) NOT NULL, + `state_code` char(2) NOT NULL, + KEY `idx_state_code` (`state_code`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cities2` +-- + +LOCK TABLES `cities2` WRITE; +/*!40000 ALTER TABLE `cities2` DISABLE KEYS */; +INSERT INTO `cities2` VALUES ('Aaronsburg','PA'),('Abbeville','AL'),('Abbeville','GA'); +/*!40000 ALTER TABLE `cities2` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; From f14be2b62ec93f49fb00fbf9ae5d4c0051063fef Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 10:08:22 -0400 Subject: [PATCH 164/200] Wrote test to make sure multiple driver pools will work in conjunction without crosstalk. --- Makefile | 2 +- test/05-multiple-drivers.js | 45 +++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 7544859..9e05038 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TESTS = test/mysql/*.js test/mssql/*.js +TESTS = test/mysql/*.js test/mssql/*.js test/*.js #TESTS = test/mssql/05-tests-multiple-pools.js #TESTS = test/05-multiple-drivers.js test: diff --git a/test/05-multiple-drivers.js b/test/05-multiple-drivers.js index 4e8f3a5..6392f88 100755 --- a/test/05-multiple-drivers.js +++ b/test/05-multiple-drivers.js @@ -1,24 +1,41 @@ const should = require('chai').should(); const expect = require('chai').expect; -const configs = require('configs'); +const configs = require('./configs'); -const QueryBuilder = require('../../index.js'); +const QueryBuilder = require('../index.js'); const my_pool = new QueryBuilder(configs.mysql, 'mysql', 'pool'); const ms_pool = new QueryBuilder(configs.mssql, 'mssql', 'pool'); +let my_pool_settings, ms_pool_settings; + +const compare_connections = (done) => { + try { + expect(my_pool_settings, 'should have port property').to.have.property('port'); + expect(ms_pool_settings, 'should have connection_settings property').to.have.property('connection_settings'); + + const port1 = my_pool_settings.port; + const port2 = ms_pool_settings.connection_settings.options.port; + + port1.should.not.be.eql(port2); + + done(); + } catch(e) { + done(e); + } +}; describe('Multiple Drivers', () => { - it('should not get confused between pools from different drivers', done => { - pool.get_connection(qb => { - qb.limit(1).delete('cities', (err, result) => { - qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { - qb.release(); - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affectedRows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - done(); - }); - }); + it('should not get confused by what pool/settings to use', done => { + let connections_established = 0; + + my_pool.get_connection(qb1 => { + my_pool_settings = qb1.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); + }); + ms_pool.get_connection(qb2 => { + ms_pool_settings = qb2.connection_settings(); + connections_established++; + if (connections_established >= 2) compare_connections(done); }); }); }); From f867db18c10bfc3b2cf428a4faefc9b049346f08 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 10:27:45 -0400 Subject: [PATCH 165/200] Fixes #23 --- Makefile | 2 +- drivers/query_builder.js | 12 +++++++++++- test/mysql/03-tests-update_batch.js | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9e05038..70039db 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mysql/*.js test/mssql/*.js test/*.js -#TESTS = test/mssql/05-tests-multiple-pools.js +#TESTS = test/mysql/03-tests-update_batch.js #TESTS = test/05-multiple-drivers.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 0323ce6..346ccd4 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -1529,8 +1529,18 @@ class GenericQueryBuilder { cases += `ELSE ${l} END, `; } + // Remove the trailing comma sql += cases.substr(0, cases.length - 2); - sql += ` WHERE ${where + index} IN (${ids.join(',')})`; + + // Make sure we don't double-up on the "WHERE" directive + if (where) { + sql += ` ${where}`; + } else { + sql += ' WHERE '; + } + + + sql += `${index} IN (${ids.join(',')})`; // Add query to batch batches.push(sql); diff --git a/test/mysql/03-tests-update_batch.js b/test/mysql/03-tests-update_batch.js index be4ddea..673a6ea 100755 --- a/test/mysql/03-tests-update_batch.js +++ b/test/mysql/03-tests-update_batch.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mysql/query_builder.js'); const qb = new QueryBuilder(); -const test_where = {id:3}; +const test_where = {quadrant: 'Alpha'}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; describe('MySQL: update_batch()', () => { @@ -18,4 +18,9 @@ describe('MySQL: update_batch()', () => { const sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `id` IN (3,4)"]); }); + it('should build a proper batch UPDATE string when where clause is provided', () => { + qb.reset_query(); + const sql = qb.update_batch('galaxies', test_data, 'id', test_where); + sql.should.eql(["UPDATE (`galaxies`) SET `name` = CASE WHEN `id` = 3 THEN 'Milky Way' WHEN `id` = 4 THEN 'Andromeda' ELSE `name` END, `type` = CASE WHEN `id` = 3 THEN 'spiral' WHEN `id` = 4 THEN 'spiral' ELSE `type` END WHERE `quadrant` = 'Alpha' AND `id` IN (3,4)"]); + }); }); From b6190cdc4c2a9ac61d0bfc32431cfa5ac241550c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 10:32:55 -0400 Subject: [PATCH 166/200] Added update batch test to mssql test suite. --- test/mssql/03-tests-update_batch.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/mssql/03-tests-update_batch.js b/test/mssql/03-tests-update_batch.js index 9e8f731..5de95e6 100755 --- a/test/mssql/03-tests-update_batch.js +++ b/test/mssql/03-tests-update_batch.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; const QueryBuilder = require('../../drivers/mssql/query_builder.js'); const qb = new QueryBuilder(); -const test_where = {id:3}; +const test_where = {quadrant: 'Alpha'}; const test_data = [{id:3, name:'Milky Way', type: 'spiral'}, {id:4, name: 'Andromeda', type: 'spiral'}]; describe('MSSQL: update_batch()', () => { @@ -18,4 +18,9 @@ describe('MSSQL: update_batch()', () => { const sql = qb.update_batch('galaxies', test_data, 'id'); sql.should.eql(["UPDATE ([galaxies]) SET [name] = CASE WHEN [id] = 3 THEN 'Milky Way' WHEN [id] = 4 THEN 'Andromeda' ELSE [name] END, [type] = CASE WHEN [id] = 3 THEN 'spiral' WHEN [id] = 4 THEN 'spiral' ELSE [type] END WHERE [id] IN (3,4)"]); }); + it('should build a proper batch UPDATE string when where clause is provided', () => { + qb.reset_query(); + const sql = qb.update_batch('galaxies', test_data, 'id', test_where); + sql.should.eql(["UPDATE ([galaxies]) SET [name] = CASE WHEN [id] = 3 THEN 'Milky Way' WHEN [id] = 4 THEN 'Andromeda' ELSE [name] END, [type] = CASE WHEN [id] = 3 THEN 'spiral' WHEN [id] = 4 THEN 'spiral' ELSE [type] END WHERE [quadrant] = 'Alpha' AND [id] IN (3,4)"]); + }); }); From a15ed96926250ce942f23018a06d2bd9590a2ab7 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 10:56:00 -0400 Subject: [PATCH 167/200] Fixed #28 --- Makefile | 2 +- drivers/query_builder.js | 19 +++++++++++-------- test/mssql/01-tests-where.js | 5 +++++ test/mysql/01-tests-where.js | 5 +++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 70039db..679dd9a 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mysql/*.js test/mssql/*.js test/*.js -#TESTS = test/mysql/03-tests-update_batch.js +#TESTS = test/mssql/01-tests-where.js #TESTS = test/05-multiple-drivers.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 346ccd4..4ddd32a 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -404,7 +404,7 @@ class GenericQueryBuilder { escape = (typeof escape === 'boolean' ? escape : true); - if (typeof key === 'string' && typeof value === 'object' && Array.isArray(value) && value.length > 0) { + if (typeof key === 'string' && Array.isArray(value) && value.length > 0) { return this._where_in(key, value, false, 'AND '); } return this._where(key, value, 'AND ', escape); @@ -422,7 +422,7 @@ class GenericQueryBuilder { _where(key, value=null, type='AND ', escape=true) { escape = (typeof escape === 'boolean' ? escape : true); - // Must be an object or a string + // If key is not an object.... if (Object.prototype.toString.call(key) !== Object.prototype.toString.call({})) { // If it's not an object, it must be a string if (typeof key !== 'string') { @@ -467,15 +467,18 @@ class GenericQueryBuilder { key = key_array; } - if (Object.keys(key).length == 0) { + // Fail if its an empty object + if (Object.keys(key).length === 0) { throw new Error("where(): You haven't provided any key value pairs to limit the resultset by."); } + // If an object is supplied... for (let k in key) { let v = key[k]; - if (typeof v === 'object' && Array.isArray(v) && v.length > 0) { - return this._where_in(k, v, false, type, escape); + if (Array.isArray(v) && v.length > 0) { + this._where_in(k, v, false, type, escape); + continue; } const prefix = (this.where_array.length == 0 ? '' : type); @@ -1300,9 +1303,9 @@ class GenericQueryBuilder { this.from(table); } - if (Object.prototype.toString.call(where) === Object.prototype.toString.call({})) { - if (Object.keys(where).length == 0) { - throw new Error("where(): The object you provided to limit the deletion of rows is empty."); + if (Object.prototype.toString.call(where) === Object.prototype.toString.call({}) && where !== null) { + if (Object.keys(where).length === 0) { + throw new Error("where(): The object you provided to limit the deletion of rows is empty. Provide NULL if you need to an empty value."); } else { this.where(where); diff --git a/test/mssql/01-tests-where.js b/test/mssql/01-tests-where.js index b232908..0802978 100755 --- a/test/mssql/01-tests-where.js +++ b/test/mssql/01-tests-where.js @@ -146,6 +146,11 @@ describe('MSSQL: where()', () => { qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); qb.where_array.should.eql([`[galaxy_id] > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(''Milky Way'',''Andromeda''))'`]); }); + it('should allow for arrays and non-arrays as values within a where object without dropping anything', () => { + qb.reset_query(); + qb.where({planet:'Earth', star_system:'Solar', moons: [1,3,5]}); + qb.where_array.should.eql(["[planet] = 'Earth'", "AND [star_system] = 'Solar'", "AND [moons] IN (1, 3, 5)"]); + }); }); describe('MSSQL: or_where()', () => { diff --git a/test/mysql/01-tests-where.js b/test/mysql/01-tests-where.js index 9a84dd4..8b7dd44 100755 --- a/test/mysql/01-tests-where.js +++ b/test/mysql/01-tests-where.js @@ -146,6 +146,11 @@ describe('MySQL: where()', () => { qb.where('galaxy_id >', "(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN('Milky Way','Andromeda'))"); qb.where_array.should.eql(["`galaxy_id` > '(SELECT MIN(id) first_galaxy FROM galaxies WHERE id IN(\\'Milky Way\\',\\'Andromeda\\'))'"]); }); + it('should allow for arrays and non-arrays as values within a where object without dropping anything', () => { + qb.reset_query(); + qb.where({planet:'Earth', star_system:'Solar', moons: [1,3,5]}); + qb.where_array.should.eql(["`planet` = 'Earth'", "AND `star_system` = 'Solar'", "AND `moons` IN (1, 3, 5)"]); + }); }); describe('MySQL: or_where()', () => { From f6878b2cc831985a2daf13c1bec3cc0ff0a4d390 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 14:10:01 -0400 Subject: [PATCH 168/200] Fixed #30 --- Makefile | 2 +- drivers/query_builder.js | 25 +- test/mssql/01-tests-like.js | 383 +++++++++++++------------- test/mssql/04-tests-query-response.js | 5 +- test/mysql/01-tests-like.js | 383 +++++++++++++------------- 5 files changed, 410 insertions(+), 388 deletions(-) diff --git a/Makefile b/Makefile index 679dd9a..13a7901 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mysql/*.js test/mssql/*.js test/*.js -#TESTS = test/mssql/01-tests-where.js +#TESTS = test/mssql/01-tests-like.js #TESTS = test/05-multiple-drivers.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 4ddd32a..8d166e2 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -604,7 +604,7 @@ class GenericQueryBuilder { for (let k in field) { let like_statement; - const v = field[k]; + let v = field[k]; k = this._protect_identifiers(k.trim()); // Make sure value is only string, number, or boolean @@ -616,17 +616,32 @@ class GenericQueryBuilder { throw new Error("like(): You have provided an invalid number value as the second parameter. Only valid strings, numbers, and booleans are allowed."); } + // Make sure to escape the value... + v = this._qb_escape(v); + if (side === 'none') { - like_statement = k + not + ' LIKE ' + "'" + v + "'"; + like_statement = k + not + ` LIKE ${v}`; } else if (side === 'before' || side === 'left') { - like_statement = k + not + ' LIKE ' + "'%" + v + "'"; + if (typeof v === 'string') { + like_statement = k + not + ` LIKE ${v.substr(0, 1)}%${v.substr(1)}`; + } else { + like_statement = k + not + ` LIKE %${v}`; + } } else if (side === 'after' || side === 'right') { - like_statement = k + not + ' LIKE ' + "'" + v + "%'"; + if (typeof v === 'string') { + like_statement = k + not + ` LIKE ${v.substr(0, v.length -1)}%${v.slice(-1)}`; + } else { + like_statement = k + not + ` LIKE ${v}%`; + } } else if (side === 'both') { - like_statement = k + not + ' LIKE ' + "'%" + v + "%'"; + if (typeof v === 'string') { + like_statement = k + not + ` LIKE ${v.substr(0, 1)}%${v.substr(1, v.length -2)}%${v.slice(-1)}`; + } else { + like_statement = k + not + ` LIKE %${v}%`; + } } else { throw new Error("like(): Invalid direction provided!"); diff --git a/test/mssql/01-tests-like.js b/test/mssql/01-tests-like.js index b23de2d..d99be6d 100755 --- a/test/mssql/01-tests-like.js +++ b/test/mssql/01-tests-like.js @@ -1,189 +1,194 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const QueryBuilder = require('../../drivers/mssql/query_builder.js'); -const qb = new QueryBuilder(); - -describe('MSSQL: like()', () => { - it('should exist', () => { - should.exist(qb.like); - }); - it('should be a function', () => { - qb.like.should.be.a('function'); - }); - it('should require first parameter to be a valid string or object with key value pairs', () => { - expect(() => qb.like(), 'nothing provided').to.throw(Error); - expect(() => qb.like(null), 'null provided').to.throw(Error); - expect(() => qb.like(false), 'false provided').to.throw(Error); - expect(() => qb.like(true), 'true provided').to.throw(Error); - expect(() => qb.like({}), 'empty object provided').to.throw(Error); - expect(() => qb.like(3), 'integer provided').to.throw(Error); - expect(() => qb.like(3.5), 'float provided').to.throw(Error); - expect(() => qb.like([]), 'empty array provided').to.throw(Error); - expect(() => qb.like(''), 'empty string provided').to.throw(Error); - - expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', () => { - expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); - expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', () => { - expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); - expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - - expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); - expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); - expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); - expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { - expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - - expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); - }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'after'); - qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'right'); - qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); - }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'before'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'left'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); - }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'both'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); - }); - it('should put AND in between multiple LIKE clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); - }); -}); - -describe('MSSQL: or_like()', () => { - it('should exist', () => { - should.exist(qb.or_like); - }); - it('should be a function', () => { - qb.or_like.should.be.a('function'); - }); - it('should put OR in between multiple OR LIKE clauses', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky'); - qb.or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'", "OR [planet_name] LIKE 'Jup%'"]); - }); -}); - -describe('MSSQL: not_like()', () => { - it('should exist', () => { - should.exist(qb.not_like); - }); - it('should be a function', () => { - qb.not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); - }); - it('should put AND in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); - }); -}); - -describe('MSSQL: or_not_like()', () => { - it('should exist', () => { - should.exist(qb.or_not_like); - }); - it('should be a function', () => { - qb.or_not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); - }); - it('should put OR in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [galaxy_name] LIKE '%meda'", "OR [planet_name] NOT LIKE 'ear%'"]); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mssql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('MSSQL: like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); + + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'"]); + }); + it('should not allow apostrophes in the query', () => { + qb.reset_query(); + qb.like('galaxy_name', "O'Conner", 'both'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%O''Conner%'"]); + }); +}); + +describe('MSSQL: or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "OR [planet_name] LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] LIKE 'ear%'", "OR [planet_name] LIKE 'Jup%'"]); + }); +}); + +describe('MSSQL: not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [planet_name] NOT LIKE 'ear%'"]); + }); +}); + +describe('MSSQL: or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["[galaxy_name] NOT LIKE '%milky%'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["[galaxy_name] LIKE '%milky%'", "AND [galaxy_name] LIKE '%meda'", "OR [planet_name] NOT LIKE 'ear%'"]); + }); +}); diff --git a/test/mssql/04-tests-query-response.js b/test/mssql/04-tests-query-response.js index 16f9f2a..a53f0aa 100755 --- a/test/mssql/04-tests-query-response.js +++ b/test/mssql/04-tests-query-response.js @@ -62,12 +62,9 @@ describe('MSSQL: Query Responses', () => { it('should respond with the requested IDs (from the `returning()` method) after insert', done => { const qb = new QueryBuilder(Object.assign({}, settings), driver); qb.connect(err => { - expect(err).to.not.be.instanceof(Error); - + expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); qb.returning(['city', 'state_code']).insert('cities', {city: 'Node QB Returns', state_code: 'NQ'}, (err, res) => { check(done, () => { - console.log("Result: ", JSON.stringify(res)); - expect(err, 'there should not be an error when querying').to.not.be.instanceof(Error); expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); expect(res.insert_id, 'insert id should be the values of the ids requested').to.not.be.null; expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); diff --git a/test/mysql/01-tests-like.js b/test/mysql/01-tests-like.js index 0cc339e..70f7e5c 100755 --- a/test/mysql/01-tests-like.js +++ b/test/mysql/01-tests-like.js @@ -1,189 +1,194 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const QueryBuilder = require('../../drivers/mysql/query_builder.js'); -const qb = new QueryBuilder(); - -describe('MySQL: like()', () => { - it('should exist', () => { - should.exist(qb.like); - }); - it('should be a function', () => { - qb.like.should.be.a('function'); - }); - it('should require first parameter to be a valid string or object with key value pairs', () => { - expect(() => qb.like(), 'nothing provided').to.throw(Error); - expect(() => qb.like(null), 'null provided').to.throw(Error); - expect(() => qb.like(false), 'false provided').to.throw(Error); - expect(() => qb.like(true), 'true provided').to.throw(Error); - expect(() => qb.like({}), 'empty object provided').to.throw(Error); - expect(() => qb.like(3), 'integer provided').to.throw(Error); - expect(() => qb.like(3.5), 'float provided').to.throw(Error); - expect(() => qb.like([]), 'empty array provided').to.throw(Error); - expect(() => qb.like(''), 'empty string provided').to.throw(Error); - - expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); - }); - it('should require second parameter if first paramter is a string', () => { - expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); - expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); - expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); - }); - it('should require second parameter (when provided) to be a string, number, or boolean', () => { - expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); - expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); - - expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); - expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); - expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); - expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); - expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); - }); - it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { - expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); - expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); - - expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); - expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); - }); - it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'after'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); - }); - it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'before'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'left'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); - }); - it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - - qb.reset_query(); - qb.like('galaxy_name', 'milky', 'both'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); - }); - it('should put AND in between multiple LIKE clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky'); - qb.like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); - }); -}); - -describe('MySQL: or_like()', () => { - it('should exist', () => { - should.exist(qb.or_like); - }); - it('should be a function', () => { - qb.or_like.should.be.a('function'); - }); - it('should put OR in between multiple OR LIKE clauses', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky'); - qb.or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); - }); -}); - -describe('MySQL: not_like()', () => { - it('should exist', () => { - should.exist(qb.not_like); - }); - it('should be a function', () => { - qb.not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put AND in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky'); - qb.not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); - }); -}); - -describe('MySQL: or_not_like()', () => { - it('should exist', () => { - should.exist(qb.or_not_like); - }); - it('should be a function', () => { - qb.or_not_like.should.be.a('function'); - }); - it('should put NOT before LIKE', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); - }); - it('should put OR in between multiple NOT LIKE clauses', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky'); - qb.or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable', () => { - qb.reset_query(); - qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); - qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); - it('should be chainable with regular like clauses', () => { - qb.reset_query(); - qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') - qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../drivers/mysql/query_builder.js'); +const qb = new QueryBuilder(); + +describe('MySQL: like()', () => { + it('should exist', () => { + should.exist(qb.like); + }); + it('should be a function', () => { + qb.like.should.be.a('function'); + }); + it('should require first parameter to be a valid string or object with key value pairs', () => { + expect(() => qb.like(), 'nothing provided').to.throw(Error); + expect(() => qb.like(null), 'null provided').to.throw(Error); + expect(() => qb.like(false), 'false provided').to.throw(Error); + expect(() => qb.like(true), 'true provided').to.throw(Error); + expect(() => qb.like({}), 'empty object provided').to.throw(Error); + expect(() => qb.like(3), 'integer provided').to.throw(Error); + expect(() => qb.like(3.5), 'float provided').to.throw(Error); + expect(() => qb.like([]), 'empty array provided').to.throw(Error); + expect(() => qb.like(''), 'empty string provided').to.throw(Error); + + expect(() => qb.like('planet_name','ear','after'), 'valid string').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'valid object').to.not.throw(Error); + }); + it('should require second parameter if first paramter is a string', () => { + expect(() => qb.like('planet_name'), 'no second param provided').to.throw(Error); + expect(() => qb.like('planet_name','ear'), 'valid second param provided').to.not.throw(Error); + expect(() => qb.like({planet_name: 'ear'}), 'object provided as first param').to.not.throw(Error); + }); + it('should require second parameter (when provided) to be a string, number, or boolean', () => { + expect(() => qb.like('planet_name',null), 'null provided').to.throw(Error); + expect(() => qb.like('planet_name',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('planet_name',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('planet_name',Infinity), 'empty array provided').to.throw(Error); + + expect(() => qb.like('planet_name',false), 'false provided').to.not.throw(Error); + expect(() => qb.like('planet_name',true), 'true provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3), 'integer provided').to.not.throw(Error); + expect(() => qb.like('planet_name',3.5), 'float provided').to.not.throw(Error); + expect(() => qb.like('planet_name',''), 'empty string provided').to.not.throw(Error); + expect(() => qb.like('planet_name','ear'), 'non-empty string provided').to.not.throw(Error); + }); + it('should only accept the following direction strings in the third parameter: undefined, "both", "right", "left", "before", "after"', () => { + expect(() => qb.like('galaxy_name','milk',null), 'null provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',{}), 'empty object provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',[]), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',NaN), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',Infinity), 'empty array provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',false), 'false provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',true), 'true provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3), 'integer provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',3.5), 'float provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk',''), 'empty string provided').to.throw(Error); + expect(() => qb.like('galaxy_name','milk','foo'), 'non-empty string provided').to.throw(Error); + + expect(() => qb.like('galaxy_name','milk'), 'no third param provided').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','right'), 'right as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','left'), 'left as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','both'), 'both as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','before'), 'before as third param').to.not.throw(Error); + expect(() => qb.like('galaxy_name','milk','after'), 'after as third param').to.not.throw(Error); + }); + it('should put percentage signs on the right side of the condition if "right" or "after" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'after'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE 'milky%'"]); + }); + it('should put percentage signs on the left side of the condition if "before" or "left" are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'before'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'left'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky'"]); + }); + it('should put percentage signs on both sides of the condition if "both" or undefined are passed as the 3rd parameter', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + + qb.reset_query(); + qb.like('galaxy_name', 'milky', 'both'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'"]); + }); + it('should put AND in between multiple LIKE clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky'); + qb.like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'"]); + }); + it('should not allow apostrophes in the query', () => { + qb.reset_query(); + qb.like('galaxy_name', "O'Conner", 'both'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%O\\'Conner%'"]); + }); +}); + +describe('MySQL: or_like()', () => { + it('should exist', () => { + should.exist(qb.or_like); + }); + it('should be a function', () => { + qb.or_like.should.be.a('function'); + }); + it('should put OR in between multiple OR LIKE clauses', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky'); + qb.or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_like('galaxy_name', 'milky').or_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "OR `planet_name` LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('planet_name', 'ear', 'right').or_like('planet_name','Jup','right'); + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` LIKE 'ear%'", "OR `planet_name` LIKE 'Jup%'"]); + }); +}); + +describe('MySQL: not_like()', () => { + it('should exist', () => { + should.exist(qb.not_like); + }); + it('should be a function', () => { + qb.not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put AND in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky'); + qb.not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.not_like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `planet_name` NOT LIKE 'ear%'"]); + }); +}); + +describe('MySQL: or_not_like()', () => { + it('should exist', () => { + should.exist(qb.or_not_like); + }); + it('should be a function', () => { + qb.or_not_like.should.be.a('function'); + }); + it('should put NOT before LIKE', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'"]); + }); + it('should put OR in between multiple NOT LIKE clauses', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky'); + qb.or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable', () => { + qb.reset_query(); + qb.or_not_like('galaxy_name', 'milky').or_not_like('planet_name', 'ear', 'right'); + qb.where_array.should.eql(["`galaxy_name` NOT LIKE '%milky%'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); + it('should be chainable with regular like clauses', () => { + qb.reset_query(); + qb.like('galaxy_name', 'milky').like('galaxy_name', 'meda', 'before').or_not_like('planet_name', 'ear', 'right') + qb.where_array.should.eql(["`galaxy_name` LIKE '%milky%'", "AND `galaxy_name` LIKE '%meda'", "OR `planet_name` NOT LIKE 'ear%'"]); + }); +}); From c76300f90fb3904cad327cc0f9eaabe69350f89e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 14:18:10 -0400 Subject: [PATCH 169/200] Fixed some code formatting things. --- test/mssql/00-AA-tests-general.js | 18 +++---- test/mssql/01-tests-where_in.js | 34 ++++++------- test/mssql/05-tests-multiple-queries.js | 66 ++++++++++++------------- test/mysql/00-AA-tests-general.js | 18 +++---- 4 files changed, 68 insertions(+), 68 deletions(-) diff --git a/test/mssql/00-AA-tests-general.js b/test/mssql/00-AA-tests-general.js index ac5ad6d..2d03f94 100755 --- a/test/mssql/00-AA-tests-general.js +++ b/test/mssql/00-AA-tests-general.js @@ -1,9 +1,9 @@ -const should = require('chai').should(); - -describe('MSSQL: QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mssql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); -}); +const should = require('chai').should(); + +describe('MSSQL: QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mssql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); diff --git a/test/mssql/01-tests-where_in.js b/test/mssql/01-tests-where_in.js index 39696cc..555cfd1 100755 --- a/test/mssql/01-tests-where_in.js +++ b/test/mssql/01-tests-where_in.js @@ -38,40 +38,40 @@ describe('MSSQL: where_in()', () => { it('should not accept anything but a non-empty array of values as second parameter', () => { qb.reset_query(); expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); - expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); - expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); - expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); - expect(() => qb.where_in('planet',{}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in('planet',{foo:'bar'}), 'empty object provided').to.throw(Error); - expect(() => qb.where_in('planet',3), 'integer provided').to.throw(Error); - expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); - expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); - expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); - expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); - expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); + expect(() => qb.where_in('planet', null), 'null provided').to.throw(Error); + expect(() => qb.where_in('planet', false), 'false provided').to.throw(Error); + expect(() => qb.where_in('planet', true), 'true provided').to.throw(Error); + expect(() => qb.where_in('planet', {}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet', {foo:'bar'}), 'empty object provided').to.throw(Error); + expect(() => qb.where_in('planet', 3), 'integer provided').to.throw(Error); + expect(() => qb.where_in('planet', 3.5), 'float provided').to.throw(Error); + expect(() => qb.where_in('planet', NaN), 'NaN provided').to.throw(Error); + expect(() => qb.where_in('planet', Infinity), 'Infinity provided').to.throw(Error); + expect(() => qb.where_in('planet', []), 'empty array provided').to.throw(Error); + expect(() => qb.where_in('planet', ''), 'empty string provided').to.throw(Error); + expect(() => qb.where_in('planet', /foobar/), 'regex provided').to.throw(Error); - expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); + expect(() => qb.where_in('planet', ['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); }); it('should require both a field name an array of values as first and second parameters, respectively', () => { qb.reset_query(); - qb.where_in('planet_position',[1,2,3]); + qb.where_in('planet_position', [1,2,3]); qb.where_array.should.eql(['[planet_position] IN (1, 2, 3)']); }); it('should concatenate multiple WHERE IN clauses with AND ', () => { qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']); - qb.where_in('galaxy_id',[123,456,789,0110]); + qb.where_in('galaxy_id', [123,456,789,0110]); qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [galaxy_id] IN (123, 456, 789, 72)"]); }); it('should be chainable', () => { qb.reset_query(); - qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); + qb.where_in('planet', ['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); qb.where_array.should.eql(["[planet] IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND [planet_position] IN (1, 2, 3, 4)"]); }); it('should not escape fields if asked not to', () => { qb.reset_query(); - qb.where_in('planet_position',[1,2,3],false); + qb.where_in('planet_position', [1, 2, 3],false); qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); diff --git a/test/mssql/05-tests-multiple-queries.js b/test/mssql/05-tests-multiple-queries.js index bff4885..3504070 100755 --- a/test/mssql/05-tests-multiple-queries.js +++ b/test/mssql/05-tests-multiple-queries.js @@ -1,33 +1,33 @@ -const should = require('chai').should(); -const expect = require('chai').expect; -const settings = require('../configs').mssql; - -const check = (done, f) => { - try { - f(); - done(); - } catch(e) { - done(e); - } -}; - -const QueryBuilder = require('../../index.js'); -const pool = new QueryBuilder(settings, 'mssql', 'pool'); - -describe('MSSQL: Multiple Queries', () => { - it('should not get confused about table after deleting records', done => { - pool.get_connection(qb => { - qb.limit(1).delete('cities', (err, result) => { - qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { - qb.release(); - check(done, () => { - expect(err, 'should not error on delete').to.not.be.instanceof(Error); - expect(result.affected_rows, 'one record should be deleted').to.be.eql(1); - expect(err2, 'should not error on select').to.not.be.instanceof(Error); - expect(result2.length, 'should have one result').to.be.equal(1); - }); - }); - }); - }); - }); -}); +const should = require('chai').should(); +const expect = require('chai').expect; +const settings = require('../configs').mssql; + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +const QueryBuilder = require('../../index.js'); +const pool = new QueryBuilder(settings, 'mssql', 'pool'); + +describe('MSSQL: Multiple Queries', () => { + it('should not get confused about table after deleting records', done => { + pool.get_connection(qb => { + qb.limit(1).delete('cities', (err, result) => { + qb.select(['city', 'state_code']).from('cities').limit(1).get((err2, result2) => { + qb.release(); + check(done, () => { + expect(err, 'should not error on delete').to.not.be.instanceof(Error); + expect(result.affected_rows, 'one record should be deleted').to.be.eql(1); + expect(err2, 'should not error on select').to.not.be.instanceof(Error); + expect(result2.length, 'should have one result').to.be.equal(1); + }); + }); + }); + }); + }); +}); diff --git a/test/mysql/00-AA-tests-general.js b/test/mysql/00-AA-tests-general.js index b8add19..ddf7f8d 100755 --- a/test/mysql/00-AA-tests-general.js +++ b/test/mysql/00-AA-tests-general.js @@ -1,9 +1,9 @@ -const should = require('chai').should(); - -describe('MySQL: QueryBuilder', () => { - it('actually exists and can be initialized', () => { - const QueryBuilder = require('../../drivers/mysql/query_builder.js'); - const qb = new QueryBuilder(); - qb.should.be.instanceOf(QueryBuilder); - }); -}); +const should = require('chai').should(); + +describe('MySQL: QueryBuilder', () => { + it('actually exists and can be initialized', () => { + const QueryBuilder = require('../../drivers/mysql/query_builder.js'); + const qb = new QueryBuilder(); + qb.should.be.instanceOf(QueryBuilder); + }); +}); From 185be280ab9453f5af628f26e906397a18714317 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 17:16:50 -0400 Subject: [PATCH 170/200] Fixes #34. Updated HISTORY file. Updated create_mock_dbs script to have travis build sqlserver as well (hopefull). --- HISTORY.md | 14 ++++++++++++-- Makefile | 2 +- drivers/query_builder.js | 14 ++++++-------- test/create_mock_dbs.sh | 2 +- test/mssql/01-tests-where_in.js | 9 +++++++-- test/mysql/01-tests-where_in.js | 23 ++++++++++++++--------- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3aa2c7d..f475210 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,16 +6,26 @@ you spot any mistakes. ## v2.0.0 (2018-06-15) -* Added mssql support -* Updated class files to use new ES6 class syntax for easier-maintainability +### Breaking Changes * Changed the Query Builder instantiation syntax +* Passing an empty array to `where_in` and `where_not_in` no longer throws an error ([#34](https://github.com/kylefarris/node-querybuilder/issues/34)) + +# General Enhancements/Changes/Features +* Added mssql (t-sql) support using `tedious` as the underlying driver +* Updated class files to use new ES6 class syntax for easier-maintainability * Added new options: ** `pool_min` (minimum number of pooled connections (`mssql` driver only)) ** `acquireTimeout` (milliseconds before a timeout occurs during the connection acquisition) * Added new query building method: `returning()` to allow for insert IDs to be returned. See docs for more info. * Added new tests + +### Bug Fixes * Fixed [#18](https://github.com/kylefarris/node-querybuilder/issues/18) +* Fixed [#23](https://github.com/kylefarris/node-querybuilder/issues/23) * Fixed [#26](https://github.com/kylefarris/node-querybuilder/issues/26) +* Fixed [#28](https://github.com/kylefarris/node-querybuilder/issues/28) +* Fixed [#30](https://github.com/kylefarris/node-querybuilder/issues/30) +* Fixed [#33](https://github.com/kylefarris/node-querybuilder/issues/33) ## v1.2.0 (2018-05-18) diff --git a/Makefile b/Makefile index 13a7901..47c3841 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TESTS = test/mysql/*.js test/mssql/*.js test/*.js -#TESTS = test/mssql/01-tests-like.js +#TESTS = test/mssql/01-tests-where_in.js #TESTS = test/05-multiple-drivers.js test: mocha --timeout 5000 --reporter spec $(TESTS) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 8d166e2..c73f961 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -540,21 +540,19 @@ class GenericQueryBuilder { throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid field name provided."); } - // Values must be an array... + // `values` must be an array... if (!Array.isArray(values)) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values."); - } - else { - if (values.length == 0) { - throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): You have provided an empty list of values to limit resultset by."); - } + throw new Error("where_" + (not === '' ? '' : not.toLowerCase() + '_') + "in(): Invalid second parameter provided--it must be an array of scalar values or an empty array."); } + // If array is empty, ignore this request + else if (values.length === 0) return; + for (let i in values) { this.where_in_array.push(this._qb_escape(values[i])); } - const prefix = (this.where_array.length == 0 ? '' : type); + const prefix = (this.where_array.length === 0 ? '' : type); const where_in = prefix + this._protect_identifiers(key, false, escape) + not + " IN (" + this.where_in_array.join(', ') + ")"; this.where_array.push(where_in); diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 30bb61f..d736caf 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash ./mysql/create_mysql_mock.sh -#./mssql/create_mssql_mock.sh +./mssql/create_mssql_mock.sh diff --git a/test/mssql/01-tests-where_in.js b/test/mssql/01-tests-where_in.js index 555cfd1..0125fa3 100755 --- a/test/mssql/01-tests-where_in.js +++ b/test/mssql/01-tests-where_in.js @@ -37,7 +37,6 @@ describe('MSSQL: where_in()', () => { }); it('should not accept anything but a non-empty array of values as second parameter', () => { qb.reset_query(); - expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); expect(() => qb.where_in('planet', null), 'null provided').to.throw(Error); expect(() => qb.where_in('planet', false), 'false provided').to.throw(Error); expect(() => qb.where_in('planet', true), 'true provided').to.throw(Error); @@ -47,10 +46,11 @@ describe('MSSQL: where_in()', () => { expect(() => qb.where_in('planet', 3.5), 'float provided').to.throw(Error); expect(() => qb.where_in('planet', NaN), 'NaN provided').to.throw(Error); expect(() => qb.where_in('planet', Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.where_in('planet', []), 'empty array provided').to.throw(Error); expect(() => qb.where_in('planet', ''), 'empty string provided').to.throw(Error); expect(() => qb.where_in('planet', /foobar/), 'regex provided').to.throw(Error); + expect(() => qb.where_in('planet'), 'nothing provided (empty array is default value)').to.not.throw(Error); + expect(() => qb.where_in('planet', []), 'empty array provided').to.not.throw(Error); expect(() => qb.where_in('planet', ['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); }); it('should require both a field name an array of values as first and second parameters, respectively', () => { @@ -58,6 +58,11 @@ describe('MSSQL: where_in()', () => { qb.where_in('planet_position', [1,2,3]); qb.where_array.should.eql(['[planet_position] IN (1, 2, 3)']); }); + it('should ignore the request if an empty array is provided to the second parameter', () => { + qb.reset_query(); + qb.where_in('planet_position', []); + qb.where_array.should.eql([]); + }); it('should concatenate multiple WHERE IN clauses with AND ', () => { qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']); diff --git a/test/mysql/01-tests-where_in.js b/test/mysql/01-tests-where_in.js index 4e70aa8..034b8f5 100755 --- a/test/mysql/01-tests-where_in.js +++ b/test/mysql/01-tests-where_in.js @@ -35,9 +35,8 @@ describe('MySQL: where_in()', () => { expect(() => qb.where_in('planet_position',[1,2,3]), 'valid string provided').to.not.throw(Error); }); - it('should not accept anything but a non-empty array of values as second parameter', () => { + it('should not accept anything but an array of values as second parameter', () => { qb.reset_query(); - expect(() => qb.where_in('planet'), 'nothing provided').to.throw(Error); expect(() => qb.where_in('planet',null), 'null provided').to.throw(Error); expect(() => qb.where_in('planet',false), 'false provided').to.throw(Error); expect(() => qb.where_in('planet',true), 'true provided').to.throw(Error); @@ -47,31 +46,37 @@ describe('MySQL: where_in()', () => { expect(() => qb.where_in('planet',3.5), 'float provided').to.throw(Error); expect(() => qb.where_in('planet',NaN), 'NaN provided').to.throw(Error); expect(() => qb.where_in('planet',Infinity), 'Infinity provided').to.throw(Error); - expect(() => qb.where_in('planet',[]), 'empty array provided').to.throw(Error); expect(() => qb.where_in('planet',''), 'empty string provided').to.throw(Error); expect(() => qb.where_in('planet',/foobar/), 'regex provided').to.throw(Error); - expect(() => qb.where_in('planet',['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); + expect(() => qb.where_in('planet'), 'nothing provided (empty array is default value)').to.not.throw(Error); + expect(() => qb.where_in('planet', []), 'empty array provided').to.not.throw(Error); + expect(() => qb.where_in('planet', ['Mars','Earth','Venus','Mercury']), 'non-empty array provided').to.not.throw(Error); }); - it('should require both a field name an array of values as first and second parameters, respectively', () => { + it('should accept both a field name and array of values as first and second parameters, respectively', () => { qb.reset_query(); - qb.where_in('planet_position',[1,2,3]); + qb.where_in('planet_position', [1,2,3]); qb.where_array.should.eql(['`planet_position` IN (1, 2, 3)']); }); + it('should ignore the request if an empty array is provided to the second parameter', () => { + qb.reset_query(); + qb.where_in('planet_position', []); + qb.where_array.should.eql([]); + }); it('should concatenate multiple WHERE IN clauses with AND ', () => { qb.reset_query(); qb.where_in('planet',['Mercury','Venus','Earth','Mars']); - qb.where_in('galaxy_id',[123,456,789,0110]); + qb.where_in('galaxy_id', [123, 456, 789, 0110]); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `galaxy_id` IN (123, 456, 789, 72)"]); }); it('should be chainable', () => { qb.reset_query(); - qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position',[1,2,3,4]); + qb.where_in('planet',['Mercury','Venus','Earth','Mars']).where_in('planet_position', [1,2,3,4]); qb.where_array.should.eql(["`planet` IN ('Mercury', 'Venus', 'Earth', 'Mars')","AND `planet_position` IN (1, 2, 3, 4)"]); }); it('should not escape fields if asked not to', () => { qb.reset_query(); - qb.where_in('planet_position',[1,2,3],false); + qb.where_in('planet_position', [1,2,3], false); qb.where_array.should.eql(['planet_position IN (1, 2, 3)']); }); }); From 1ca80916587ecb227a3601f29b8c4c7916ae871c Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Thu, 26 Jul 2018 17:33:05 -0400 Subject: [PATCH 171/200] Accomplishes #32 - Implement insert_batch as described in docs --- drivers/mssql/query_exec.js | 26 +++++++++++++++++++------- drivers/mysql/query_exec.js | 28 ++++++++++++++++++---------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index 5d972e3..3dee253 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -99,14 +99,14 @@ class QueryExec extends QueryBuilder { this._exec(sql,cb); } - insert(table,set,cb,ignore,suffix) { - const sql = this._insert(table,set,ignore,suffix); + insert(table, set, cb, ignore, suffix) { + const sql = this._insert(table, set, ignore, suffix); this.reset_query(sql); this._exec(sql,cb); } - insert_ignore(table,set,on_dupe,cb) { - throw new Error("This feature is currently unsupported in the MSSQL driver."); + insert_ignore(table, set, on_dupe, cb) { + throw new Error("insert_ignore(): This feature is currently unsupported in the MSSQL driver."); // if (typeof on_dupe === 'function') { // cb = on_dupe; // on_dupe = null; @@ -116,10 +116,22 @@ class QueryExec extends QueryBuilder { // this._exec(sql,cb); } - insert_batch(table,set,cb) { - const sql = this._insert_batch(table,set); + insert_batch(table, set, ignore, on_dupe, cb) { + if (typeof ignore === 'function') { + cb = ignore; + ignore = null; + } + else if (typeof on_dupe === 'function') { + cb = on_dupe; + on_dupe = null; + } + + if (ignore) throw new Error("QE insert_batch(): INSERT IGNORE is currently unsupported on the MSSQL driver."); + if (suffix) throw new Error("QE insert_batch(): 'on_dupe' string (4th parameter) is currently unsupported on the MSSQL driver."); + + const sql = this._insert_batch(table, set); this.reset_query(sql); - this._exec(sql,cb); + this._exec(sql, cb); } update(table,set,where,cb) { diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 3dea7d9..9eaa4e9 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -87,25 +87,33 @@ class QueryExec extends QueryBuilder { } insert(table,set,cb,ignore,suffix) { - const sql = this._insert(table,set,ignore,suffix); + const sql = this._insert(table, set, ignore, suffix); this.reset_query(sql); - this._exec(sql,cb); + this._exec(sql, cb); } - insert_ignore(table,set,on_dupe,cb) { + insert_ignore(table, set, on_dupe, cb) { if (typeof on_dupe === 'function') { cb = on_dupe; on_dupe = null; } - const sql = this._insert_ignore(table,set,on_dupe); + const sql = this._insert_ignore(table, set, on_dupe); this.reset_query(sql); this._exec(sql,cb); } - insert_batch(table,set,cb) { - const sql = this._insert_batch(table,set); + insert_batch(table, set, ignore, on_dupe, cb) { + if (typeof ignore === 'function') { + cb = ignore; + ignore = null; + } + else if (typeof on_dupe === 'function') { + cb = on_dupe; + on_dupe = null; + } + const sql = this._insert_batch(table, set, ignore, on_dupe); this.reset_query(sql); - this._exec(sql,cb); + this._exec(sql, cb); } update(table,set,where,cb) { @@ -121,9 +129,9 @@ class QueryExec extends QueryBuilder { where = null; } - const sql = this._update(table,set,where); + const sql = this._update(table, set, where); this.reset_query(sql); - this._exec(sql,cb); + this._exec(sql, cb); } // TODO: Write this complicated-ass function @@ -140,7 +148,7 @@ class QueryExec extends QueryBuilder { where = null; } - const sqls = this._update_batch(table,set,index,where); + const sqls = this._update_batch(table, set, index, where); const results = null; const errors = []; From 5a4cd3b9ddd88b70160db79f7ea9ccab18a0f0b8 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 27 Jul 2018 10:36:33 -0400 Subject: [PATCH 172/200] Updated package version to 2.0.0-beta --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 01a3f90..b156b56 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "node-querybuilder", - "version": "1.2.2", + "version": "2.0.0-beta", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", + "bugs": "https://github.com/kylefarris/node-querybuilder/issues", "scripts": { "test": "make test" }, @@ -27,7 +28,7 @@ "chai": "^4.1.2", "mocha": "*" }, - "main": "./", + "main": "index.js", "engines": { "node": ">=8.0" } From 506be0d738107e544d8e001bb3136b356a1ef02e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 27 Jul 2018 11:31:14 -0400 Subject: [PATCH 173/200] Fixed driver definition for mssql. --- drivers/drivers.json | 44 +++----------------------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/drivers/drivers.json b/drivers/drivers.json index 106721d..abdf6da 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -18,47 +18,9 @@ "path": "./drivers/mssql/", "active": true }, - "default": "4.1.0" - } - }, - "oracle": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "0.3.7": { - "path": "./drivers/oracle/", - "active": false - }, - "default": "0.3.7" - } - }, - "postgres": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "4.2.0": { - "path": "./drivers/postgres/", - "active": false - }, - "default": "4.2.0" - } - }, - "sqlite": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "3.0.4": { - "path": "./drivers/sqlite/", - "active": false - }, - "default": "3.0.4" - } - }, - "mongo": { - "connection_types": { "single": true, "pool": false, "cluster": false }, - "versions": { - "1.4.30": { - "path": "./drivers/mongo/", - "active": false - }, - "default": "1.4.30" + "default": { + "version": "4.1.0" + } } } } From a5833223551c7ccf95d50a5f1b2a4d95d90adbf3 Mon Sep 17 00:00:00 2001 From: Flamenco Date: Fri, 3 Aug 2018 09:04:52 -0400 Subject: [PATCH 174/200] bugfix: Return reserved identifier Fixes https://github.com/kylefarris/node-querybuilder/issues/37 --- drivers/query_builder.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index c73f961..3fe0714 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -269,7 +269,9 @@ class GenericQueryBuilder { parts = parts.map((v,i) => { if (!this.reserved_identifiers.includes(v)) { return this._escape_identifiers(v); - return v; + } + else { + return v; } }); From 8be2c877c00643830b66a46a39c321b542ea0b66 Mon Sep 17 00:00:00 2001 From: Flamenco Date: Fri, 3 Aug 2018 10:34:37 -0400 Subject: [PATCH 175/200] Remove trailing slash from driver path Fixes https://github.com/kylefarris/node-querybuilder/issues/39 --- drivers/drivers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/drivers.json b/drivers/drivers.json index abdf6da..da0ca14 100755 --- a/drivers/drivers.json +++ b/drivers/drivers.json @@ -3,7 +3,7 @@ "connection_types": { "single": true, "pool": true, "cluster": false }, "versions": { "2.5.4": { - "path": "./drivers/mysql/", + "path": "./drivers/mysql", "active": true }, "default": { @@ -15,7 +15,7 @@ "connection_types": { "single": true, "pool": true, "cluster": false }, "versions": { "4.1.0": { - "path": "./drivers/mssql/", + "path": "./drivers/mssql", "active": true }, "default": { From 24bd2f09f9d2efe03327255511a4cfe9b58c0e6b Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Fri, 26 Jul 2019 13:06:23 -0400 Subject: [PATCH 176/200] Testing on Bionic and node 8-12 now --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8efdb3..abc5ab7 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ before_script: ./test/create_mock_dbs.sh language: node_js -dist: precise +dist: bionic node_js: - - "10.1.0" - - "9.11.1" - - "8.11.2" + - "12" + - "11" + - "10" + - "9" + - "8" services: mysql test: adapter: mysql2 From b9a87d0c85c550093413359d48f4fd9012d60947 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 15:22:28 -0400 Subject: [PATCH 177/200] Fixed bug caused by array of data passed to the `insert` method --- drivers/query_builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/query_builder.js b/drivers/query_builder.js index 3fe0714..d9c1940 100755 --- a/drivers/query_builder.js +++ b/drivers/query_builder.js @@ -1137,7 +1137,7 @@ class GenericQueryBuilder { throw new Error("insert(): Invalid data provided to insert into database!"); } if (Array.isArray(set)) { - return this.insert_batch(table, set, ignore, suffix); + return this._insert_batch(table, set, ignore, suffix); } if (set !== null) { From 3de3e61bbca4d81c44b34c4918177dc52ec8903e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 15:58:58 -0400 Subject: [PATCH 178/200] Trying to get travis to test for mssql. --- .travis.yml | 10 +- package-lock.json | 1028 +++++++++++++++++++++++++++++++ test/mssql/create_mssql_mock.sh | 32 +- 3 files changed, 1052 insertions(+), 18 deletions(-) create mode 100644 package-lock.json diff --git a/.travis.yml b/.travis.yml index abc5ab7..768729f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ -before_script: ./test/create_mock_dbs.sh +before_script: + - sudo docker run --name=mssql-server-linux-latest -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=Password123' -p 1433:1433 -d microsoft/mssql-server-linux:2017-latest + - sudo docker cp ./test/mssql/mock_data.sql mssql-server-linux-latest:mock_data.sql + - sudo docker cp ./test/mssql/mock_data2.sql mssql-server-linux-latest:mock_data2.sql + - ./test/create_mock_dbs.sh language: node_js dist: bionic node_js: @@ -7,7 +11,9 @@ node_js: - "10" - "9" - "8" -services: mysql +services: + - mysql + - docker test: adapter: mysql2 database: mock_db diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4bd6f29 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1028 @@ +{ + "name": "node-querybuilder", + "version": "2.0.0-beta", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + } + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "big-number": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/big-number/-/big-number-0.3.1.tgz", + "integrity": "sha1-rHMCDApZu3nrF8LOLbd/d9l04BM=" + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-js": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dns-lookup-all": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dns-lookup-all/-/dns-lookup-all-1.0.2.tgz", + "integrity": "sha1-TYsrGvacg6eyYutd6SSFt7OiFes=", + "requires": { + "semver": "^5.3.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", + "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "mysql": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.17.1.tgz", + "integrity": "sha512-7vMqHQ673SAk5C8fOzTG2LpPcf3bNt0oL3sFpxPEEFp1mdlDcrLK0On7z8ZYKaaHrHwNcQ/MTUz7/oobZ2OyyA==", + "requires": { + "bignumber.js": "7.2.1", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + } + }, + "native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A=" + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "sprintf": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz", + "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=" + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tedious": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-2.7.1.tgz", + "integrity": "sha512-u3ciATGm5byim91b3+c3MVTvY1zKjDmhUhnBQZXKymT2Vb9w322dziPQY6MhBNyBEcNONPsAMR+7/Uub7NYABQ==", + "requires": { + "babel-runtime": "^6.26.0", + "big-number": "0.3.1", + "bl": "^1.2.2", + "depd": "^1.1.2", + "iconv-lite": "^0.4.23", + "native-duplexpair": "^1.0.0", + "punycode": "^2.1.0", + "readable-stream": "^2.3.6", + "sprintf-js": "^1.1.1" + } + }, + "tedious-connection-pool": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tedious-connection-pool/-/tedious-connection-pool-1.0.5.tgz", + "integrity": "sha1-LWlKcqL0dzVk7ykR6G5f1iLKAcE=", + "requires": { + "tedious": "^1.14.0" + }, + "dependencies": { + "tedious": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-1.15.0.tgz", + "integrity": "sha1-m9qel5ghLI/NlDinDLKoBqvK5wo=", + "requires": { + "babel-runtime": "^6.9.2", + "big-number": "0.3.1", + "bl": "^1.0.0", + "dns-lookup-all": "^1.0.2", + "iconv-lite": "^0.4.11", + "readable-stream": "^2.0.2", + "sprintf": "0.1.5" + } + } + } + }, + "tsqlstring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tsqlstring/-/tsqlstring-1.0.0.tgz", + "integrity": "sha512-+j5NC+shdkRbXWU15K2AMqWZT7JvwCYwyGlUtece1TCVquBmg0aZ99dOAswWpxcWZRKzUNppM8/iDvshog1Otg==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh index 01cb6a6..2d54cfd 100755 --- a/test/mssql/create_mssql_mock.sh +++ b/test/mssql/create_mssql_mock.sh @@ -10,25 +10,25 @@ db2_name=mock_db2 db2_table=cities2 # Initial Admin Stuff -sqlcmd -S $server -U $admin_user -P $admin_pass -Q "If not Exists (SELECT loginname FROM [master].[dbo].[syslogins] WHERE name = '$db_user') CREATE LOGIN $db_user WITH PASSWORD='$password';" -sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db_name') IS NULL) CREATE DATABASE $db_name;" -sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db2_name') IS NULL) CREATE DATABASE $db2_name;" -sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 -sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 -sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "GRANT CONTROL ON DATABASE:: $db_name TO $db_user;" -sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "GRANT CONTROL ON DATABASE:: $db2_name TO $db_user;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -Q "If not Exists (SELECT loginname FROM [master].[dbo].[syslogins] WHERE name = '$db_user') CREATE LOGIN $db_user WITH PASSWORD='$password';" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db_name') IS NULL) CREATE DATABASE $db_name;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -Q "IF (db_id(N'$db2_name') IS NULL) CREATE DATABASE $db2_name;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "CREATE USER $db_user FOR LOGIN $db_user;" > /dev/null 2>&1 +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -d $db_name -Q "GRANT CONTROL ON DATABASE:: $db_name TO $db_user;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $admin_user -P $admin_pass -d $db2_name -Q "GRANT CONTROL ON DATABASE:: $db2_name TO $db_user;" # User stuff -sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "IF OBJECT_ID('dbo.$db_table', 'U') IS NOT NULL DROP TABLE dbo.$db_table;" -sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "IF OBJECT_ID('dbo.$db2_table', 'U') IS NOT NULL DROP TABLE dbo.$db2_table;" -sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" -sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "CREATE TABLE $db2_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" -sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql -sqlcmd -S $server -U $db_user -P $password -d $db2_name -i mock_data2.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "IF OBJECT_ID('dbo.$db_table', 'U') IS NOT NULL DROP TABLE dbo.$db_table;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "IF OBJECT_ID('dbo.$db2_table', 'U') IS NOT NULL DROP TABLE dbo.$db2_table;" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "CREATE TABLE $db2_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -i mock_data2.sql # Check to see if table exists now... -#sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" +#sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" # Check to see if data exists in $db_name -sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [$db_table];" -sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "SELECT TOP 5 * FROM [$db2_table];" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [$db_table];" +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "SELECT TOP 5 * FROM [$db2_table];" From 9b514788d3f1a986fea222f54e772746b6f984d1 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 16:02:05 -0400 Subject: [PATCH 179/200] Another attempt to get travis to test for mssql. --- test/create_mock_dbs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index d736caf..353d0fe 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -./mysql/create_mysql_mock.sh -./mssql/create_mssql_mock.sh +./test/mysql/create_mysql_mock.sh +./test/mssql/create_mssql_mock.sh \ No newline at end of file From 8b07a769781fc320b9e55aa5908afe8ce5bc776d Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 16:30:13 -0400 Subject: [PATCH 180/200] Removed mssql mock creation script to fix travis? --- test/create_mock_dbs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index 353d0fe..dd8a100 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash ./test/mysql/create_mysql_mock.sh -./test/mssql/create_mssql_mock.sh \ No newline at end of file +# ./test/mssql/create_mssql_mock.sh \ No newline at end of file From 89a28c6727abeb807d3d7a5c8e4c7035d0e96268 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 16:39:45 -0400 Subject: [PATCH 181/200] Bypassing mysql to see if mssql will work. --- test/create_mock_dbs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index dd8a100..d50c197 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -./test/mysql/create_mysql_mock.sh -# ./test/mssql/create_mssql_mock.sh \ No newline at end of file +# ./test/mysql/create_mysql_mock.sh +./test/mssql/create_mssql_mock.sh \ No newline at end of file From 3cf715dd91fa5215bd14479b0467d78a8a27c447 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 17:50:34 -0400 Subject: [PATCH 182/200] Attempting to get mysql built again... --- test/create_mock_dbs.sh | 2 +- test/mysql/create_mysql_mock.sh | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/create_mock_dbs.sh b/test/create_mock_dbs.sh index d50c197..353d0fe 100755 --- a/test/create_mock_dbs.sh +++ b/test/create_mock_dbs.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -# ./test/mysql/create_mysql_mock.sh +./test/mysql/create_mysql_mock.sh ./test/mssql/create_mssql_mock.sh \ No newline at end of file diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 7b1bc3a..7bf114b 100755 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS mock_db;" -mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS mock_db2;" -mysql -uroot -p -e "CREATE USER IF NOT EXISTS 'travis'@'localhost';" -mysql -uroot -p -e "GRANT ALL ON mock_db.* TO 'travis'@'localhost';" -mysql -uroot -p -e "GRANT ALL ON mock_db2.* TO 'travis'@'localhost';" -mysql -uroot -p mock_db -e "DROP TABLE IF EXISTS cities;" -mysql -uroot -p mock_db2 -e "DROP TABLE IF EXISTS cities2;" +mysql -uroot -e "CREATE DATABASE IF NOT EXISTS mock_db;" +mysql -uroot -e "CREATE DATABASE IF NOT EXISTS mock_db2;" +mysql -uroot -e "CREATE USER IF NOT EXISTS 'travis'@'localhost';" +mysql -uroot -e "GRANT ALL ON mock_db.* TO 'travis'@'localhost';" +mysql -uroot -e "GRANT ALL ON mock_db2.* TO 'travis'@'localhost';" +mysql -uroot mock_db -e "DROP TABLE IF EXISTS cities;" +mysql -uroot mock_db2 -e "DROP TABLE IF EXISTS cities2;" mysql -utravis mock_db < ./mock_data.sql mysql -utravis mock_db2 < ./mock_data2.sql From a717863e896cf3a775893073ea9d34e39ae6d066 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 17:55:49 -0400 Subject: [PATCH 183/200] MySQL tests are still failing and I don't know why... --- .travis.yml | 3 ++- test/mssql/create_mssql_mock.sh | 2 ++ test/mysql/create_mysql_mock.sh | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 768729f..f457b9f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ before_script: - sudo docker run --name=mssql-server-linux-latest -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=Password123' -p 1433:1433 -d microsoft/mssql-server-linux:2017-latest - sudo docker cp ./test/mssql/mock_data.sql mssql-server-linux-latest:mock_data.sql - sudo docker cp ./test/mssql/mock_data2.sql mssql-server-linux-latest:mock_data2.sql - - ./test/create_mock_dbs.sh + - ./test/mssql/create_mysql_mock.sh + - ./test/mssql/create_mssql_mock.sh language: node_js dist: bionic node_js: diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh index 2d54cfd..f0fed33 100755 --- a/test/mssql/create_mssql_mock.sh +++ b/test/mssql/create_mssql_mock.sh @@ -32,3 +32,5 @@ sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $s # Check to see if data exists in $db_name sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "SELECT TOP 5 * FROM [$db_table];" sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "SELECT TOP 5 * FROM [$db2_table];" + +echo "Done with MS SQL Import" \ No newline at end of file diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 7bf114b..1980f41 100755 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -8,3 +8,5 @@ mysql -uroot mock_db -e "DROP TABLE IF EXISTS cities;" mysql -uroot mock_db2 -e "DROP TABLE IF EXISTS cities2;" mysql -utravis mock_db < ./mock_data.sql mysql -utravis mock_db2 < ./mock_data2.sql + +echo "Done with MySQL Import" \ No newline at end of file From 77e5138f4d3999afb788091b661b51ac57b057b1 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:00:26 -0400 Subject: [PATCH 184/200] Stupid typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f457b9f..9b9fabc 100755 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ before_script: - sudo docker run --name=mssql-server-linux-latest -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=Password123' -p 1433:1433 -d microsoft/mssql-server-linux:2017-latest - sudo docker cp ./test/mssql/mock_data.sql mssql-server-linux-latest:mock_data.sql - sudo docker cp ./test/mssql/mock_data2.sql mssql-server-linux-latest:mock_data2.sql - - ./test/mssql/create_mysql_mock.sh + - ./test/mysql/create_mysql_mock.sh - ./test/mssql/create_mssql_mock.sh language: node_js dist: bionic From 6d27fb59a03e7b6de611d01c215dc1c347f51527 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:05:12 -0400 Subject: [PATCH 185/200] I feel like I'm actually getting closer... --- test/mssql/create_mssql_mock.sh | 4 ++-- test/mysql/create_mysql_mock.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh index f0fed33..31ff0d2 100755 --- a/test/mssql/create_mssql_mock.sh +++ b/test/mssql/create_mssql_mock.sh @@ -23,8 +23,8 @@ sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $s sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "IF OBJECT_ID('dbo.$db2_table', 'U') IS NOT NULL DROP TABLE dbo.$db2_table;" sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "CREATE TABLE $db2_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" -sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql -sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -i mock_data2.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -i ./test/mssql/mock_data.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -i ./test/mssql/mock_data2.sql # Check to see if table exists now... #sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" diff --git a/test/mysql/create_mysql_mock.sh b/test/mysql/create_mysql_mock.sh index 1980f41..1012c69 100755 --- a/test/mysql/create_mysql_mock.sh +++ b/test/mysql/create_mysql_mock.sh @@ -6,7 +6,7 @@ mysql -uroot -e "GRANT ALL ON mock_db.* TO 'travis'@'localhost';" mysql -uroot -e "GRANT ALL ON mock_db2.* TO 'travis'@'localhost';" mysql -uroot mock_db -e "DROP TABLE IF EXISTS cities;" mysql -uroot mock_db2 -e "DROP TABLE IF EXISTS cities2;" -mysql -utravis mock_db < ./mock_data.sql -mysql -utravis mock_db2 < ./mock_data2.sql +mysql -utravis mock_db < ./test/mysql/mock_data.sql +mysql -utravis mock_db2 < ./test/mysql/mock_data2.sql echo "Done with MySQL Import" \ No newline at end of file From 7784d5a077a8fead03880e401a8f24a29f6e2697 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:24:50 -0400 Subject: [PATCH 186/200] Gotta make sure the mock data makes it into the MSSQL database. --- test/mssql/create_mssql_mock.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mssql/create_mssql_mock.sh b/test/mssql/create_mssql_mock.sh index 31ff0d2..f0fed33 100755 --- a/test/mssql/create_mssql_mock.sh +++ b/test/mssql/create_mssql_mock.sh @@ -23,8 +23,8 @@ sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $s sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "IF OBJECT_ID('dbo.$db2_table', 'U') IS NOT NULL DROP TABLE dbo.$db2_table;" sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -Q "CREATE TABLE $db_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -Q "CREATE TABLE $db2_table([city] varchar(50) NOT NULL, [state_code] char(2) NOT NULL);" -sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -i ./test/mssql/mock_data.sql -sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -i ./test/mssql/mock_data2.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db_name -i mock_data.sql +sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -S $server -U $db_user -P $password -d $db2_name -i mock_data2.sql # Check to see if table exists now... #sudo docker exec -it mssql-server-linux-latest /opt/mssql-tools/bin/sqlcmd -U $db_user -P $password -d $db_name -Q "SELECT TABLE_NAME FROM $db_name.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'" From dac21d660053e08f961baa3f495149a77ad8271e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:43:52 -0400 Subject: [PATCH 187/200] Forcing mocha to quit when tests are done. --- Makefile | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 47c3841..ff9c2dc 100755 --- a/Makefile +++ b/Makefile @@ -2,6 +2,6 @@ TESTS = test/mysql/*.js test/mssql/*.js test/*.js #TESTS = test/mssql/01-tests-where_in.js #TESTS = test/05-multiple-drivers.js test: - mocha --timeout 5000 --reporter spec $(TESTS) + mocha --exit --timeout 5000 --reporter spec $(TESTS) .PHONY: test diff --git a/package.json b/package.json index b156b56..1a4d722 100755 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.0.0-beta", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", - "keywords": "nodejs, node, mysql, active record, activerecord, querybuilder, query builder, codeigniter, postgres, mssql, mongo, sqlite, translator", + "keywords": ["nodejs", "node", "mysql", "active record", "activerecord", "querybuilder", "query builder", "codeigniter", "postgres", "mssql", "mongo", "sqlite", "translator"], "bugs": "https://github.com/kylefarris/node-querybuilder/issues", "scripts": { "test": "make test" From 583f85c33f32d06de1463e8df98efe1e13f8ca0e Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:52:52 -0400 Subject: [PATCH 188/200] 2.0.0 --- package-lock.json | 2 +- package.json | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4bd6f29..64e5bdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.0-beta", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1a4d722..19abcda 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,23 @@ { "name": "node-querybuilder", - "version": "2.0.0-beta", + "version": "2.0.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", - "keywords": ["nodejs", "node", "mysql", "active record", "activerecord", "querybuilder", "query builder", "codeigniter", "postgres", "mssql", "mongo", "sqlite", "translator"], + "keywords": [ + "nodejs", + "node", + "mysql", + "active record", + "activerecord", + "querybuilder", + "query builder", + "codeigniter", + "postgres", + "mssql", + "mongo", + "sqlite", + "translator" + ], "bugs": "https://github.com/kylefarris/node-querybuilder/issues", "scripts": { "test": "make test" From 6f1b3968853f2c6035402defd570021275f44efa Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:54:42 -0400 Subject: [PATCH 189/200] Package file was not on the right version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19abcda..8520ac9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.0", + "version": "2.0.1", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": [ From 44ca4ea9205ff634ef7ce73bbb8e1f1ab7f396c9 Mon Sep 17 00:00:00 2001 From: Kyle Farris Date: Mon, 28 Oct 2019 18:55:02 -0400 Subject: [PATCH 190/200] 2.0.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64e5bdc..b7eb99e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.0", + "version": "2.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8520ac9..75792a6 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.1", + "version": "2.0.2", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": [ From 08d0a13258974daf83dc43d37cf4124af1f98bf4 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 10:43:25 -0500 Subject: [PATCH 191/200] First step to getting this module 'promisified'. Let's see what Travis CI thinks. --- drivers/QueryExecError.js | 13 +++ drivers/WrapperPromise.js | 51 ++++++++++++ drivers/mssql/query_exec.js | 122 ++++++++++++++++------------ drivers/mysql/query_exec.js | 155 ++++++++++++++++++++++-------------- 4 files changed, 231 insertions(+), 110 deletions(-) create mode 100644 drivers/QueryExecError.js create mode 100644 drivers/WrapperPromise.js diff --git a/drivers/QueryExecError.js b/drivers/QueryExecError.js new file mode 100644 index 0000000..b2e495e --- /dev/null +++ b/drivers/QueryExecError.js @@ -0,0 +1,13 @@ +/** + * List of Error of query execution + * + */ + +module.exports = { + NO_TBL_NAME_ERR: new Error("Table name not specified"), + NO_WHERE_CLAUSE_ERR: new Error("Where clause is not defined"), + NO_CONN_OBJ_ERR: new Error("No connection object supplied to the Query Exec Library!"), + FIRST_PARAM_OF_GET_WHERE_ERR: new Error("First parameter of get_where() must be a string or an array of strings."), + SECOND_PARAM_OF_GET_WHERE_ERR: new Error("Second parameter of get_where() must be an object with key:value pairs."), + NO_VALID_RESULTS_HANDLER: new Error('QueryBuilder is unsure how to respond since no callback or Promise resolve/reject methods were provided.'), +} diff --git a/drivers/WrapperPromise.js b/drivers/WrapperPromise.js new file mode 100644 index 0000000..9d4e6a1 --- /dev/null +++ b/drivers/WrapperPromise.js @@ -0,0 +1,51 @@ +class WrapperPromise { + /** + * + * @param {String} sql - A sql command + * @param {function} exec - A sql execution function + * @param {function} cb - A callback function + * + * @description + * Wraps the execute command in promise + */ + constructor(sql, exec, cb) { + this.sql = sql; + this.exec = exec; + if (typeof cb === "function") this.cb = cb.bind(this); + } + + /** + * Promisify + */ + promisify() { + return new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + this.invoke(); + }); + } + + /** + * Executet the query and resolve as the result + */ + invoke() { + this.exec(this.sql, (err, res) => { + if (err) { + this.reject(err); + return; + } + + /** + * If theres is a cb function, let the cb function resolve and reject the promise + */ + if (typeof this.cb === "function") { + this.cb(err, res); + return; + } + + this.resolve(res); + }); + } +} + +module.exports = WrapperPromise; diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index 3dee253..c7e51ff 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -51,6 +51,7 @@ class QueryExec extends QueryBuilder { } query(sql, cb) { + if (!cb || typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } @@ -62,15 +63,26 @@ class QueryExec extends QueryBuilder { const sql = this._count(table); this.reset_query(sql); - this._exec(sql, (err, row) => { + + const handler = (err, row) => { if (!err) { - //console.dir(row[0].numrows); + if (typeof callback !== "function") { + this.resolve(row[0].numrows); + return; + } cb(err, row[0].numrows); } else { + if (typeof callback !== "function") { + this.reject(err); + return; + } cb(err, row); } - }); + } + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this), handler).promisify(); + this._exec(sql, handler); } get(table,cb,conn) { @@ -78,31 +90,34 @@ class QueryExec extends QueryBuilder { if (typeof table === 'function' && typeof cb !== 'function') { cb = table; } - else if (typeof table === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your 'get' call!"); - } const sql = this._get(table); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } get_where(table,where,cb) { if (typeof table !== 'string' && !Array.isArray(table)) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); + throw ERROR.FIRST_PARAM_OF_GET_WHERE_ERR; } if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + throw ERROR.SECOND_PARAM_OF_GET_WHERE_ERR; } - const sql = this._get_where(table,where); + const sql = this._get_where(table, where); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } insert(table, set, cb, ignore, suffix) { const sql = this._insert(table, set, ignore, suffix); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } insert_ignore(table, set, on_dupe, cb) { @@ -131,6 +146,8 @@ class QueryExec extends QueryBuilder { const sql = this._insert_batch(table, set); this.reset_query(sql); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } @@ -140,16 +157,15 @@ class QueryExec extends QueryBuilder { cb = where; where = null; } - else if (typeof where === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where).length === 0)) { where = null; } const sql = this._update(table,set,where); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } // TODO: Write this complicated-ass function @@ -159,41 +175,46 @@ class QueryExec extends QueryBuilder { cb = where; where = null; } - else if (typeof where === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where).length === 0)) { where = null; } - const sqls = this._update_batch(table,set,index,where); + const sqls = this._update_batch(table, set, index, where); const results = null; const errors = []; // Execute each batch of (at least) 100 - (function next_batch() { - const sql = sqls.shift(); - this.reset_query(sql); - - this._exec(sql, (err, res) => { - if (!err) { - if (null === results) { - results = res; + const handler = (resolve, reject) => { + (function next_batch() { + const sql = sqls.shift(); + this.reset_query(sql); + + this._exec(sql, (err, res) => { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; + errors.push(err); } - } else { - errors.push(err); - } - if (sqls.length > 0) { - setTimeout(next_batch,0); - } else { - return cb(errors, results); - } - }); - })(); + if (sqls.length > 0) { + setTimeout(next_batch,0); + } else { + return cb(errors, results); + } + }); + })(); + }; + + if (!cb || cb !== 'function') { + return new Promise(handler); + } else { + handler(); + } } delete(table, where, cb) { @@ -208,26 +229,27 @@ class QueryExec extends QueryBuilder { where = undefined; } - if (typeof cb !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - const sql = this._delete(table, where); - this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } empty_table(table, cb) { const sql = this._empty_table(table,cb); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } truncate(table, cb) { const sql = this._truncate(table,cb); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } } diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 9eaa4e9..45bbd68 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -1,4 +1,6 @@ const QueryBuilder = require('./query_builder.js'); +const ERROR = require("../QueryExecError"); +const WrapperPromise = require("../WrapperPromise"); // **************************************************************************** // QueryBuilder "Query Execution" methods. @@ -33,11 +35,12 @@ class QueryExec extends QueryBuilder { cb(err, results); }); } else { - throw new Error("No connection object available to the Query Exec Library!"); + throw ERROR.NO_CONN_OBJ_ERR; } } query(sql, cb) { + if (!cb || typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } @@ -49,46 +52,60 @@ class QueryExec extends QueryBuilder { const sql = this._count(table); this.reset_query(sql); - this._exec(sql, (err, row) => { + + const handler = (err, row) => { if (!err) { - //console.dir(row[0].numrows); + if (typeof callback !== "function") { + this.resolve(row[0].numrows); + return; + } cb(err, row[0].numrows); } else { + if (typeof callback !== "function") { + this.reject(err); + return; + } cb(err, row); } - }); + } + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this), handler).promisify(); + this._exec(sql, handler); } - get(table,cb,conn) { + get(table, cb, conn) { // The table parameter is optional, it could be the cb... if (typeof table === 'function' && typeof cb !== 'function') { cb = table; } - else if (typeof table === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your 'get' call!"); - } const sql = this._get(table); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } - get_where(table,where,cb) { + get_where(table, where, cb) { if (typeof table !== 'string' && !Array.isArray(table)) { - throw new Error("First parameter of get_where() must be a string or an array of strings."); + throw ERROR.FIRST_PARAM_OF_GET_WHERE_ERR; } if (Object.prototype.toString.call(where) !== Object.prototype.toString.call({})) { - throw new Error("Second parameter of get_where() must be an object with key:value pairs."); + throw ERROR.SECOND_PARAM_OF_GET_WHERE_ERR; } - const sql = this._get_where(table,where); + const sql = this._get_where(table, where); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } - insert(table,set,cb,ignore,suffix) { + insert(table, set, cb, ignore, suffix) { const sql = this._insert(table, set, ignore, suffix); this.reset_query(sql); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } @@ -97,54 +114,56 @@ class QueryExec extends QueryBuilder { cb = on_dupe; on_dupe = null; } + const sql = this._insert_ignore(table, set, on_dupe); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } insert_batch(table, set, ignore, on_dupe, cb) { if (typeof ignore === 'function') { cb = ignore; ignore = null; - } - else if (typeof on_dupe === 'function') { - cb = on_dupe; - on_dupe = null; } + else if (typeof on_dupe === 'function') { + cb = on_dupe; + on_dupe = null; + } + const sql = this._insert_batch(table, set, ignore, on_dupe); this.reset_query(sql); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } - update(table,set,where,cb) { + update(table, set, where, cb) { // The where parameter is optional, it could be the cb... if (typeof where === 'function' && typeof cb !== 'function') { cb = where; where = null; } - else if (typeof where === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your update call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where.length) === 0)) { where = null; } const sql = this._update(table, set, where); this.reset_query(sql); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); this._exec(sql, cb); } // TODO: Write this complicated-ass function - update_batch(table,set,index,where,cb) { + update_batch(table, set, index, where, cb) { // The where parameter is optional, it could be the cb... if (typeof where === 'function' && typeof cb !== 'function') { cb = where; where = null; } - else if (typeof where === 'undefined' && typeof cb !== 'function') { - throw new Error("No cb function has been provided in your update_batch call!"); - } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && where.length == 0)) { + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where).length === 0)) { where = null; } @@ -153,29 +172,44 @@ class QueryExec extends QueryBuilder { const errors = []; // Execute each batch of (at least) 100 - (function next_batch() { - const sql = sqls.shift(); - this.reset_query(sql); - - this._exec(sql, (err, res) => { - if (!err) { - if (null === results) { - results = res; + const handler = (resolve, reject) => { + (function next_batch() { + const sql = sqls.shift(); + this.reset_query(sql); + + this._exec(sql, (err, res) => { + if (!err) { + if (null === results) { + results = res; + } else { + results.affected_rows += res.affected_rows; + results.changed_rows += res.changed_rows; + } } else { - results.affected_rows += res.affected_rows; - results.changed_rows += res.changed_rows; + errors.push(err); } - } else { - errors.push(err); - } - if (sqls.length > 0) { - setTimeout(next_batch,0); - } else { - return cb(errors, results); - } - }); - })(); + if (sqls.length > 0) { + setTimeout(next_batch, 0); + } else { + if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) { + if (Array.isArray(errors) && errors.length > 0) return reject(new Error(errors.join("\n\n"))); + return resolve(results); + } else if (cb && typeof cb === 'function') { + return cb(errors, results); + } else { + throw ERRORS.NO_VALID_RESULTS_HANDLER; + } + } + }); + })(); + }; + + if (!cb || cb !== 'function') { + return new Promise(handler); + } else { + handler(); + } } delete(table, where, cb) { @@ -190,26 +224,27 @@ class QueryExec extends QueryBuilder { where = undefined; } - if (typeof cb !== 'function') { - throw new Error("delete(): No callback function has been provided!"); - } - const sql = this._delete(table, where); - this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } empty_table(table, cb) { - const sql = this._empty_table(table,cb); + const sql = this._empty_table(table, cb); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } truncate(table, cb) { - const sql = this._truncate(table,cb); + const sql = this._truncate(table, cb); this.reset_query(sql); - this._exec(sql,cb); + + if (typeof cb !== "function") return new WrapperPromise(sql, this._exec.bind(this)).promisify(); + this._exec(sql, cb); } } From 5c78a0b0e90512e3299966826ab89c74f6f08fed Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 10:48:16 -0500 Subject: [PATCH 192/200] Fixed minor typo. --- drivers/mysql/query_exec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mysql/query_exec.js b/drivers/mysql/query_exec.js index 45bbd68..a6c23f1 100755 --- a/drivers/mysql/query_exec.js +++ b/drivers/mysql/query_exec.js @@ -145,7 +145,7 @@ class QueryExec extends QueryBuilder { cb = where; where = null; } - else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where.length) === 0)) { + else if (typeof where === 'undefined' || where === false || (where !== null && typeof where === 'object' && Object.keys(where).length === 0)) { where = null; } From 1877f5464220fd9fb11f793f8dfe9e9a4e5f9163 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 12:16:39 -0500 Subject: [PATCH 193/200] Added promise functionality to connection and disconnect methods of MSSQL and MySQL adapters. --- drivers/mssql/adapters/pool.js | 45 +++++++++++++++++++++++++------- drivers/mssql/adapters/single.js | 25 +++++++++++++++--- drivers/mysql/adapters/pool.js | 45 +++++++++++++++++++++++++------- drivers/mysql/adapters/single.js | 30 ++++++++++++++++++--- 4 files changed, 120 insertions(+), 25 deletions(-) diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js index 7aa5fff..aae373f 100755 --- a/drivers/mssql/adapters/pool.js +++ b/drivers/mssql/adapters/pool.js @@ -30,21 +30,48 @@ class Pool extends Adapter { throw new Error(error_msg); } - this._pool.acquire((err, connection) => { - if (err) throw err; - const adapter = new Single(this._original_settings, { - pool: { - pool: this._pool, - connection: connection, + const handler = (reject, resolve) => { + this._pool.acquire((err, connection) => { + if (err) { + if (cb && typeof cb === 'function') return cb(err, null); + else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); + throw err; } + + const adapter = new Single(this._original_settings, { + pool: { + pool: this._pool, + connection, + } + }); + + if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return resolve(adapter) + else if (cb && typeof cb === 'function') return cb(err, adapter); + throw ERRORS.NO_VALID_RESULTS_HANDLER; }); + } - cb(adapter); - }); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise(handler); + } else { + handler(); + } } disconnect(cb) { - this._pool.drain(cb); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise((resolve, reject) => { + this._pool.drain((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } else { + this._pool.drain(cb); + } } } diff --git a/drivers/mssql/adapters/single.js b/drivers/mssql/adapters/single.js index 0ac5ad5..a591b43 100755 --- a/drivers/mssql/adapters/single.js +++ b/drivers/mssql/adapters/single.js @@ -35,8 +35,18 @@ class Single extends Adapter { } connect(cb) { - if (this._connection) return this._connection; - this._connection = this.sql_connection.connect(cb); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise((resolve, reject) => { + if (this._connection) return resolve(); + this._connection = this.sql_connection.connect((err) => { + if (err) return reject(err); + resolve(); + }); + }); + } else { + if (this._connection) return cb(); + this._connection = this.sql_connection.connect(cb); + } } connection() { @@ -47,13 +57,20 @@ class Single extends Adapter { return tsqlstring.escapeId(str); } - disconnect(callback) { + disconnect(cb) { if (this.pool) { this.pool.drain(); } else { this._connection.close(); } - if (callback && typeof callback === 'function') callback(null); + + if (cb && typeof cb === 'function') { + cb(null); + } else { + return new Promise((resolve, reject) => { + resolve(); + }); + } } release() { diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js index a3653d4..25511ef 100755 --- a/drivers/mysql/adapters/pool.js +++ b/drivers/mysql/adapters/pool.js @@ -38,21 +38,48 @@ class Pool extends Adapter { throw new Error(error_msg); } - this._pool.getConnection((err, connection) => { - if (err) throw err; - const adapter = new Single(this._original_settings, { - pool: { - pool: this._pool, - connection: connection + const handler = (resolve, reject) => { + this._pool.getConnection((err, connection) => { + if (err) { + if (cb && typeof cb === 'function') return cb(err, null); + else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); + throw err; } + + const adapter = new Single(this._original_settings, { + pool: { + pool: this._pool, + connection + } + }); + + if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return resolve(adapter) + else if (cb && typeof cb === 'function') return cb(err, adapter); + throw ERRORS.NO_VALID_RESULTS_HANDLER; }); + } - cb(adapter); - }); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise(handler); + } else { + handler(); + } } disconnect(cb) { - this._pool.end(cb); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise((resolve, reject) => { + this._pool.end((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } else { + this._pool.end(cb); + } } } diff --git a/drivers/mysql/adapters/single.js b/drivers/mysql/adapters/single.js index 61d8b63..6db44e8 100755 --- a/drivers/mysql/adapters/single.js +++ b/drivers/mysql/adapters/single.js @@ -31,7 +31,19 @@ class Single extends Adapter { } connect(cb) { - return this._connection.connect(cb); + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise((resolve, reject) => { + return this._connection.connect((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } else { + this._connection.connect(cb); + } } connection() { @@ -42,8 +54,20 @@ class Single extends Adapter { return this._connection.escapeId(str); } - disconnect(callback) { - return this._connection.end(callback); + disconnect(cb) { + if (!cb || (cb && typeof cb !== 'function')) { + return new Promise((resolve, reject) => { + this._connection.end((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } else { + this._connection.end(cb); + } } release() { From 89194f251d5fcfa4b32070b0af1ac312d3e48f3a Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 12:26:06 -0500 Subject: [PATCH 194/200] Fixed issue with context in get_connection method on Pool adatpers. --- drivers/mssql/adapters/pool.js | 7 ++++--- drivers/mysql/adapters/pool.js | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js index aae373f..a0febd6 100755 --- a/drivers/mssql/adapters/pool.js +++ b/drivers/mssql/adapters/pool.js @@ -30,17 +30,18 @@ class Pool extends Adapter { throw new Error(error_msg); } + const self = this; const handler = (reject, resolve) => { - this._pool.acquire((err, connection) => { + self._pool.acquire((err, connection) => { if (err) { if (cb && typeof cb === 'function') return cb(err, null); else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); throw err; } - const adapter = new Single(this._original_settings, { + const adapter = new Single(self._original_settings, { pool: { - pool: this._pool, + pool: self._pool, connection, } }); diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js index 25511ef..86384df 100755 --- a/drivers/mysql/adapters/pool.js +++ b/drivers/mysql/adapters/pool.js @@ -38,17 +38,18 @@ class Pool extends Adapter { throw new Error(error_msg); } + const self = this; const handler = (resolve, reject) => { - this._pool.getConnection((err, connection) => { + self._pool.getConnection((err, connection) => { if (err) { if (cb && typeof cb === 'function') return cb(err, null); else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); throw err; } - const adapter = new Single(this._original_settings, { + const adapter = new Single(self._original_settings, { pool: { - pool: this._pool, + pool: self._pool, connection } }); From e13ec911e94dcdc4aead209ab7705d66c84a9389 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 12:42:11 -0500 Subject: [PATCH 195/200] Fixed issue with error handling on get_connection method on Pool adapters. --- drivers/mssql/adapters/pool.js | 8 ++------ drivers/mysql/adapters/pool.js | 10 +++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/mssql/adapters/pool.js b/drivers/mssql/adapters/pool.js index a0febd6..caf8010 100755 --- a/drivers/mssql/adapters/pool.js +++ b/drivers/mssql/adapters/pool.js @@ -33,11 +33,7 @@ class Pool extends Adapter { const self = this; const handler = (reject, resolve) => { self._pool.acquire((err, connection) => { - if (err) { - if (cb && typeof cb === 'function') return cb(err, null); - else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); - throw err; - } + if (err) throw err; const adapter = new Single(self._original_settings, { pool: { @@ -47,7 +43,7 @@ class Pool extends Adapter { }); if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return resolve(adapter) - else if (cb && typeof cb === 'function') return cb(err, adapter); + else if (cb && typeof cb === 'function') return cb(adapter); throw ERRORS.NO_VALID_RESULTS_HANDLER; }); } diff --git a/drivers/mysql/adapters/pool.js b/drivers/mysql/adapters/pool.js index 86384df..2fee644 100755 --- a/drivers/mysql/adapters/pool.js +++ b/drivers/mysql/adapters/pool.js @@ -41,11 +41,7 @@ class Pool extends Adapter { const self = this; const handler = (resolve, reject) => { self._pool.getConnection((err, connection) => { - if (err) { - if (cb && typeof cb === 'function') return cb(err, null); - else if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return reject(err); - throw err; - } + if (err) throw err; const adapter = new Single(self._original_settings, { pool: { @@ -54,8 +50,8 @@ class Pool extends Adapter { } }); - if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return resolve(adapter) - else if (cb && typeof cb === 'function') return cb(err, adapter); + if ((!cb || typeof cb !== 'function') && (typeof resolve === 'function' && typeof reject === 'function')) return resolve(adapter); + else if (cb && typeof cb === 'function') return cb(adapter); throw ERRORS.NO_VALID_RESULTS_HANDLER; }); } From 7a17ec9366b6f53bb1354ced24e8fd2874ebd612 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 13:27:41 -0500 Subject: [PATCH 196/200] Testing promise API for querying with MySQL. --- test/mysql/04-tests-query-promise.js | 106 +++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 test/mysql/04-tests-query-promise.js diff --git a/test/mysql/04-tests-query-promise.js b/test/mysql/04-tests-query-promise.js new file mode 100755 index 0000000..a1de804 --- /dev/null +++ b/test/mysql/04-tests-query-promise.js @@ -0,0 +1,106 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../index.js'); +const settings = require('../configs').mysql; +const driver = 'mysql'; + +const check = (done, f) => { + try { + f(); + done(); + } catch(e) { + done(e); + } +}; + +describe('MySQL: Query Responses', () => { + it('should allow us to execute a simple SELECT query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.query("select * from `cities` where `city` like 'Z%' and `state_code` = 'FL'"); + + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + } catch (err) { + throw err; + } finally { + + } + }); + it('should have a non-empty array of objects as a response value when there should be results from a SELECT query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.like('city', 'Z', 'right').get_where('cities', { state_code: 'FL' }); + + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + } catch (err) { + throw err; + } finally { + + } + }); + it('should have a javascript Standard Error object when running an invalid query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + + await qb.connect(); + + try { + await qb.query("select * = 'FL'"); + } catch (err) { + expect(err, 'there should be an error when the query is invalid').to.be.instanceof(Error); + } finally { + + } + }); + it('should respond with an object explaining the results of an INSERT query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.insert('cities', { city: 'Node QueryBuilder', state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + } catch (err) { + throw err; + } finally { + + } + }); + it('should respond with an object explaining the results of an UPDATE query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.update('cities', { city: 'Node Query Builder' }, { state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 1').to.gte(1); + } catch (err) { + throw err; + } finally { + + } + }); + it('should respond with an object explaining the results of a DELETE query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.delete('cities', { state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + } catch (err) { + throw err; + } finally { + + } + }); +}); From bcac545e7a3a1ef33074734b8f39f53f8ab892de Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 13:38:54 -0500 Subject: [PATCH 197/200] Testing promise API for querying with MSSQL. --- test/mssql/04-tests-query-promise.js | 110 +++++++++++++++++++++++++++ test/mysql/04-tests-query-promise.js | 2 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100755 test/mssql/04-tests-query-promise.js diff --git a/test/mssql/04-tests-query-promise.js b/test/mssql/04-tests-query-promise.js new file mode 100755 index 0000000..116b918 --- /dev/null +++ b/test/mssql/04-tests-query-promise.js @@ -0,0 +1,110 @@ +const should = require('chai').should(); +const expect = require('chai').expect; +const QueryBuilder = require('../../index.js'); +const settings = require('../configs').mssql; +const driver = 'mssql'; + +const check = (done, f) => { + try { + f(); + done(); + } catch (e) { + done(e); + } +}; + +describe('MSSQL: Query Responses', () => { + it('should allow us to execute a simple SELECT query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.like('city', 'Z', 'right').get_where('cities', { state_code: 'FL' }); + + expect(res, 'results should not be empty').to.not.be.empty; + expect(res, 'should have 3 results').to.have.length(3); + + const expected_result = [{ city: 'Zellwood', state_code: 'FL' }, { city: 'Zephyrhills', state_code: 'FL' }, { city: 'Zolfo Springs', state_code: 'FL' }]; + expect(res, 'should be just an array of objects representing the desired rows and columns').to.eql(expected_result); + } catch (err) { + throw err; + } finally { + + } + }); + it('should have a javascript Standard Error object when running an invalid query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + await qb.query("select * = 'FL'"); + } catch (err) { + expect(err, 'there should be an error when the query is invalid').to.be.instanceof(Error); + } finally { + + } + }); + it('should respond with an object explaining the results of an INSERT query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.insert('cities', { city: 'Node QueryBuilder', state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + } catch (err) { + throw err; + } finally { + + } + }); + it('should respond with the requested IDs (from the `returning()` method) after insert', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + + try { + await qb.connect(); + const res = await qb.returning(['city', 'state_code']).insert('cities', { city: 'Node QB Returns', state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be the values of the ids requested').to.not.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.eql(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + } catch (err) { + throw err; + } finally { + + } + }); + it('should respond with an object explaining the results of an UPDATE query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.update('cities', { city: 'Node Query Builder' }, { state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 1').to.gte(1); + } catch (err) { + throw err; + } finally { + + } + }); + it('should respond with an object explaining the results of a DELETE query', async () => { + const qb = new QueryBuilder(Object.assign({}, settings), driver); + try { + await qb.connect(); + const res = await qb.delete('cities', { state_code: 'NQ' }); + + expect(res, 'results should be an object with all the expected keys').to.be.an('object').that.includes.all.keys('insert_id', 'affected_rows', 'changed_rows'); + expect(res.insert_id, 'insert id should be null').to.be.null; + expect(res.affected_rows, 'affected_rows should be 1').to.gte(1); + expect(res.changed_rows, 'changed_rows should be 0').to.eql(0); + } catch (err) { + throw err; + } finally { + + } + }); +}); diff --git a/test/mysql/04-tests-query-promise.js b/test/mysql/04-tests-query-promise.js index a1de804..f45205b 100755 --- a/test/mysql/04-tests-query-promise.js +++ b/test/mysql/04-tests-query-promise.js @@ -13,7 +13,7 @@ const check = (done, f) => { } }; -describe('MySQL: Query Responses', () => { +describe('MySQL: Query Promises', () => { it('should allow us to execute a simple SELECT query', async () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); try { From ce4bb5552f6e5d00f53dc0741020daee5ab15bbc Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 13:44:39 -0500 Subject: [PATCH 198/200] Included the required dependencies for MSSQL adapter Promise support. --- drivers/mssql/query_exec.js | 2 ++ test/mssql/04-tests-query-promise.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mssql/query_exec.js b/drivers/mssql/query_exec.js index c7e51ff..a6185b7 100755 --- a/drivers/mssql/query_exec.js +++ b/drivers/mssql/query_exec.js @@ -1,5 +1,7 @@ const Request = require('tedious').Request; const QueryBuilder = require('./query_builder.js'); +const ERROR = require("../QueryExecError"); +const WrapperPromise = require("../WrapperPromise"); // **************************************************************************** // QueryBuilder "Query Execution" methods. diff --git a/test/mssql/04-tests-query-promise.js b/test/mssql/04-tests-query-promise.js index 116b918..e03f0f2 100755 --- a/test/mssql/04-tests-query-promise.js +++ b/test/mssql/04-tests-query-promise.js @@ -13,7 +13,7 @@ const check = (done, f) => { } }; -describe('MSSQL: Query Responses', () => { +describe('MSSQL: Query Promises', () => { it('should allow us to execute a simple SELECT query', async () => { const qb = new QueryBuilder(Object.assign({}, settings), driver); try { From 5e9fbee121eb3c8d9a81e9d7a226efb298b51aa8 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 13:54:34 -0500 Subject: [PATCH 199/200] 2.1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b7eb99e..dd2431a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.2", + "version": "2.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 75792a6..9a0fc15 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-querybuilder", - "version": "2.0.2", + "version": "2.1.0", "author": "Kyle Farris ", "description": "Modeled after Codeigniter's QueryBuilder. Build and execute queries in a safe and database-agnostic way.", "keywords": [ From 59598c184ea41f844eb49f033b8a282798daced9 Mon Sep 17 00:00:00 2001 From: Rebecca Long Date: Fri, 8 Nov 2019 16:15:16 -0500 Subject: [PATCH 200/200] Major updates for the README and HISTORY files to reflect updates made in 2.1.0. --- HISTORY.md | 13 ++ README.md | 474 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 310 insertions(+), 177 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f475210..7367372 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,19 @@ This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. +## v2.1.1 (2019-11-08) + +* Updated documentation to reflect new Promise API support. + +## v2.1.0 (2019-11-08) + +* Added Promise support. Closes [#18](https://github.com/kylefarris/node-querybuilder/pull/52). Thanks for the contribution @AbhijetPokhrel! +* Added new tests to verify that Promise support is working. + +## v2.0.2 (2019-10-28) + +* Finally got testing working on Travis CI for MySQL and MSSQL. + ## v2.0.0 (2018-06-15) ### Breaking Changes diff --git a/README.md b/README.md index d483c71..ab35934 100755 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Licensed under the GPL license and MIT: - [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) # Quick Example -This quick example shows how to connect to and asynchronously query a MySQL database using a single connection. +This quick example shows how to connect to and asynchronously query a MySQL database using a pooled connection. ```javascript const QueryBuilder = require('node-querybuilder'); @@ -78,22 +78,58 @@ const settings = { user: 'myuser', password: 'MyP@ssw0rd' }; -const qb = new QueryBuilder(settings, 'mysql', 'single'); +const pool = new QueryBuilder(settings, 'mysql', 'pool'); -qb.select('name', 'position') - .where({type: 'rocky', 'diameter <': 12000}) - .get('planets', (err, response) => { - qb.disconnect(); +pool.get_connection(qb => { + qb.select('name', 'position') + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets', (err, response) => { + qb.disconnect(); + + if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); + + // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 + console.log("Query Ran: " + qb.last_query()); + + // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] + console.log("Results:", response); + } + ); +}); +``` + +Anywhere a callback is used in the examples below, you can substitute for a Promise (or async/await). Here's the same code above in Promise format: + +```javascript +const QueryBuilder = require('node-querybuilder'); +const settings = { + host: 'localhost', + database: 'mydatabase', + user: 'myuser', + password: 'MyP@ssw0rd' +}; +const pool = new QueryBuilder(settings, 'mysql', 'pool'); - if (err) return console.error("Uh oh! Couldn't get results: " + err.msg); +async function getPlanets() { + try { + const qb = await pool.get_connection(); + const response = await qb.select('name', 'position') + .where({type: 'rocky', 'diameter <': 12000}) + .get('planets'); // SELECT `name`, `position` FROM `planets` WHERE `type` = 'rocky' AND `diameter` < 12000 console.log("Query Ran: " + qb.last_query()); // [{name: 'Mercury', position: 1}, {name: 'Mars', position: 4}] console.log("Results:", response); + } catch (err) { + return console.error("Uh oh! Couldn't get results: " + err.msg); + } finally { + qb.disconnect(); } -); +} + +getPlanets(); ``` # Connecting to Your Database @@ -286,10 +322,14 @@ The fields provided to this method will be automatically escaped by the database ```javascript // SELECT * FROM galaxies -qb.select('*').get('foo', callback); +qb.select('*').get('foo', (err, results) => {}); // Easier and same result: -qb.get('foo', callback); +qb.get('foo', (err, results) => {}); + +// Async/Await version: +const results = await qb.get('foo'); + ``` An array of field names: @@ -1119,10 +1159,19 @@ API Method | SQL Command | MySQL | MSSQL | Oracle [empty_table()](#empty_table) | DELETE | ✓ | ✓ | | | | ### What are "Execution Methods"?? -Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. Both (if provided) will be supplied to the callback function. +Execution methods are the end-of-chain methods in the QueryBuilder library. Once these methods are called, all the chainable methods you've called up until this point will be compiled into a query string and sent to the driver's `query()` method. At this point, the QueryBuilder will be reset and ready to build a new query. The database driver will respond with results depending on the type of query being executed or with an error message. ### Handling Error Messages and Results -The final parameter of every execution method will be a callback function. For `single` connection setups, the parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a JavaScript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. + +#### Callback Style +The final parameter of every execution method will be a callback function. If a callback is supplied to that final parameter, a Promise object *WILL NOT* be returned. + +The parameters for the callback are in the `node.js` standard `(err, response)` format. If the driver throws an error, a JavaScript `Standard Error` object will be passed into the `err` parameter. The `response` parameter can be supplied with an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. + +#### Promise Style +If a callback function is not supplied to the final parameter of execution methods, a Promise will be returned. If the driver throws an error a JavaScript `Standard Error` object will be sent to the the Promise's `reject` callback parameter. The `response` will be sent to the Promise's `resolve` callback parameter. The `response` can be an array of result rows (`.get()` & `.get_where()`), an integer (`.count()`), or a response object containing rows effected, last insert id, etc... in any other scenario. + +Obviously, `async/await` is supported through this style. ### Response Format Examples @@ -1135,75 +1184,101 @@ insert_batch(), update_batch() | Example: `{insert_id: 579, affected_rows: 1, ch **NOTE** -When using the [returning()](#returning) method with compatible drivers, the `insert_id` property of the response object will be an array of objects containing key value pairs representing the requested "returned" columns along with their values. +When using the [returning()](#returning) method with compatible drivers (`mssql`), the `insert_id` property of the response object will be an array of objects containing key value pairs representing the requested "returned" columns along with their values. Example: ```javascript -qb.returning('id').insert('users', {firstName: 'John', lastName: 'Smith'}, (err, results) => { - // Results: {insert_id: [{id: 12345}], affected_rows: 1, changed_rows: 0} -}); +// results: {insert_id: [{id: 12345}], affected_rows: 1, changed_rows: 0} +const results = await qb.returning('id').insert('users', {firstName: 'John', lastName: 'Smith'}); ``` #### Callback Example ```javascript -const callback = (err, response) => { +pool.get_connection(qb => qb.get('foo', (err, response) => { qb.release(); - if (err) { + if (err) return console.error(err); + response.forEach((v) => /* Do Something */); +})); +``` + +#### Promise Example + +**Note:** Don't do it this way. It's silly, verbose, and out-dated. + +```javascript +pool.get_connection().then(qb => { + const result = qb.get('foo'); + qb.release(); + return result; +}).then(response => { + response.forEach((v) => /* Do Something */); + return response; +}).catch(err =>{ + return console.error(err); +}); +``` + +#### Async/Await Example + +```javascript +async function getFoo() { + let qb; + try { + qb = await pool.get_connection(); + const response = qb.get('foo'); + response.forEach((v) => /* Do Something */); + } catch (err) { console.error(err); + } finally { + if (qb) qb.release(); } - else { - for (const i in response) { - const row = response[i]; - /* - Do something with each row... - */ - } - } -}; -pool.get_connection(qb => { - qb.get('foo', callback); -}); +} + +getFoo(); ``` #### Using the Same Connection Pool Connection for Successive Calls +This is an ideal scenario for the async/await pattern. + ```javascript const pool = new require('node-querybuilder')(settings,'mysql','pool'); -const data = {username: 'jsmith', first_name: 'John', last_name: 'Smith'}; - -pool.get_connection(qb => { - qb.insert('employees', data, (err, res) => { - if (err) { - console.error(err); - } - else { - if (res.affected_rows > 0) { - const insert_id = res.insert_id; - qb.get_where('employees', {id: insert_id}, (err, res) => { - qb.release(); - console.dir(res); - }); - } - else { - console.error("New user was not added to database!"); - } +const data = {first_name: 'John', last_name: 'Smith'}; + +async function addUser() { + let qb; + try { + qb = await pool.get_connection(); + const results = await qb.insert('users', data); + + if (results.affected_rows === 1) { + const user = await qb.get_where('users', {id: res.insert_id}); + console.log('New User: ', user); + } else { + throw new Error("New user was not added to database!"); } - }); -}); + } catch (err) { + console.error(err); + } finally { + if (qb) qb.release(); + } +} + +updateUser(); ``` -------------------------------------------------------------------------------- -### .query(query_string, callback) +### .query(query_string[, callback]) -Parameter | Type | Default | Description -:----------- | :------- | :------- | :--------------------------------------------- -query_string | String | Required | Query to send directly to your database driver -callback | Function | Required | What to do when the driver has responded +Parameter | Type | Default | Description +:----------- | :------- | :-------- | :--------------------------------------------- +query_string | String | Required | Query to send directly to your database driver +callback | Function | undefined | (optional) What to do when the driver has responded *****This method bypasses the entire QueryBuilder portion of this module***** is simply uses your database driver's native querying method. You should be cautious when using this as none of this module's security and escaping functionality will be utilized. @@ -1216,6 +1291,7 @@ const sql = qb.select(['f.foo', 'b.bar']) .from('foo f') .join('bar b', 'b.foo_id=f.id', 'left') .get_compiled_select(); + qb.query("CREATE VIEW `foobar` AS " + sql, callback); ``` @@ -1223,18 +1299,18 @@ qb.query("CREATE VIEW `foobar` AS " + sql, callback); -### .get([table,]callback) +### .get([table[,callback]]) -Parameter | Type | Default | Description -:-------- | :------- | :-------- | :------------------------------------------------------------ -table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. -callback | Function | Required | What to do when the driver has responded +Parameter | Type | Default | Description +:-------- | :------- | :--------- | :------------------------------------------------------------ +table | String | undefined | (optional) Used to avoid having to call `.from()` seperately. +callback | Function | undefined | (optional) What to do when the driver has responded This method is used when running queries that might respond with rows of data (namely, "SELECT" statements...). You can pass a table name as the first parameter to avoid having to call [.from()](#from) separately. If the table name is omitted, and the first parameter is a callback function, there will be no need to pass a callback function into the second parameter. -**Type of Response Sent to Callback** +**Response Type** -Array of rows +Array of rows/records **Examples** @@ -1252,12 +1328,12 @@ If you already have the table added to the query: qb.from('galaxies').get(callback); ``` -Just a more-complicated example for the sake of it (note: using connection pool): +Just a more-complicated example for the sake of it: ```javascript /** * SELECT - * `g`.`name`, + * `g`.`name`, * `g`.`diameter`, * `g`.`type_id`, * `gt`.`name` AS `type`, @@ -1277,16 +1353,12 @@ qb.limit(10) .join('stars s', 's.galaxy_id=g.id', 'left') .group_by('g.id') .order_by('g.name', 'asc') - .get((err, response, conn) => { - conn.release(); + .get((err, response) => { if (err) return console.error(err); - for (const i in response) { - const row = response[i]; - console.log("The " + row.name + " is a " + row.diameter - + " lightyear-wide " + row.type + " galaxy with " - + row.num_stars + " stars."); - } + response.forEach(row => { + console.log(`The ${row.name} is a ${row.diameter} lightyear-wide ${row.type} galaxy with ${row.num_stars} stars.`); + }); }); ``` @@ -1294,17 +1366,17 @@ qb.limit(10) -### .get_where(table, where, callback) +### .get_where(table, where[, callback]) -Parameter | Type | Default | Description -:-------- | :-------------- | :------- | :------------------------------------------------- -table | String or Array | Required | Used to avoid having to call `.from()` separately. -where | Object | Required | Used to avoid having to call `.where()` separately -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :-------------- | :-------- | :------------------------------------------------- +table | String or Array | Required | Used to avoid having to call `.from()` separately. +where | Object | Required | Used to avoid having to call `.where()` separately +callback | Function | undefined | (optional) What to do when the driver has responded. This method is basically the same as the `.get()` method except that if offers an additional shortcut parameter to provide a list of filters (`{field_name:value}`) to limit the results by (effectively a shortcut to avoid calling `.where()` separately). The other difference is that _all_ parameters are required and they must be in the proper order. -**Type of Response Sent to Callback** +**Response Type** Array of objects representing the result rows. @@ -1328,18 +1400,18 @@ qb.where('num_stars >', 100000000).get_where('galaxies', {galaxy_type_id: 3}, ca -### .count([table,]callback) +### .count([[table[, callback]]) -Parameter | Type | Default | Description -:-------- | :------- | :-------- | :------------------------------------------------------------ -table | String | undefined | (optional) Used to avoid having to call `.from()` separately. -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :------- | :--------- | :------------------------------------------------------------ +table | String | undefined | (optional) Used to avoid having to call `.from()` separately. +callback | Function | undefined | (optional) What to do when the driver has responded. This method is used to determine the total number of results that a query would return without actually returning the entire resultset back to this module. Obviously, you could simply execute the same query with `.get()` and then check the `length` property of the response array, but, that would take significantly more time and memory for very large resultsets. The field in the resultset will always labeled be 'numrows'. -**Type of Response Sent to Callback** +**Response Type** Integer @@ -1358,14 +1430,14 @@ qb.where('type', type).count('galaxies', (err, count) => { -### .update(table, data[,where], callback) +### .update(table, data[,where][, callback]) -Parameter | Type | Default | Description -:-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- -table | String | null | (suggested) The table/collection you'd like to update -data | Object | null | (suggested) The data to update (ex. `{field: value}`) -where | Object | null | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :---------------------------------------------------------------------------------------------------- +table | String | null | (suggested) The table/collection you'd like to update +data | Object | null | (suggested) The data to update (ex. `{field: value}`) +where | Object | null | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. +callback | Function | undefined | (optional) What to do when the driver has responded. This method is used to update a table (SQL) or collection (NoSQL) with new data. All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the number of records updated, and the number of changed rows... @@ -1373,7 +1445,7 @@ This method is used to update a table (SQL) or collection (NoSQL) with new data. The first and second parameters are not required but I do suggest you use them as your code will be much easier to read. If you choose not to use them, you will need to pass a "falsey" value to each... you can't simply skip them. My recommendation is to use `null`. The way you would supply these values without using this method would be through the `from()` method for the first parameter and the `set()` method for the second parameter. -**Type of Response Sent to Callback** +**Response Type** Object containing information about the results of the query. @@ -1430,15 +1502,15 @@ qb.where('id', 42) -### .update_batch(table, dataset, index[,where], callback) +### .update_batch(table, dataset, index[,where][, callback]) -Parameter | Type | Default | Description -:-------- | :------- | :------- | :---------------------------------------------------------------------------------------------------- -table | String | Required | The table/collection you'd like to insert into -dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) -index | String | Required | Name of the key in each data object that represents a `where` clause. -where | Object | NULL | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :---------------------------------------------------------------------------------------------------- +table | String | Required | The table/collection you'd like to insert into +dataset | Array | Required | An array of data (rows) to update (ex. `[{id: 3, field: value}, {id: 4, field: val}]`) +index | String | Required | Name of the key in each data object that represents a `where` clause. +where | Object | NULL | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. +callback | Function | undefined | (optional) What to do when the driver has responded. This method is a somewhat-complex one and, when using transactional databases, a bit pointless. Nevertheless, this will allow you to update a batch of rows with one query which, in theory, should be faster than running multiple update queries. @@ -1494,19 +1566,19 @@ As you can see, in each `CASE` statement, the `key` and it's value are being use -### .insert(table, data[,ignore[,on_dupe]], callback) +### .insert(table, data[,ignore[,on_dupe]][, callback]) -Parameter | Type | Default | Description -:-------- | :------- | :-------- | :-------------------------------------------------------------------------------------------------------------- -table | String | Required | The table/collection you'd like to insert into -data | Object | Required | The data to insert (ex. `{field: value}`) -ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. -on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :------- | :--------- | :-------------------------------------------------------------------------------------------------------------- +table | String | Required | The table/collection you'd like to insert into +data | Object | Required | The data to insert (ex. `{field: value}`) +ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. +on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). +callback | Function | undefined | (optional) What to do when the driver has responded. -This method is used to insert new data into a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with information like the ID of the newly inserted item, the affected rows (should be 1), etc... +This method is used to insert new data into a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback (or the Promise resolution value) should receive a response object with information like the ID of the newly inserted item, the affected rows (should be 1), etc... -**Type of Response Sent to Callback** +**Response Type** Object containing information about the result of the query. @@ -1548,7 +1620,7 @@ app.post('/add_article', (req, res) => { -### .insert_batch(table, dataset[,ignore[,on_dupe]], callback) +### .insert_batch(table, dataset[,ignore[,on_dupe]][, callback]) Parameter | Type | Default | Description :-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ @@ -1556,11 +1628,11 @@ table | String | Required | The table/collection you'd like to delete rec dataset | Array | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. ignore | Boolean | false | (optional) If TRUE, generates IGNORE syntax for your driver if it's supported; ignored (haha) if not supported. on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). -callback | Function | Required | What to do when the driver has responded. +callback | Function | undefined | (optional) What to do when the driver has responded. The goal of this method is to speed the insertion of many rows. For instance, if you were insert 1,000 rows... Instead of making 1,000 queries to the server, you could just call `insert_batch()` and it would generate a single query to insert 1,000 rows. This is _much_ more efficient and less taxing on your app and database server. -**Type of Response Sent to Callback** +**Response Type** Object containing information about the result of the query. @@ -1588,18 +1660,18 @@ qb.insert_batch('db_engines', data, (err, res) => { -### .insert_ignore(table, data[,on_dupe], callback) +### .insert_ignore(table, data[,on_dupe][, callback]) Parameter | Type | Default | Description :-------- | :------- | :-------- | :------------------------------------------------------------------------------------------------------------------ table | String | Required | The table/collection you'd like to delete records from. data | Object | undefined | An array of objects containing the data you want to insert. Pass *can* pass an empty array if you want to be silly. on_dupe | String | undefined | (optional) Query suffix needed for generating an 'upsert' (ex. `ON DUPLICATE KEY UPDATE ...`). -callback | Function | Required | What to do when the driver has responded. +callback | Function | undefined | (optional) What to do when the driver has responded. This method is just a wrapper to the `insert()` method which passes `true` to the ignore parameter. The purpose of using `IGNORE` syntax, for the drivers that support it, is so that a row insertion will be skipped if it's an exact duplicate of another row in the database. Optionally, you can provide a 3rd parameter containing a query that will update specified keys in the case of a duplicate entry (instead of simply ignoring it). With the third parameter, you can create an 'upsert' of sorts. Without the third parameter, it's essentially just "ignoring" errors, or, rather, converting them to simple warnings. -**Type of Response Sent to Callback** +**Response Type** Object containing information about the result of the query. @@ -1658,19 +1730,19 @@ qb.insert_ignore('db_engines', data, 'ON DUPLICATE KEY UPDATE last_modified = NO -### .delete(table, where, callback) +### .delete(table, where[, callback]) Parameter | Type | Default | Description :-------- | :------- | :-------- | :---------------------------------------------------------------------------------------------------- table | String | Required | The table/collection you'd like to delete records from. where | Object | undefined | (optional) Used to avoid having to call `.where()` separately. Pass NULL if you don't want to use it. -callback | Function | Required | What to do when the driver has responded. +callback | Function | undefined | (optional) What to do when the driver has responded. This method is used to delete records from a table (SQL) or collection (NoSQL). All identifiers and values are escaped automatically when applicable. The response parameter of the callback should receive a response object with the number of affected rows. **NOTE:** If tables are added to the querybuilder query cache via the `from()` method, only first table in the array (the first added) will be used for this method. -**Type of Response Sent to Callback** +**Response Type** Object containing information about the result of the query. @@ -1684,25 +1756,23 @@ const app = express(); const settings = require('db.json'); const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); -app.post('/delete_comment/:id', (req, res) => { - const comment_id = req.params.id; - - pool.get_connection(qb => { - qb.get('comments', {id: id}, (err, res) => { - if (err) return console.error(err); - const article_id = res.article_id; - - qb.delete('comments', {id: id}, (err, res) => { - qb.release(); - if (err) return console.error(err); - - const page_data = { - num_removed: res.affected_rows, - } - return res.render('/article/' + article_id, page_data); - }); - }); - }); +app.post('/delete_comment/:id', async (req, res) => { + const id = req.params.id; + let qb; + + try { + qb = await pool.get_connection(); + const comment = qb.get('comments', {id}); + const comment_id = comment.id; + const results = await qb.delete('comments', {id: comment_id}); + const num_removed = res.affected_rows; + return res.render(`/article/${article_id}`, {num_removed}); + } catch (err) { + console.error(err); + return res.status(400).send('Something bad happened.'); + } finally { + if (qb) qb.release(); + } }); ``` @@ -1710,18 +1780,18 @@ app.post('/delete_comment/:id', (req, res) => { -### .truncate(table, callback) +### .truncate(table[, callback]) -Parameter | Type | Default | Description -:-------- | :------- | :------- | :------------------------------------------- -table | String | Required | The table/collection you'd like to truncate. -callback | Function | Required | What to do when the driver has responded. +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :------------------------------------------- +table | String | Required | The table/collection you'd like to truncate. +callback | Function | undefined | (optional) What to do when the driver has responded. For drivers that support it (MySQL, MSSQL), this method will utilize the `TRUNCATE` directive to empty a table of all it's data. The main difference between the `truncate()` method and the `empty_table()` method is that, when available, and when possible, truncating a table will reset your AUTO_INCREMENT counter back to zero. If you simply delete every row from a table, the next item inserted will just continue with the next highest ID from the deleted records. For drivers that don't support the truncate method, this will simply act as a wrapper to the [.empty_table()](#empty_table) method. -**Type of Response Sent to Callback** +**Response Type** Object containing information about the result of the query. @@ -1732,7 +1802,7 @@ const settings = require('db.json'); const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); /* - * Assume we have a table like this to start with... + * Assume we have a table (with auto-incrementing ID) like this to start with... * [ * { id: 1, name: 'Mary' }, * { id: 2, name: 'Jane' }, @@ -1740,27 +1810,31 @@ const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); * ]; */ -pool.get_connection(qb => { - qb.truncate('users', (err, res) => { - if (err) throw err; - qb.insert('users', {name: 'Bob'}, (err, res) => { - if (err) throw err; - qb.get_where('users', {id: res.insert_id}, (err, res) => { - qb.release(); - if (err) throw err; - // { id: 1, name: 'Bob' } (notice ID is 1) - console.dir(res); - }); - }); - }); -}); +async function deleteUser() { + let qb; + try { + qb = await pool.get_connection(); + await qb.truncate('users'); + await qb.insert('users', { name: 'Bob' }); + const results = await qb.get_where('users', { id: res.insert_id }); + + // { id: 1, name: 'Bob' } (notice ID is 1) + console.log('Results: ', results); + } catch (err) { + console.error(err); + } finally { + if (qb) qb.release(); + } +} + +deleteUser(); ``` -------------------------------------------------------------------------------- -### .empty_table(table, callback) +### .empty_table(table[, callback]) Parameter | Type | Default | Description :-------- | :------- | :------- | :------------------------------------------- @@ -1769,7 +1843,9 @@ callback | Function | Required | What to do when the driver has responded. This method will allow you to delete all records from a table/collection. -**Type of Response Sent to Callback** +**Note:** This will *not* reset your `AUTO_INCREMENT`ing primary key field. For that, please use the [.truncate()](#truncate) method where possible. + +**Response Type** Object containing information about the result of the query. @@ -1816,7 +1892,7 @@ API Method | MySQL | MSSQL | Oracle | [disconnect()](#disconnect) | ✓ | ✓ | | | | [escape()](#escape) | ✓ | ✓ | | | | [get_connection()](#get_connection) | ✓ | ✓ | | | | -[last_query()](#last_query) | ✓ | | | | | +[last_query()](#last_query) | ✓ | ✗ | | | | [release()](#release) | ✓ | ✓ | | | | [get_compiled_select()](#get_compiled_select) | ✓ | ✓ | | | | [get_compiled_insert()](#get_compiled_insert) | ✓ | ✓ | | | | @@ -1873,17 +1949,17 @@ const connection_settings = qb.connection_settings(); -### .disconnect(callback) +### .disconnect([callback]) -Parameter | Type | Default | Description -:-------- | :------- | :------- | :--------------------------------------------------------- -callback | Function | Required | What to do when the connection is fully terminated. +Parameter | Type | Default | Description +:-------- | :------- | :-------- | :--------------------------------------------------------- +callback | Function | undefined | (optional) What to do when the connection is fully terminated. -Disconnect from the server after your query is complete. You _must_ call this each time after your done querying the database! **NOTE:** You would _not_ use this when using a connection pool. This should only be called when working with single one-off connections. After disconnecting, all subsequent queries will fail unless you reconnect ([connect()](#connect)). +Disconnect from the server after your query is complete. You _must_ call this each time after your done querying the database! **NOTE:** You would _not_ use this when using a connection pool. This should only be called when working with single one-off connections (command-line scripts, for example). After disconnecting, all subsequent queries will fail unless you reconnect ([connect()](#connect)). **Examples** -Below is a contrived example that gets a list of all users in a users table where their username starts with a `|` character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not terminated until all the queries that needed to be executed have been executed. +Below is a contrived example that gets a list of all users in a users table where their username starts with a `|` (pipe) character. It then loops over each one and removes the `|` from the username and re-inserts it. Notice that the connection is not terminated until all the queries that needed to be executed have been executed. ```javascript const settings = require('db.json'); @@ -1919,12 +1995,33 @@ const settings = require('db.json'); const qb = new require('node-querybuilder')(settings, 'mysql'); qb.get_where('users', {username: 'foobar'}, (err, res) => { - qb.disconnect(); if (err) throw err; - console.log("Success: ", res); + qb.disconnect((err) => { // NOTE: an error here is unlikely + if (err) return console.error(err); + console.log("Success: ", res); + }); }); ``` +As with all functions in this library, it has a Promise-style verion as well. Simply don't supply the callback and a Promise will be returned. Here's an example: + +```javascript +const settings = require('db.json'); +const qb = new require('node-querybuilder')(settings, 'mysql'); + +async function getFooUsers() { + try { + const res = await qb.get_where('users', {username: 'foobar'}); + await qb.disconnect(); + console.log("Success: ", res); + } catch (err) { + if (err) console.error(err); + } +} + +getFooUsers(); +``` + -------------------------------------------------------------------------------- @@ -1963,15 +2060,15 @@ qb.query(sql, (err, res) => { -### .get_connection(callback) +### .get_connection([callback]) Parameter | Type | Default | Description :-------- | :------- | :------- | :--------------------------------------------------------- callback | Function | Required | What to do when the connection is retrieved (or not) from the pool. -Used to get a new connection from the connection pool or cluster pool. An instance of the QueryBuilder adapter for your specific connection will be passed to the callback. Make sure that your connection is [release](#release)d when you are done with it! +Used to get a new connection from the connection pool or cluster pool. An instance of the QueryBuilder adapter for your specific connection will be passed to the callback or Promise resolution. Make sure that your connection is [release](#release)d when you are done with it! -**Example** +**Callback Example** ```javascript const settings = require('db.json'); @@ -1980,11 +2077,34 @@ const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); pool.get_connection(qb => { qb.limit(10).get('users', (err, res) => { qb.release(); - // Do stuff with results or err + if (err) return console.error(err); + // Do stuff with results }); }); ``` +**Async/Await Example** + +```javascript +const settings = require('db.json'); +const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); + +async function getUsers() { + let qb; + try { + qb = await pool.get_connection(); + const res = await qb.limit(10).get('users'); + // Do stuff with results + } catch (err) { + console.error(err); + } finally { + if (qb) qb.release(); + } +} + +getUsers(); +``` + -------------------------------------------------------------------------------- @@ -2001,7 +2121,7 @@ const settings = require('db.json'); const pool = new require('node-querybuilder')(settings, 'mysql', 'pool'); pool.get_connection(qb => { const id = 4531; - qb.get('comments', {id: id}, (err, res) => { + qb.get('comments', {id}, (err, res) => { // SELECT * FROM `comments` WHERE `id` = 4531 console.log(qb.last_query()); qb.release(); @@ -2065,7 +2185,7 @@ pool.get_connection(qb => { ### SQL Compilation Methods These methods can be used to build a query string without having to execute it. This is a fantastic option if you want to use the querybuilder to simply build queries and display the resulting string or to send the compiled query string off to a driver/engine other than the one offered by `node-querybuilder`. -These are excellent educational tools and can be used like a SQL/NoSQL language Rosetta Stone of sorts. +These are also excellent educational tools and can be used like a SQL/NoSQL language Rosetta Stone of sorts. These methods are not asynchronous and, therefore, just return the compiled query string.