From 4724fab294d2277322306ed9df2957da1397d97c Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 9 Jul 2019 10:51:47 +0800
Subject: [PATCH 01/37] feat: union grammar, to resolve grammar conflicts. #2

---
 src/sqlParser.jison | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 40b0631..1550d71 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -89,6 +89,7 @@ SHARE                                                             return 'SHARE'
 MODE                                                              return 'MODE'
 OJ                                                                return 'OJ'
 LIMIT                                                             return 'LIMIT'
+UNION                                                             return 'UNION'
 
 ","                                                               return ','
 "="                                                               return '='
@@ -159,8 +160,32 @@ LIMIT                                                             return 'LIMIT'
 %% /* language grammar */
 
 main
-  : selectClause EOF { return {nodeType: 'Main', value: $1}; }
-  | selectClause ';' EOF { return {nodeType: 'Main', value: $1, hasSemicolon: true}; }
+  : selectClause semicolonOpt EOF { return {nodeType: 'Main', value: $1, hasSemicolon: $2}; }
+  | unionClause semicolonOpt EOF { return {nodeType: 'Main', value: $1, hasSemicolon: $2}; }
+  ;
+
+semicolonOpt
+  : ';' { $$ = true }
+  | { $$ = false }
+  ;
+
+unionClause
+  : unionClauseNotParenthesized { $$ = $1 }
+  | unionClauseParenthesized { $$ = $1 }
+  ;
+
+unionClauseParenthesized
+  : selectClauseParenthesized UNION distinctOpt selectClauseParenthesized order_by_opt limit_opt { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4, orderBy: $5, limit: $6 }; }
+  | selectClauseParenthesized UNION distinctOpt unionClauseParenthesized order_by_opt limit_opt { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4, orderBy: $5, limit: $6 } }
+  ;
+
+selectClauseParenthesized
+  : '(' selectClause ')' { $$ = { type: 'SelectParenthesized', value: $2 } }
+  ;
+
+unionClauseNotParenthesized
+  : selectClause UNION distinctOpt selectClause { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 } }
+  | selectClause UNION distinctOpt unionClauseNotParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 } }
   ;
 
 selectClause

From 2bbd726f37b299bbd8a20b4a4276457278cb9fb1 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 22 Jul 2019 12:11:24 +0800
Subject: [PATCH 02/37] feat: union supported

---
 src/sqlParser.jison |  8 ++++----
 src/stringify.js    | 19 +++++++++++++++++--
 test/main.test.js   | 23 ++++++++++++++++++++---
 3 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 1550d71..f322390 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -171,16 +171,16 @@ semicolonOpt
 
 unionClause
   : unionClauseNotParenthesized { $$ = $1 }
-  | unionClauseParenthesized { $$ = $1 }
+  | unionClauseParenthesized order_by_opt limit_opt { $$ = $1, $$.orderBy = $2, $$.limit = $3; }
   ;
 
 unionClauseParenthesized
-  : selectClauseParenthesized UNION distinctOpt selectClauseParenthesized order_by_opt limit_opt { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4, orderBy: $5, limit: $6 }; }
-  | selectClauseParenthesized UNION distinctOpt unionClauseParenthesized order_by_opt limit_opt { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4, orderBy: $5, limit: $6 } }
+  : selectClauseParenthesized UNION distinctOpt selectClauseParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 }; }
+  | selectClauseParenthesized UNION distinctOpt unionClauseParenthesized { $$ = { type: 'Union', left: $1, distinctOpt: $3, right: $4 }; }
   ;
 
 selectClauseParenthesized
-  : '(' selectClause ')' { $$ = { type: 'SelectParenthesized', value: $2 } }
+  : '(' selectClause ')' { $$ = { type: 'SelectParenthesized', value: $2 }; }
   ;
 
 unionClauseNotParenthesized
diff --git a/src/stringify.js b/src/stringify.js
index fd1d289..338479e 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -1,3 +1,5 @@
+import { timingSafeEqual } from "crypto";
+
 if (!sqlParser) {
   sqlParser = {};
 }
@@ -55,13 +57,13 @@ Sql.prototype.append = function(word, noPrefix, noSuffix) {
   }
 }
 Sql.prototype.travelMain = function(ast) {
-  this.travelSelect(ast.value);
+  this.travel(ast.value);
   if (ast.hasSemicolon) {
     this.append(';', true);
   }
 }
 Sql.prototype.travelSelect = function(ast) {
-  this.appendKeyword('select', true);
+  this.appendKeyword('select');
   if (ast.distinctOpt) {
     this.appendKeyword(ast.distinctOpt);
   }
@@ -537,3 +539,16 @@ Sql.prototype.travelTableFactor = function (ast) {
     this.travel(ast.indexHintOpt);
   }
 }
+Sql.prototype.travelUnion = function (ast) {
+  this.travel(ast.left);
+  this.appendKeyword('UNION');
+  if (ast.distinctOpt) {
+    this.appendKeyword(ast.distinctOpt)
+  }
+  this.travel(ast.right);
+}
+Sql.prototype.travelSelectParenthesized = function (ast) {
+  this.appendKeyword('(');
+  this.travel(ast.value);
+  this.appendKeyword(')');
+}
diff --git a/test/main.test.js b/test/main.test.js
index b4114ba..8908c22 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -5,9 +5,13 @@ const parser = require('../');
 
 const testParser = function (sql) {
   let firstAst = parser.parse(sql);
+  debug(JSON.stringify(firstAst, null, 2));
   let firstSql = parser.stringify(firstAst);
+  debug(firstSql);
   let secondAst = parser.parse(firstSql);
+  debug(parser.stringify(secondAst));
   let secondSql = parser.stringify(secondAst);
+  debug(JSON.stringify(secondAst, null, 2));
 
   if (firstSql !== secondSql) {
     console.log('firstSql', firstSql);
@@ -15,9 +19,6 @@ const testParser = function (sql) {
     throw 'err firstSql don\'t equals secondSql. ';
   }
 
-  debug(JSON.stringify(secondAst, null, 2));
-  debug(parser.stringify(secondAst));
-
   return secondAst;
 }
 
@@ -360,5 +361,21 @@ describe('select grammar support', function () {
     testParser('SELECT COUNT(*) AS total, a b, b as c, c/2 d, d & e an FROM b');
   });
 
+  it ('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+    testParser('select a from dual union select a from foo;');
+  });
+
+  it ('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+    testParser('(select a from dual) union (select a from foo) order by a desc limit 100, 100;');
+  });
+
+  it ('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+    testParser('(select a from dual) union all (select a from foo) order by a limit 100');
+  });
+
+  it ('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+    testParser('select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1');
+  });
+
 });
 

From 9e6128284a6f22e6e2bd1f95f98df472fc4eb258 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 22 Jul 2019 12:30:08 +0800
Subject: [PATCH 03/37] release: 1.0.8

---
 CHANGELOG    | 1 +
 package.json | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index 73b22bb..77f18d1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,2 +1,3 @@
 1.0.6 add MIT license.
 1.0.7 fix alias for identifier #16 , pr: #17
+1.0.8 support union grammar follow: https://dev.mysql.com/doc/refman/8.0/en/union.html
diff --git a/package.json b/package.json
index 07942c0..9f90f81 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.0.7",
+  "version": "1.0.8",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From 6bf4dc7e1f626e47fcdc87874e4e9516ae72db2f Mon Sep 17 00:00:00 2001
From: Michael Mior <mmior@cs.rit.edu>
Date: Tue, 23 Jul 2019 16:35:25 -0400
Subject: [PATCH 04/37] Remove unused import

---
 src/stringify.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/stringify.js b/src/stringify.js
index 338479e..c9eb348 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -1,5 +1,3 @@
-import { timingSafeEqual } from "crypto";
-
 if (!sqlParser) {
   sqlParser = {};
 }

From ff9b7ce20c31b115012fc3a34f1041e3fbee33ec Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 25 Jul 2019 10:05:09 +0800
Subject: [PATCH 05/37] release: v1.1.0

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

diff --git a/package.json b/package.json
index 9f90f81..b2c8cb7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.0.8",
+  "version": "1.1.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From 499bc189d83ca084026c606163c700a9ff974440 Mon Sep 17 00:00:00 2001
From: Caleb Rogers <rogersjcaleb@gmail.com>
Date: Tue, 30 Jul 2019 15:42:36 -0700
Subject: [PATCH 06/37] Change "refrence" to "reference" in all instances

"Refrence" is a misspelling of "reference."
---
 src/sqlParser.jison | 18 +++++++++---------
 src/stringify.js    |  8 ++++----
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index f322390..bc15230 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -10,7 +10,7 @@
 [-][-]\s.*\n                                                      /* skip sql comments */
 [#]\s.*\n                                                         /* skip sql comments */
 \s+                                                               /* skip whitespace */
-                                                                  
+
 [`][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[`]            return 'IDENTIFIER'
 [\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]*                   return 'IDENTIFIER'
 [\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]*                         return 'IDENTIFIER'
@@ -117,7 +117,7 @@ UNION                                                             return 'UNION'
 "{"                                                               return '{'
 "}"                                                               return '}'
 ";"                                                               return ';'
-                                                                 
+
 ['](\\.|[^'])*[']                                                 return 'STRING'
 ["](\\.|[^"])*["]                                                 return 'STRING'
 [0][x][0-9a-fA-F]+                                                return 'HEX_NUMERIC'
@@ -127,7 +127,7 @@ UNION                                                             return 'UNION'
 [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
 ['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'QUOTED_IDENTIFIER'
-                                                                 
+
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
 
@@ -189,7 +189,7 @@ unionClauseNotParenthesized
   ;
 
 selectClause
-  : SELECT 
+  : SELECT
       distinctOpt
       highPriorityOpt
       maxStateMentTimeOpt
@@ -228,7 +228,7 @@ selectClause
   ;
 
 distinctOpt
-  : ALL { $$ = $1 } 
+  : ALL { $$ = $1 }
   | DISTINCT { $$ = $1 }
   | DISTINCTROW { $$ = $1 }
   | { $$ = null }
@@ -361,7 +361,7 @@ simple_expr
   ;
 bit_expr
   : simple_expr { $$ = $1 }
-  | bit_expr '|' bit_expr { $$ = { type: 'BitExpression', operator: '|', left: $1, right: $3 } } 
+  | bit_expr '|' bit_expr { $$ = { type: 'BitExpression', operator: '|', left: $1, right: $3 } }
   | bit_expr '&' bit_expr { $$ = { type: 'BitExpression', operator: '&', left: $1, right: $3 } }
   | bit_expr '<<' bit_expr { $$ = { type: 'BitExpression', operator: '<<', left: $1, right: $3 } }
   | bit_expr '>>' bit_expr { $$ = { type: 'BitExpression', operator: '>>', left: $1, right: $3 } }
@@ -494,12 +494,12 @@ selectDataSetOpt
     { $$ = { from: $2, partition: $3, where: $4, groupBy: $5, having: $6, orderBy: $7, limit: $8, procedure: $9, updateLockMode: $10 } }
   ;
 table_refrences
-  : escaped_table_reference { $$ = { type: 'TableRefrences', value: [ $1 ] } }
+  : escaped_table_reference { $$ = { type: 'TableReferences', value: [ $1 ] } }
   | table_refrences ',' escaped_table_reference %prec TABLE_REF_COMMA { $$ = $1; $1.value.push($3); }
   ;
 escaped_table_reference
-  : table_reference { $$ = { type: 'TableRefrence', value: $1 } }
-  | '{' OJ table_reference '}' { $$ = { type: 'TableRefrence', hasOj: true, value: $3 } }
+  : table_reference { $$ = { type: 'TableReference', value: $1 } }
+  | '{' OJ table_reference '}' { $$ = { type: 'TableReference', hasOj: true, value: $3 } }
   ;
 join_inner_cross
   : { $$ = null }
diff --git a/src/stringify.js b/src/stringify.js
index c9eb348..14b7e05 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -387,9 +387,9 @@ Sql.prototype.travelLimit = function (ast) {
     }
   }
 }
-Sql.prototype.travelTableRefrences = function (ast) {
+Sql.prototype.travelTableReferences = function (ast) {
   var list = ast.value;
-  if (ast.TableRefrences) {
+  if (ast.TableReferences) {
     this.append('(', false, true);
   }
   for (var i = 0; i < list.length; i++) {
@@ -398,11 +398,11 @@ Sql.prototype.travelTableRefrences = function (ast) {
       this.append(',', true);
     }
   }
-  if (ast.TableRefrences) {
+  if (ast.TableReferences) {
     this.append(')');
   }
 }
-Sql.prototype.travelTableRefrence = function (ast) {
+Sql.prototype.travelTableReference = function (ast) {
   if (ast.hasOj) {
     this.append('{');
     this.appendKeyword('oj');

From c8758ee7c157f350cf04cf59ba6cebeb633fad8d Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Wed, 31 Jul 2019 14:37:08 +0800
Subject: [PATCH 07/37] release: v1.2.0 & append fix for typo.

---
 CHANGELOG           | 2 ++
 package.json        | 2 +-
 src/sqlParser.jison | 8 ++++----
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 77f18d1..9165fdc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
 1.0.6 add MIT license.
 1.0.7 fix alias for identifier #16 , pr: #17
 1.0.8 support union grammar follow: https://dev.mysql.com/doc/refman/8.0/en/union.html
+1.1.0 hotfix: Remove unused import
+1.2.0 fix typo "refrence" to "reference" #24
diff --git a/package.json b/package.json
index b2c8cb7..d971222 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {
diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index bc15230..e3cb798 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -490,12 +490,12 @@ for_update_lock_in_share_mode_opt
   ;
 selectDataSetOpt
   : { $$ = {} }
-  | FROM table_refrences partitionOpt where_opt group_by_opt having_opt order_by_opt limit_opt procedure_opt for_update_lock_in_share_mode_opt
+  | FROM table_references partitionOpt where_opt group_by_opt having_opt order_by_opt limit_opt procedure_opt for_update_lock_in_share_mode_opt
     { $$ = { from: $2, partition: $3, where: $4, groupBy: $5, having: $6, orderBy: $7, limit: $8, procedure: $9, updateLockMode: $10 } }
   ;
-table_refrences
+table_references
   : escaped_table_reference { $$ = { type: 'TableReferences', value: [ $1 ] } }
-  | table_refrences ',' escaped_table_reference %prec TABLE_REF_COMMA { $$ = $1; $1.value.push($3); }
+  | table_references ',' escaped_table_reference %prec TABLE_REF_COMMA { $$ = $1; $1.value.push($3); }
   ;
 escaped_table_reference
   : table_reference { $$ = { type: 'TableReference', value: $1 } }
@@ -583,5 +583,5 @@ index_hint
 table_factor
   : identifier partitionOpt aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, partition: $2, alias: $3.alias, hasAs: $3.hasAs, indexHintOpt: $4 } }
   | '(' selectClause ')' aliasOpt { $$ = { type: 'SubQuery', value: $2, alias: $4.alias, hasAs: $4.hasAs } }
-  | '(' table_refrences ')' { $$ = $2; $$.hasParentheses = true }
+  | '(' table_references ')' { $$ = $2; $$.hasParentheses = true }
   ;

From 508f697ca407165be084b894b1248bc93791ce42 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Wed, 31 Jul 2019 14:48:28 +0800
Subject: [PATCH 08/37] chore: put `news` into README.md

---
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.md b/README.md
index 642fa81..e7297dc 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,12 @@
 
 sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
+## news
+
+- Typo 'refrence' has been fixed to 'reference' since v1.2.0.
+
+for more changes see [CHANGELOG](./CHANGELOG)
+
 ## commonjs usage
 
 `npm install --save js-sql-parser`

From e8e7cc41a4bac167ac7d73ca0917495a1d501e22 Mon Sep 17 00:00:00 2001
From: Ryan Mentzer <rmentzer@sfgmembers.com>
Date: Mon, 21 Oct 2019 10:16:31 -0500
Subject: [PATCH 09/37] support stringifying the having keyword

---
 package.json      |   5 +-
 src/stringify.js  | 229 +++++++++++++++++++++++-----------------------
 test/main.test.js |  69 ++++++++------
 3 files changed, 157 insertions(+), 146 deletions(-)

diff --git a/package.json b/package.json
index d971222..728c4f8 100644
--- a/package.json
+++ b/package.json
@@ -5,11 +5,11 @@
   "main": "./dist/parser/sqlParser.js",
   "scripts": {
     "build": "jison -m js ./src/sqlParser.jison -o ./dist/parser/sqlParser.js && npm run build-concat",
-    "build-concat": "cat src/stringify.js src/suffix.js >> ./dist/parser/sqlParser.js",
+    "build-concat": "minicat src/stringify.js src/suffix.js >> dist/parser/sqlParser.js",
     "postbuild": "npm run test:build",
     "test": "npm run build",
     "test:all": "mocha --require babel-register",
-    "test:build": "mocha --require babel-register 'test/*.test.js'",
+    "test:build": "mocha --require babel-register test/*.test.js",
     "test:benchmark": "mocha --require babel-register test/benchmark.js"
   },
   "repository": {
@@ -33,6 +33,7 @@
     "concat": "^1.0.3",
     "debug": "^3.1.0",
     "jison": "^0.4.17",
+    "minicat": "^1.0.0",
     "mocha": "^3.2.0"
   }
 }
diff --git a/src/stringify.js b/src/stringify.js
index 14b7e05..57ea74e 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -6,13 +6,13 @@ function Sql() {
   this.buffer = '';
 }
 
-sqlParser.stringify = function (ast) {
+sqlParser.stringify = function(ast) {
   var sql = new Sql();
   sql.travelMain(ast);
   return sql.buffer;
-}
+};
 
-Sql.prototype.travel = function (ast) {
+Sql.prototype.travel = function(ast) {
   if (!ast) return;
 
   if (typeof ast === 'string') {
@@ -21,7 +21,7 @@ Sql.prototype.travel = function (ast) {
 
   var processor = this['travel' + ast.type];
   processor.call(this, ast);
-}
+};
 
 var noSuffixFlag = false;
 Sql.prototype.appendKeyword = function(keyword, noPrefix, noSuffix) {
@@ -38,7 +38,7 @@ Sql.prototype.appendKeyword = function(keyword, noPrefix, noSuffix) {
   if (noSuffix) {
     noSuffixFlag = true;
   }
-}
+};
 Sql.prototype.append = function(word, noPrefix, noSuffix) {
   if (noSuffixFlag) {
     noPrefix = true;
@@ -53,13 +53,13 @@ Sql.prototype.append = function(word, noPrefix, noSuffix) {
   if (noSuffix) {
     noSuffixFlag = true;
   }
-}
+};
 Sql.prototype.travelMain = function(ast) {
   this.travel(ast.value);
   if (ast.hasSemicolon) {
     this.append(';', true);
   }
-}
+};
 Sql.prototype.travelSelect = function(ast) {
   this.appendKeyword('select');
   if (ast.distinctOpt) {
@@ -107,6 +107,7 @@ Sql.prototype.travelSelect = function(ast) {
     this.travel(ast.groupBy);
   }
   if (ast.having) {
+    this.appendKeyword('having');
     this.travel(ast.having);
   }
   if (ast.orderBy) {
@@ -122,8 +123,8 @@ Sql.prototype.travelSelect = function(ast) {
   if (ast.updateLockMode) {
     this.appendKeyword(ast.updateLockMode);
   }
-}
-Sql.prototype.travelSelectExpr = function (ast) {
+};
+Sql.prototype.travelSelectExpr = function(ast) {
   var exprList = ast.value;
   for (var i = 0; i < exprList.length; i++) {
     if (typeof ast === 'string') {
@@ -141,69 +142,69 @@ Sql.prototype.travelSelectExpr = function (ast) {
       this.append(',', true);
     }
   }
-}
-Sql.prototype.travelIsExpression = function (ast) {
+};
+Sql.prototype.travelIsExpression = function(ast) {
   this.travel(ast.left);
   this.appendKeyword('in');
   if (ast.hasNot) {
     this.appendKeyword('not');
   }
   this.append(ast.right);
-}
-Sql.prototype.travelNotExpression = function (ast) {
+};
+Sql.prototype.travelNotExpression = function(ast) {
   this.appendKeyword('not');
   this.travel(ast.value);
-}
-Sql.prototype.travelOrExpression =
-Sql.prototype.travelAndExpression =
-Sql.prototype.travelXORExpression = function (ast) {
+};
+Sql.prototype.travelOrExpression = Sql.prototype.travelAndExpression = Sql.prototype.travelXORExpression = function(
+  ast
+) {
   this.travel(ast.left);
   this.appendKeyword(ast.operator);
   this.travel(ast.right);
-}
-Sql.prototype.travelNull =
-Sql.prototype.travelBoolean =
-Sql.prototype.travelBooleanExtra = function (ast) {
+};
+Sql.prototype.travelNull = Sql.prototype.travelBoolean = Sql.prototype.travelBooleanExtra = function(
+  ast
+) {
   this.appendKeyword(ast.value);
-}
-Sql.prototype.travelNumber = function (ast) {
+};
+Sql.prototype.travelNumber = function(ast) {
   this.append(ast.value);
-}
-Sql.prototype.travelString = function (ast) {
+};
+Sql.prototype.travelString = function(ast) {
   this.append(ast.value);
-}
-Sql.prototype.travelFunctionCall = function (ast) {
+};
+Sql.prototype.travelFunctionCall = function(ast) {
   this.append(ast.name);
   this.append('(', true, true);
   var params = ast.params;
   for (var i = 0; i < params.length; i++) {
     var param = params[i];
     this.travel(param);
-    if (i !== params.length -1) {
+    if (i !== params.length - 1) {
       this.append(',', true);
     }
   }
   this.append(')', true);
-}
-Sql.prototype.travelFunctionCallParam = function (ast) {
+};
+Sql.prototype.travelFunctionCallParam = function(ast) {
   if (ast.distinctOpt) {
     this.appendKeyword(ast.distinctOpt);
   }
   this.travel(ast.value);
-}
-Sql.prototype.travelIdentifier = function (ast) {
+};
+Sql.prototype.travelIdentifier = function(ast) {
   this.append(ast.value);
-}
-Sql.prototype.travelIdentifierList = function (ast) {
+};
+Sql.prototype.travelIdentifierList = function(ast) {
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
     this.travel(list[i]);
-    if (i !== list.length -1) {
+    if (i !== list.length - 1) {
       this.append(',', true);
     }
   }
-}
-Sql.prototype.travelWhenThenList = function (ast) {
+};
+Sql.prototype.travelWhenThenList = function(ast) {
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
     this.appendKeyword('when');
@@ -211,8 +212,8 @@ Sql.prototype.travelWhenThenList = function (ast) {
     this.appendKeyword('then');
     this.travel(list[i].then);
   }
-}
-Sql.prototype.travelCaseWhen = function (ast) {
+};
+Sql.prototype.travelCaseWhen = function(ast) {
   this.appendKeyword('case');
   if (ast.caseExprOpt) {
     this.travel(ast.caseExprOpt);
@@ -223,39 +224,39 @@ Sql.prototype.travelCaseWhen = function (ast) {
     this.travel(ast.else);
   }
   this.appendKeyword('end');
-}
-Sql.prototype.travelPrefix = function (ast) {
+};
+Sql.prototype.travelPrefix = function(ast) {
   this.appendKeyword(ast.prefix);
   this.travel(ast.value);
-}
-Sql.prototype.travelSimpleExprParentheses = function (ast) {
+};
+Sql.prototype.travelSimpleExprParentheses = function(ast) {
   if (ast.hasRow) {
     this.appendKeyword('row');
   }
   this.append('(', false, true);
   this.travel(ast.value);
   this.append(')', true);
-}
-Sql.prototype.travelSubQuery = function (ast) {
+};
+Sql.prototype.travelSubQuery = function(ast) {
   if (ast.hasExists) {
     this.appendKeyword('exists');
   }
   this.append('(', false, true);
   this.travel(ast.value);
   this.append(')', true);
-}
-Sql.prototype.travelIdentifierExpr = function (ast) {
+};
+Sql.prototype.travelIdentifierExpr = function(ast) {
   this.append('{');
   this.travel(ast.identifier);
   this.travel(ast.value);
   this.append('}');
-}
-Sql.prototype.travelBitExpression = function (ast) {
+};
+Sql.prototype.travelBitExpression = function(ast) {
   this.travel(ast.left);
   this.appendKeyword(ast.operator);
   this.travel(ast.right);
-}
-Sql.prototype.travelInSubQueryPredicate = function (ast) {
+};
+Sql.prototype.travelInSubQueryPredicate = function(ast) {
   this.travel(ast.left);
   if (ast.hasNot) {
     this.appendKeyword('not');
@@ -264,8 +265,8 @@ Sql.prototype.travelInSubQueryPredicate = function (ast) {
   this.append('(', false, true);
   this.travel(ast.right);
   this.append(')');
-}
-Sql.prototype.travelInExpressionListPredicate = function (ast) {
+};
+Sql.prototype.travelInExpressionListPredicate = function(ast) {
   this.travel(ast.left);
   if (ast.hasNot) {
     this.appendKeyword('not');
@@ -274,8 +275,8 @@ Sql.prototype.travelInExpressionListPredicate = function (ast) {
   this.append('(', false, true);
   this.travel(ast.right);
   this.append(')');
-}
-Sql.prototype.travelBetweenPredicate = function (ast) {
+};
+Sql.prototype.travelBetweenPredicate = function(ast) {
   this.travel(ast.left);
   if (ast.hasNot) {
     this.appendKeyword('not');
@@ -284,14 +285,14 @@ Sql.prototype.travelBetweenPredicate = function (ast) {
   this.travel(ast.right.left);
   this.appendKeyword('and');
   this.travel(ast.right.right);
-}
-Sql.prototype.travelSoundsLikePredicate = function (ast) {
+};
+Sql.prototype.travelSoundsLikePredicate = function(ast) {
   this.travel(ast.left);
   this.appendKeyword('sounds');
   this.appendKeyword('like');
   this.travel(ast.right);
-}
-Sql.prototype.travelLikePredicate = function (ast) {
+};
+Sql.prototype.travelLikePredicate = function(ast) {
   this.travel(ast.left);
   if (ast.hasNot) {
     this.appendKeyword('not');
@@ -299,40 +300,40 @@ Sql.prototype.travelLikePredicate = function (ast) {
   this.appendKeyword('like');
   this.travel(ast.right);
   if (ast.escape) {
-    this.appendKeyword('escape')
+    this.appendKeyword('escape');
     this.travel(ast.escape);
   }
-}
-Sql.prototype.travelRegexpPredicate = function (ast) {
+};
+Sql.prototype.travelRegexpPredicate = function(ast) {
   this.travel(ast.left);
   if (ast.hasNot) {
     this.appendKeyword('not');
   }
   this.appendKeyword('regexp');
   this.travel(ast.right);
-}
-Sql.prototype.travelIsNullBooleanPrimary = function (ast) {
+};
+Sql.prototype.travelIsNullBooleanPrimary = function(ast) {
   this.travel(ast.value);
   this.appendKeyword('is');
   if (ast.hasNot) {
     this.appendKeyword('not');
   }
   this.appendKeyword('null');
-}
-Sql.prototype.travelComparisonBooleanPrimary = function (ast) {
+};
+Sql.prototype.travelComparisonBooleanPrimary = function(ast) {
   this.travel(ast.left);
   this.append(ast.operator);
   this.travel(ast.right);
-}
-Sql.prototype.travelComparisonSubQueryBooleanPrimary = function (ast) {
+};
+Sql.prototype.travelComparisonSubQueryBooleanPrimary = function(ast) {
   this.travel(ast.left);
   this.append(ast.operator);
   this.appendKeyword(ast.subQueryOpt);
   this.append('(', false, true);
   this.travel(ast.right);
   this.append(')');
-}
-Sql.prototype.travelExpressionList = function (ast) {
+};
+Sql.prototype.travelExpressionList = function(ast) {
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
     this.travel(list[i]);
@@ -340,8 +341,8 @@ Sql.prototype.travelExpressionList = function (ast) {
       this.append(',', true);
     }
   }
-}
-Sql.prototype.travelGroupBy = function (ast) {
+};
+Sql.prototype.travelGroupBy = function(ast) {
   this.appendKeyword('group by');
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
@@ -350,8 +351,8 @@ Sql.prototype.travelGroupBy = function (ast) {
       this.append(',', true);
     }
   }
-}
-Sql.prototype.travelOrderBy = function (ast) {
+};
+Sql.prototype.travelOrderBy = function(ast) {
   this.appendKeyword('order by');
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
@@ -363,14 +364,14 @@ Sql.prototype.travelOrderBy = function (ast) {
   if (ast.rollUp) {
     this.appendKeyword('with rollup');
   }
-}
-Sql.prototype.travelGroupByOrderByItem = function (ast) {
+};
+Sql.prototype.travelGroupByOrderByItem = function(ast) {
   this.travel(ast.value);
   if (ast.sortOpt) {
     this.appendKeyword(ast.sortOpt);
   }
-}
-Sql.prototype.travelLimit = function (ast) {
+};
+Sql.prototype.travelLimit = function(ast) {
   this.appendKeyword('limit');
   var list = ast.value;
   if (list.length === 1) {
@@ -386,8 +387,8 @@ Sql.prototype.travelLimit = function (ast) {
       this.append(list[1]);
     }
   }
-}
-Sql.prototype.travelTableReferences = function (ast) {
+};
+Sql.prototype.travelTableReferences = function(ast) {
   var list = ast.value;
   if (ast.TableReferences) {
     this.append('(', false, true);
@@ -401,8 +402,8 @@ Sql.prototype.travelTableReferences = function (ast) {
   if (ast.TableReferences) {
     this.append(')');
   }
-}
-Sql.prototype.travelTableReference = function (ast) {
+};
+Sql.prototype.travelTableReference = function(ast) {
   if (ast.hasOj) {
     this.append('{');
     this.appendKeyword('oj');
@@ -411,8 +412,8 @@ Sql.prototype.travelTableReference = function (ast) {
   } else {
     this.travel(ast.value);
   }
-}
-Sql.prototype.travelInnerCrossJoinTable = function (ast) {
+};
+Sql.prototype.travelInnerCrossJoinTable = function(ast) {
   this.travel(ast.left);
   if (ast.innerCrossOpt) {
     this.appendKeyword(ast.innerCrossOpt);
@@ -422,14 +423,14 @@ Sql.prototype.travelInnerCrossJoinTable = function (ast) {
   if (ast.condition) {
     this.travel(ast.condition);
   }
-}
-Sql.prototype.travelStraightJoinTable = function (ast) {
+};
+Sql.prototype.travelStraightJoinTable = function(ast) {
   this.travel(ast.left);
   this.appendKeyword('straight_join');
   this.travel(ast.right);
   this.travel(ast.condition);
-}
-Sql.prototype.travelLeftRightJoinTable = function (ast) {
+};
+Sql.prototype.travelLeftRightJoinTable = function(ast) {
   this.travel(ast.left);
   this.appendKeyword(ast.leftRight);
   if (ast.outOpt) {
@@ -438,8 +439,8 @@ Sql.prototype.travelLeftRightJoinTable = function (ast) {
   this.appendKeyword('join');
   this.travel(ast.right);
   this.travel(ast.condition);
-}
-Sql.prototype.travelNaturalJoinTable = function (ast) {
+};
+Sql.prototype.travelNaturalJoinTable = function(ast) {
   this.travel(ast.left);
   this.appendKeyword('natural');
   if (ast.leftRight) {
@@ -450,18 +451,18 @@ Sql.prototype.travelNaturalJoinTable = function (ast) {
   }
   this.appendKeyword('join');
   this.travel(ast.right);
-}
-Sql.prototype.travelOnJoinCondition = function (ast) {
+};
+Sql.prototype.travelOnJoinCondition = function(ast) {
   this.appendKeyword('on');
   this.travel(ast.value);
-}
-Sql.prototype.travelUsingJoinCondition = function (ast) {
+};
+Sql.prototype.travelUsingJoinCondition = function(ast) {
   this.appendKeyword('using');
   this.appendKeyword('(', false, true);
   this.travel(ast.value);
   this.appendKeyword(')');
-}
-Sql.prototype.travelPartitions = function (ast) {
+};
+Sql.prototype.travelPartitions = function(ast) {
   this.appendKeyword('partition');
   this.appendKeyword('(', false, true);
   var list = ast.value;
@@ -472,12 +473,12 @@ Sql.prototype.travelPartitions = function (ast) {
     }
   }
   this.appendKeyword(')');
-}
-Sql.prototype.travelForOptIndexHint = function (ast) {
+};
+Sql.prototype.travelForOptIndexHint = function(ast) {
   this.appendKeyword('for');
   this.appendKeyword(ast.value);
-}
-Sql.prototype.travelIndexList = function (ast) {
+};
+Sql.prototype.travelIndexList = function(ast) {
   var list = ast.value;
   for (var i = 0; i < list.length; i++) {
     this.travel(list[i]);
@@ -485,8 +486,8 @@ Sql.prototype.travelIndexList = function (ast) {
       this.append(',', true);
     }
   }
-}
-Sql.prototype.travelUseIndexHint = function (ast) {
+};
+Sql.prototype.travelUseIndexHint = function(ast) {
   this.appendKeyword('use');
   this.appendKeyword(ast.indexOrKey);
   if (ast.forOpt) {
@@ -497,8 +498,8 @@ Sql.prototype.travelUseIndexHint = function (ast) {
     this.travel(ast.value);
   }
   this.appendKeyword(')');
-}
-Sql.prototype.travelIgnoreIndexHint = function (ast) {
+};
+Sql.prototype.travelIgnoreIndexHint = function(ast) {
   this.appendKeyword('ignore');
   this.appendKeyword(ast.indexOrKey);
   if (ast.forOpt) {
@@ -509,8 +510,8 @@ Sql.prototype.travelIgnoreIndexHint = function (ast) {
     this.travel(ast.value);
   }
   this.appendKeyword(')');
-}
-Sql.prototype.travelForceIndexHint = function (ast) {
+};
+Sql.prototype.travelForceIndexHint = function(ast) {
   this.appendKeyword('force');
   this.appendKeyword(ast.indexOrKey);
   if (ast.forOpt) {
@@ -521,8 +522,8 @@ Sql.prototype.travelForceIndexHint = function (ast) {
     this.travel(ast.value);
   }
   this.appendKeyword(')');
-}
-Sql.prototype.travelTableFactor = function (ast) {
+};
+Sql.prototype.travelTableFactor = function(ast) {
   this.travel(ast.value);
   if (ast.partition) {
     this.travel(ast.partition);
@@ -536,17 +537,17 @@ Sql.prototype.travelTableFactor = function (ast) {
   if (ast.indexHintOpt) {
     this.travel(ast.indexHintOpt);
   }
-}
-Sql.prototype.travelUnion = function (ast) {
+};
+Sql.prototype.travelUnion = function(ast) {
   this.travel(ast.left);
   this.appendKeyword('UNION');
   if (ast.distinctOpt) {
-    this.appendKeyword(ast.distinctOpt)
+    this.appendKeyword(ast.distinctOpt);
   }
   this.travel(ast.right);
-}
-Sql.prototype.travelSelectParenthesized = function (ast) {
+};
+Sql.prototype.travelSelectParenthesized = function(ast) {
   this.appendKeyword('(');
   this.travel(ast.value);
   this.appendKeyword(')');
-}
+};
diff --git a/test/main.test.js b/test/main.test.js
index 8908c22..988c1cc 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -3,7 +3,7 @@
 const debug = require('debug')('js-sql-parser');
 const parser = require('../');
 
-const testParser = function (sql) {
+const testParser = function(sql) {
   let firstAst = parser.parse(sql);
   debug(JSON.stringify(firstAst, null, 2));
   let firstSql = parser.stringify(firstAst);
@@ -16,30 +16,30 @@ const testParser = function (sql) {
   if (firstSql !== secondSql) {
     console.log('firstSql', firstSql);
     console.log('secondSql', secondSql);
-    throw 'err firstSql don\'t equals secondSql. ';
+    throw "err firstSql don't equals secondSql. ";
   }
 
   return secondAst;
-}
+};
 
-describe('select grammar support', function () {
-  it('test0', function () {
+describe('select grammar support', function() {
+  it('test0', function() {
     testParser('select a from b where c > 1 group by d order by e desc;');
   });
 
-  it('test1', function () {
+  it('test1', function() {
     testParser('select distinct max_statement_time = 1.2 a ');
   });
 
-  it('test2', function () {
+  it('test2', function() {
     testParser('select all 0x1f');
   });
 
-  it('test3', function () {
+  it('test3', function() {
     testParser('select distinctrow "xx", a in (1,2)');
   });
 
-  it('test4', function () {
+  it('test4', function() {
     testParser(`
       select
         tag_basic.gender as gender,
@@ -58,17 +58,17 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test5', function () {
+  it('test5', function() {
     testParser('select function(), function(1, "sd", 0x1F)');
   });
 
-  it ('test6 unicode', function () {
+  it('test6 unicode', function() {
     testParser(`
       select in中文 from tags
     `);
   });
 
-  it ('test7', function () {
+  it('test7', function() {
     testParser(`
       SELECT 
         DISTINCT high_priority MAX_STATEMENT_TIME=1 STRAIGHT_JOIN SQL_SMALL_RESULT SQL_BIG_RESULT SQL_BUFFER_RESULT SQL_CACHE SQL_CALC_FOUND_ROWS fm_customer.lname AS name1,
@@ -89,7 +89,7 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test8', function () {
+  it('test8', function() {
     testParser(`
       SELECT   P1.PAYMENTNO, P1.AMOUNT,
       (P1.AMOUNT * 100) / SUM(P2.AMOUNT)
@@ -99,7 +99,7 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test9', function () {
+  it('test9', function() {
     testParser(`
         SELECT  PLAYERS.PLAYERNO, NAME,
        (SELECT   COUNT(*)
@@ -114,7 +114,7 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test10', function () {
+  it('test10', function() {
     testParser(`
       SELECT rd.*, rd.rd_numberofrooms - (
         SELECT SUM(rn.reservation_numberofrooms) AS count_reserve_room
@@ -148,11 +148,11 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test11 SELECT `LEFT`(a, 3) FROM b support.', function () {
+  it('test11 SELECT `LEFT`(a, 3) FROM b support.', function() {
     testParser('SELECT `LEFT`(a, 3) FROM b');
   });
 
-  it ('test12', function () {
+  it('test12', function() {
     testParser(`
       select
           a.product_id,
@@ -202,7 +202,7 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test13', function () {
+  it('test13', function() {
     testParser(`
       SELECT
           a.*, f.ORG_NAME DEPT_NAME,
@@ -286,7 +286,7 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('test14', function () {
+  it('test14', function() {
     testParser(`
       SELECT
           k.*,
@@ -345,37 +345,46 @@ describe('select grammar support', function () {
     `);
   });
 
-  it ('limit support.', function () {
+  it('test15', function() {
+    testParser(`
+      SELECT   P1.PAYMENTNO, P1.AMOUNT, (P1.AMOUNT * 100) / SUM(P2.AMOUNT)
+      FROM     PENALTIES AS P1, PENALTIES AS P2
+      GROUP BY P1.PAYMENTNO, P1.AMOUNT
+      HAVING MAX(P2.AMOUNT) > 0
+    `);
+  });
+
+  it('limit support.', function() {
     testParser('select a from b limit 2, 3');
   });
 
-  it ('fix not equal.', function () {
+  it('fix not equal.', function() {
     testParser('select a from b where a <> 1 limit 2, 3');
   });
 
-  it ('restore semicolon.', function () {
+  it('restore semicolon.', function() {
     testParser('select a from b limit 2;');
   });
 
-  it ('recognoce alias for sql-function calls in stringify function.', function () {
+  it('recognoce alias for sql-function calls in stringify function.', function() {
     testParser('SELECT COUNT(*) AS total, a b, b as c, c/2 d, d & e an FROM b');
   });
 
-  it ('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+  it('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
     testParser('select a from dual union select a from foo;');
   });
 
-  it ('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+  it('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
     testParser('(select a from dual) union (select a from foo) order by a desc limit 100, 100;');
   });
 
-  it ('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
+  it('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
     testParser('(select a from dual) union all (select a from foo) order by a limit 100');
   });
 
-  it ('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
-    testParser('select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1');
+  it('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
+    testParser(
+      'select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1'
+    );
   });
-
 });
-

From f5ae2eca5a7eb4dc288b205b92b20ed9c632dfab Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 22 Oct 2019 09:55:33 +0800
Subject: [PATCH 10/37] release: 1.2.1 & update CHANGELOG.

---
 CHANGELOG    | 3 +++
 package.json | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index 9165fdc..76f97c3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
 1.0.6 add MIT license.
 1.0.7 fix alias for identifier #16 , pr: #17
 1.0.8 support union grammar follow: https://dev.mysql.com/doc/refman/8.0/en/union.html
+1.0.9 fix stringify having #29 , pr: #28
 1.1.0 hotfix: Remove unused import
+1.1.1 fix stringify having #29 , pr: #28
 1.2.0 fix typo "refrence" to "reference" #24
+1.2.1 fix stringify having #29 , pr: #28
diff --git a/package.json b/package.json
index 728c4f8..292c2d3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.2.0",
+  "version": "1.2.1",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From 047b8701d689c611396fe54d2c84c43501ea7243 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 22 Oct 2019 10:06:46 +0800
Subject: [PATCH 11/37] chore: update news tab.

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index e7297dc..7d645b2 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 ## news
 
 - Typo 'refrence' has been fixed to 'reference' since v1.2.0.
+- Bug stringify keyword `having` has been fixed since v1.2.1.
 
 for more changes see [CHANGELOG](./CHANGELOG)
 

From 2960e33c1d2a63a2b99895cdde75bcd3acc72f1b Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 22 Oct 2019 10:12:59 +0800
Subject: [PATCH 12/37] chore: add Makefile.

---
 Makefile | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 Makefile

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..51dd70d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+publish: test
+	@npm publish
+
+test: 
+	@npm test
+
+.PHONY: publish test

From 141ef504a39cc98ed250ff299606aeb4cb1a5f4a Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 22 Oct 2019 14:22:56 +0800
Subject: [PATCH 13/37] chore: readme.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7d645b2..08ce092 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 ## news
 
 - Typo 'refrence' has been fixed to 'reference' since v1.2.0.
-- Bug stringify keyword `having` has been fixed since v1.2.1.
+- Fix bug stringify keyword `having` since v1.2.1. #29
 
 for more changes see [CHANGELOG](./CHANGELOG)
 

From 03743788078dcddcaf6d45e35e7f240f4cc9b620 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Tue, 22 Oct 2019 14:24:41 +0800
Subject: [PATCH 14/37] chore: update readme.md.

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 08ce092..c18376a 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 ## news
 
 - Typo 'refrence' has been fixed to 'reference' since v1.2.0.
-- Fix bug stringify keyword `having` since v1.2.1. #29
+- Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)
 
 for more changes see [CHANGELOG](./CHANGELOG)
 

From d3c48713227e85f97011aec98ff6241e6d0cf33f Mon Sep 17 00:00:00 2001
From: Ernesto Rodriguez Ortiz <ernesto.ortiz@tracktik.com>
Date: Thu, 26 Mar 2020 19:42:16 -0400
Subject: [PATCH 15/37] feat: add support for quoted alias

---
 src/sqlParser.jison |  3 +++
 test/main.test.js   | 10 ++++++++++
 2 files changed, 13 insertions(+)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index e3cb798..ee5b329 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -127,6 +127,7 @@ UNION                                                             return 'UNION'
 [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
 ['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'QUOTED_IDENTIFIER'
+[`].+[`]                                                          return 'QUOTED_IDENTIFIER'
 
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
@@ -279,6 +280,8 @@ selectExprAliasOpt
   : { $$ = {alias: null, hasAs: null} }
   | AS IDENTIFIER { $$ = {alias: $2, hasAs: true} }
   | IDENTIFIER { $$ = {alias: $1, hasAs: false} }
+  | AS QUOTED_IDENTIFIER { $$ = {alias: $2, hasAs: true} }
+  | QUOTED_IDENTIFIER { $$ = {alias: $1, hasAs: false} }
   ;
 
 string
diff --git a/test/main.test.js b/test/main.test.js
index 988c1cc..558d133 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -387,4 +387,14 @@ describe('select grammar support', function() {
       'select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1'
     );
   });
+
+  it('support quoted alias', function() {
+    testParser('select a as `A-A` from b limit 2;');
+    testParser('select a as `A#A` from b limit 2;');
+    testParser('select a as `A?A` from b limit 2;');
+    testParser('select a as `A/B` from b limit 2;');
+    testParser('select a as `A.A` from b limit 2;');
+    testParser('select a as `A|A` from b limit 2;');
+    testParser('select a as `A A` from b limit 2;');
+  });
 });

From 24381eee5acd06a844a3d2354328acc3e5312e8f Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:11:24 +0800
Subject: [PATCH 16/37] release: v1.2.2

---
 CHANGELOG    | 1 +
 README.md    | 1 +
 package.json | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index 76f97c3..185e98b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,3 +6,4 @@
 1.1.1 fix stringify having #29 , pr: #28
 1.2.0 fix typo "refrence" to "reference" #24
 1.2.1 fix stringify having #29 , pr: #28
+1.2.2 feat: add support for "`" quoted alias, pr: #33
diff --git a/README.md b/README.md
index c18376a..24afab7 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 - Typo 'refrence' has been fixed to 'reference' since v1.2.0.
 - Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)
+- Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33)
 
 for more changes see [CHANGELOG](./CHANGELOG)
 
diff --git a/package.json b/package.json
index 292c2d3..b44a9e9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.2.1",
+  "version": "1.2.2",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From f8034b8637ce59e86bcbd2337a9275a12904c5e9 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:18:37 +0800
Subject: [PATCH 17/37] fix: test case.

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

diff --git a/package.json b/package.json
index b44a9e9..796b08c 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,7 @@
   "scripts": {
     "build": "jison -m js ./src/sqlParser.jison -o ./dist/parser/sqlParser.js && npm run build-concat",
     "build-concat": "minicat src/stringify.js src/suffix.js >> dist/parser/sqlParser.js",
-    "postbuild": "npm run test:build",
-    "test": "npm run build",
+    "test": "npm run test:build",
     "test:all": "mocha --require babel-register",
     "test:build": "mocha --require babel-register test/*.test.js",
     "test:benchmark": "mocha --require babel-register test/benchmark.js"

From 0c0a54f0348a16e17500c6323e374b31a07440bb Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:29:39 +0800
Subject: [PATCH 18/37] fix: test case.

---
 .travis.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4ba6f27..b72d710 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,5 +12,5 @@ node_js:
   - "v8.11.3"
   - "v10.7.0"
 scripts: {
-  "test": "npm run test:build"
-  }
+  "test": "npm test"
+}

From 8f4464d9d92aa5dc5334807f0f42fbc0639ebe0e Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:35:40 +0800
Subject: [PATCH 19/37] fix: test case.

---
 .travis.yml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index b72d710..b0cd44d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,4 @@ node_js:
   - "v6.9.1"
   - "v8.11.3"
   - "v10.7.0"
-scripts: {
-  "test": "npm test"
-}
+script: npm run test:build

From 915dc767373feb1a21264db121c5ce0ecab094ee Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:39:13 +0800
Subject: [PATCH 20/37] fix: test case.

---
 .travis.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index b0cd44d..a94918b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,4 +11,6 @@ node_js:
   - "v6.9.1"
   - "v8.11.3"
   - "v10.7.0"
-script: npm run test:build
+scripts:
+  - npm run build
+  - npm run test

From f674bffb7add53f38be5df6afe1114f855973241 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 11:41:54 +0800
Subject: [PATCH 21/37] fix: test case.

---
 .travis.yml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a94918b..5d6b173 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,3 @@ node_js:
   - "v6.9.1"
   - "v8.11.3"
   - "v10.7.0"
-scripts:
-  - npm run build
-  - npm run test

From 90031e321631f47c6dc6539aed513a3d108eb427 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Mon, 30 Mar 2020 16:39:52 +0800
Subject: [PATCH 22/37] fix: test case.

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

diff --git a/package.json b/package.json
index 796b08c..5dfec0f 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
   "scripts": {
     "build": "jison -m js ./src/sqlParser.jison -o ./dist/parser/sqlParser.js && npm run build-concat",
     "build-concat": "minicat src/stringify.js src/suffix.js >> dist/parser/sqlParser.js",
-    "test": "npm run test:build",
+    "test": "npm run build && npm run test:build",
     "test:all": "mocha --require babel-register",
     "test:build": "mocha --require babel-register test/*.test.js",
     "test:benchmark": "mocha --require babel-register test/benchmark.js"

From 394e5cc0d6aa255776214459314dc171a5bada0c Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 2 Apr 2020 15:08:21 +0800
Subject: [PATCH 23/37] fix: #34

---
 src/sqlParser.jison |  2 +-
 test/main.test.js   | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index ee5b329..7cff3e8 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -585,6 +585,6 @@ index_hint
   ;
 table_factor
   : identifier partitionOpt aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, partition: $2, alias: $3.alias, hasAs: $3.hasAs, indexHintOpt: $4 } }
-  | '(' selectClause ')' aliasOpt { $$ = { type: 'SubQuery', value: $2, alias: $4.alias, hasAs: $4.hasAs } }
+  | '(' selectClause ')' aliasOpt { $$ = { type: 'TableFactor', value: { type: 'SubQuery', value: $2 }, alias: $4.alias, hasAs: $4.hasAs} }
   | '(' table_references ')' { $$ = $2; $$.hasParentheses = true }
   ;
diff --git a/test/main.test.js b/test/main.test.js
index 558d133..834d3f1 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -397,4 +397,18 @@ describe('select grammar support', function() {
     testParser('select a as `A|A` from b limit 2;');
     testParser('select a as `A A` from b limit 2;');
   });
+
+  it('bugfix table alias', function() {
+    testParser(`
+    SELECT stime, A.names, B.names FROM (
+      SELECT stime, names FROM iaas_data.iaas_d3c0d0681cc1900
+    ) AS A LEFT JOIN (
+      SELECT stime, names FROM iaas_data.iaas_1071f89feaa0e100
+    ) AS B ON A.stime = B.stime
+    `);
+  });
+
+  it('bugfix table alias2', function() {
+    testParser('select a.* from a t1 join b t2 on t1.a = t2.a')
+  })
 });

From 315faf7b44ac4a5ed0a281840a2787746e133072 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 2 Apr 2020 15:12:23 +0800
Subject: [PATCH 24/37] release: v1.3.0

---
 CHANGELOG    | 1 +
 README.md    | 5 +++--
 package.json | 2 +-
 3 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 185e98b..3d1a64c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,3 +7,4 @@
 1.2.0 fix typo "refrence" to "reference" #24
 1.2.1 fix stringify having #29 , pr: #28
 1.2.2 feat: add support for "`" quoted alias, pr: #33
+1.3.0 fix tableFactor alias bug. AST changed in tableFactor. #34
diff --git a/README.md b/README.md
index 24afab7..467f6e6 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,10 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
-- Typo 'refrence' has been fixed to 'reference' since v1.2.0.
-- Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)
+- Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
 - Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33)
+- Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)
+- Typo 'refrence' has been fixed to 'reference' since v1.2.0.
 
 for more changes see [CHANGELOG](./CHANGELOG)
 
diff --git a/package.json b/package.json
index 5dfec0f..3b620bc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.2.2",
+  "version": "1.3.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From 42efe32c247b5874ae8d66b0d99385c0e1f63168 Mon Sep 17 00:00:00 2001
From: Nicola Capovilla <nicola.capovilla93@gmail.com>
Date: Wed, 6 May 2020 10:27:54 +0200
Subject: [PATCH 25/37] fix: fix support for quoted alias

---
 src/sqlParser.jison | 14 +++++++++++---
 test/main.test.js   | 10 ++++++++--
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 7cff3e8..53444e0 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -127,7 +127,7 @@ UNION                                                             return 'UNION'
 [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
 ['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'QUOTED_IDENTIFIER'
-[`].+[`]                                                          return 'QUOTED_IDENTIFIER'
+([`])(?:(?=(\\?))\2.)*?\1                                         return 'QUOTED_IDENTIFIER'
 
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
@@ -285,8 +285,7 @@ selectExprAliasOpt
   ;
 
 string
-  : QUOTED_IDENTIFIER { $$ = { type: 'String', value: $1 } }
-  | STRING { $$ = { type: 'String', value: $1 } }
+  : STRING { $$ = { type: 'String', value: $1 } }
   ;
 number
   : NUMERIC { $$ = { type: 'Number', value: $1 } }
@@ -328,6 +327,14 @@ identifier_list
   : identifier { $$ = { type: 'IdentifierList', value: [ $1 ] } }
   | identifier_list ',' identifier { $$ = $1; $1.value.push($3); }
   ;
+quoted_identifier
+  : QUOTED_IDENTIFIER { $$ = { type: 'Identifier', value: $1 } }
+  | quoted_identifier DOT QUOTED_IDENTIFIER { $$ = $1; $1.value += '.' + $3 }
+  ;
+quoted_identifier_list
+  : quoted_identifier { $$ = { type: 'IdentifierList', value: [ $1 ] } }
+  | quoted_identifier_list ',' quoted_identifier { $$ = $1; $1.value.push($3); }
+  ;
 case_expr_opt
   : { $$ = null }
   | expr { $$ = $1 }
@@ -353,6 +360,7 @@ simple_expr_prefix
 simple_expr
   : literal { $$ = $1 }
   | identifier { $$ = $1 }
+  | quoted_identifier { $$ = $1 }
   | function_call { $$ = $1 }
   | simple_expr_prefix { $$ = $1 }
   | '(' expr_list ')' { $$ = { type: 'SimpleExprParentheses', value: $2 } }
diff --git a/test/main.test.js b/test/main.test.js
index 834d3f1..56330d7 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -408,7 +408,13 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('bugfix table alias2', function() {
+  it('bugfix table alias2', function () {
     testParser('select a.* from a t1 join b t2 on t1.a = t2.a')
-  })
+  });
+
+  it('support quoted alias: multiple alias and orderby support', function () {
+    testParser('select a as `A A`, b as `B B` from z');
+    testParser('select a as `A A` from z order by `A A` desc');
+    testParser('select a as `A A`, b as `B B` from z group by `A A`, `B B` order by `A A` desc');
+  });
 });

From b32a6ccc419f4da27eaf6397f61c1f871462fc45 Mon Sep 17 00:00:00 2001
From: Nicola Capovilla <nicola.capovilla93@gmail.com>
Date: Thu, 7 May 2020 17:30:57 +0200
Subject: [PATCH 26/37] support for double quote alias

---
 src/sqlParser.jison |  3 ++-
 test/main.test.js   | 61 ++++++++++++++++++++++++---------------------
 2 files changed, 35 insertions(+), 29 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 53444e0..1c2c253 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -282,8 +282,9 @@ selectExprAliasOpt
   | IDENTIFIER { $$ = {alias: $1, hasAs: false} }
   | AS QUOTED_IDENTIFIER { $$ = {alias: $2, hasAs: true} }
   | QUOTED_IDENTIFIER { $$ = {alias: $1, hasAs: false} }
+  | AS STRING { $$ = {alias: $2, hasAs: true} }
+  | STRING { $$ = {alias: $2, hasAs: false} }
   ;
-
 string
   : STRING { $$ = { type: 'String', value: $1 } }
   ;
diff --git a/test/main.test.js b/test/main.test.js
index 56330d7..8ba2249 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -3,7 +3,7 @@
 const debug = require('debug')('js-sql-parser');
 const parser = require('../');
 
-const testParser = function(sql) {
+const testParser = function (sql) {
   let firstAst = parser.parse(sql);
   debug(JSON.stringify(firstAst, null, 2));
   let firstSql = parser.stringify(firstAst);
@@ -22,24 +22,24 @@ const testParser = function(sql) {
   return secondAst;
 };
 
-describe('select grammar support', function() {
-  it('test0', function() {
+describe('select grammar support', function () {
+  it('test0', function () {
     testParser('select a from b where c > 1 group by d order by e desc;');
   });
 
-  it('test1', function() {
+  it('test1', function () {
     testParser('select distinct max_statement_time = 1.2 a ');
   });
 
-  it('test2', function() {
+  it('test2', function () {
     testParser('select all 0x1f');
   });
 
-  it('test3', function() {
+  it('test3', function () {
     testParser('select distinctrow "xx", a in (1,2)');
   });
 
-  it('test4', function() {
+  it('test4', function () {
     testParser(`
       select
         tag_basic.gender as gender,
@@ -58,17 +58,17 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test5', function() {
+  it('test5', function () {
     testParser('select function(), function(1, "sd", 0x1F)');
   });
 
-  it('test6 unicode', function() {
+  it('test6 unicode', function () {
     testParser(`
       select in中文 from tags
     `);
   });
 
-  it('test7', function() {
+  it('test7', function () {
     testParser(`
       SELECT 
         DISTINCT high_priority MAX_STATEMENT_TIME=1 STRAIGHT_JOIN SQL_SMALL_RESULT SQL_BIG_RESULT SQL_BUFFER_RESULT SQL_CACHE SQL_CALC_FOUND_ROWS fm_customer.lname AS name1,
@@ -89,7 +89,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test8', function() {
+  it('test8', function () {
     testParser(`
       SELECT   P1.PAYMENTNO, P1.AMOUNT,
       (P1.AMOUNT * 100) / SUM(P2.AMOUNT)
@@ -99,7 +99,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test9', function() {
+  it('test9', function () {
     testParser(`
         SELECT  PLAYERS.PLAYERNO, NAME,
        (SELECT   COUNT(*)
@@ -114,7 +114,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test10', function() {
+  it('test10', function () {
     testParser(`
       SELECT rd.*, rd.rd_numberofrooms - (
         SELECT SUM(rn.reservation_numberofrooms) AS count_reserve_room
@@ -148,11 +148,11 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test11 SELECT `LEFT`(a, 3) FROM b support.', function() {
+  it('test11 SELECT `LEFT`(a, 3) FROM b support.', function () {
     testParser('SELECT `LEFT`(a, 3) FROM b');
   });
 
-  it('test12', function() {
+  it('test12', function () {
     testParser(`
       select
           a.product_id,
@@ -202,7 +202,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test13', function() {
+  it('test13', function () {
     testParser(`
       SELECT
           a.*, f.ORG_NAME DEPT_NAME,
@@ -286,7 +286,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test14', function() {
+  it('test14', function () {
     testParser(`
       SELECT
           k.*,
@@ -345,7 +345,7 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('test15', function() {
+  it('test15', function () {
     testParser(`
       SELECT   P1.PAYMENTNO, P1.AMOUNT, (P1.AMOUNT * 100) / SUM(P2.AMOUNT)
       FROM     PENALTIES AS P1, PENALTIES AS P2
@@ -354,41 +354,41 @@ describe('select grammar support', function() {
     `);
   });
 
-  it('limit support.', function() {
+  it('limit support.', function () {
     testParser('select a from b limit 2, 3');
   });
 
-  it('fix not equal.', function() {
+  it('fix not equal.', function () {
     testParser('select a from b where a <> 1 limit 2, 3');
   });
 
-  it('restore semicolon.', function() {
+  it('restore semicolon.', function () {
     testParser('select a from b limit 2;');
   });
 
-  it('recognoce alias for sql-function calls in stringify function.', function() {
+  it('recognoce alias for sql-function calls in stringify function.', function () {
     testParser('SELECT COUNT(*) AS total, a b, b as c, c/2 d, d & e an FROM b');
   });
 
-  it('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
+  it('union support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
     testParser('select a from dual union select a from foo;');
   });
 
-  it('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
+  it('union Parenthesized support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
     testParser('(select a from dual) union (select a from foo) order by a desc limit 100, 100;');
   });
 
-  it('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
+  it('union all support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
     testParser('(select a from dual) union all (select a from foo) order by a limit 100');
   });
 
-  it('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function() {
+  it('union distinct support, https://dev.mysql.com/doc/refman/8.0/en/union.html', function () {
     testParser(
       'select a from dual order by a desc limit 1, 1 union distinct select a from foo order by a limit 1'
     );
   });
 
-  it('support quoted alias', function() {
+  it('support quoted alias', function () {
     testParser('select a as `A-A` from b limit 2;');
     testParser('select a as `A#A` from b limit 2;');
     testParser('select a as `A?A` from b limit 2;');
@@ -398,7 +398,7 @@ describe('select grammar support', function() {
     testParser('select a as `A A` from b limit 2;');
   });
 
-  it('bugfix table alias', function() {
+  it('bugfix table alias', function () {
     testParser(`
     SELECT stime, A.names, B.names FROM (
       SELECT stime, names FROM iaas_data.iaas_d3c0d0681cc1900
@@ -417,4 +417,9 @@ describe('select grammar support', function() {
     testParser('select a as `A A` from z order by `A A` desc');
     testParser('select a as `A A`, b as `B B` from z group by `A A`, `B B` order by `A A` desc');
   });
+
+  it('support double quoted alias', function () {
+    testParser('select a as "A A" from z');
+    testParser('select a as "A A" from z order by "A A" desc');
+  });
 });

From cf6db04054810bb4d70303e78c2721fff2b75442 Mon Sep 17 00:00:00 2001
From: Nicola Capovilla <nicola.capovilla93@gmail.com>
Date: Tue, 12 May 2020 22:14:33 +0200
Subject: [PATCH 27/37] Merge quoted_identifier in identifier

---
 src/sqlParser.jison | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 1c2c253..3e0f452 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -126,8 +126,8 @@ UNION                                                             return 'UNION'
 
 [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
-['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'QUOTED_IDENTIFIER'
-([`])(?:(?=(\\?))\2.)*?\1                                         return 'QUOTED_IDENTIFIER'
+['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'IDENTIFIER'
+([`])(?:(?=(\\?))\2.)*?\1                                         return 'IDENTIFIER'
 
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
@@ -280,8 +280,6 @@ selectExprAliasOpt
   : { $$ = {alias: null, hasAs: null} }
   | AS IDENTIFIER { $$ = {alias: $2, hasAs: true} }
   | IDENTIFIER { $$ = {alias: $1, hasAs: false} }
-  | AS QUOTED_IDENTIFIER { $$ = {alias: $2, hasAs: true} }
-  | QUOTED_IDENTIFIER { $$ = {alias: $1, hasAs: false} }
   | AS STRING { $$ = {alias: $2, hasAs: true} }
   | STRING { $$ = {alias: $2, hasAs: false} }
   ;
@@ -328,14 +326,6 @@ identifier_list
   : identifier { $$ = { type: 'IdentifierList', value: [ $1 ] } }
   | identifier_list ',' identifier { $$ = $1; $1.value.push($3); }
   ;
-quoted_identifier
-  : QUOTED_IDENTIFIER { $$ = { type: 'Identifier', value: $1 } }
-  | quoted_identifier DOT QUOTED_IDENTIFIER { $$ = $1; $1.value += '.' + $3 }
-  ;
-quoted_identifier_list
-  : quoted_identifier { $$ = { type: 'IdentifierList', value: [ $1 ] } }
-  | quoted_identifier_list ',' quoted_identifier { $$ = $1; $1.value.push($3); }
-  ;
 case_expr_opt
   : { $$ = null }
   | expr { $$ = $1 }
@@ -361,7 +351,6 @@ simple_expr_prefix
 simple_expr
   : literal { $$ = $1 }
   | identifier { $$ = $1 }
-  | quoted_identifier { $$ = $1 }
   | function_call { $$ = $1 }
   | simple_expr_prefix { $$ = $1 }
   | '(' expr_list ')' { $$ = { type: 'SimpleExprParentheses', value: $2 } }

From 6503d59caecc30c34983054d6780311d5cd32cd2 Mon Sep 17 00:00:00 2001
From: sockstack <85564748@qq.com>
Date: Mon, 31 Aug 2020 19:58:52 +0800
Subject: [PATCH 28/37] place holder support

${value} like value place holder support.
---
 src/sqlParser.jison |  5 +++++
 src/stringify.js    | 17 +++++++++++++++++
 test/main.test.js   |  6 ++++++
 3 files changed, 28 insertions(+)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 7cff3e8..110f77f 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -11,6 +11,7 @@
 [#]\s.*\n                                                         /* skip sql comments */
 \s+                                                               /* skip whitespace */
 
+[$][{](.*?)[}]                                                    return 'PLACE_HOLDER'
 [`][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[`]            return 'IDENTIFIER'
 [\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]*                   return 'IDENTIFIER'
 [\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]*                         return 'IDENTIFIER'
@@ -305,6 +306,7 @@ literal
   | number { $$ = $1 }
   | boolean { $$ = $1 }
   | null { $$ = $1 }
+  | place_holder { $$ = $1 }
   ;
 function_call
   : IDENTIFIER '(' function_call_param_list ')' { $$ = {type: 'FunctionCall', name: $1, params: $3} }
@@ -588,3 +590,6 @@ table_factor
   | '(' selectClause ')' aliasOpt { $$ = { type: 'TableFactor', value: { type: 'SubQuery', value: $2 }, alias: $4.alias, hasAs: $4.hasAs} }
   | '(' table_references ')' { $$ = $2; $$.hasParentheses = true }
   ;
+place_holder
+  : PLACE_HOLDER { $$ = { type: 'PlaceHolder', value: $1, param: $1.slice(2, -1)} }
+  ;
\ No newline at end of file
diff --git a/src/stringify.js b/src/stringify.js
index 57ea74e..635e801 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -551,3 +551,20 @@ Sql.prototype.travelSelectParenthesized = function(ast) {
   this.travel(ast.value);
   this.appendKeyword(')');
 };
+Sql.prototype.travelPlaceHolder = function (ast) {
+  if (ast.left) {
+    this.travel(ast.left);
+  }
+
+  if (ast.operator) {
+    this.append(ast.operator);
+  }
+  
+  if (ast.right) {
+    this.append(ast.right);
+  }
+
+  if (ast.value) {
+    this.travel(ast.value);
+  }
+};
\ No newline at end of file
diff --git a/test/main.test.js b/test/main.test.js
index 834d3f1..3d46b3d 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -411,4 +411,10 @@ describe('select grammar support', function() {
   it('bugfix table alias2', function() {
     testParser('select a.* from a t1 join b t2 on t1.a = t2.a')
   })
+
+  it('place holder support', function() {
+    testParser(
+    "select sum(quota_value) value, busi_col2 as sh, ${a} as a, YEAR(now()) from der_quota_summary where table_ename = 'gshmyyszje_derivedidx' and cd = (select id from t1 where a = ${t1})"
+    )
+  }) 
 });

From 2361c5723d9532703dee333883f82719c67b4bb2 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 4 Feb 2021 16:26:10 +0800
Subject: [PATCH 29/37] fix: #40

---
 src/sqlParser.jison | 5 +++--
 test/main.test.js   | 9 +++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 3e0f452..cc99391 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -126,8 +126,9 @@ UNION                                                             return 'UNION'
 
 [a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
-['"][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["']          return 'IDENTIFIER'
-([`])(?:(?=(\\?))\2.)*?\1                                         return 'IDENTIFIER'
+["][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["]            return 'STRING'
+['][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[']            return 'STRING'
+[`].+[`]                                                          return 'IDENTIFIER'
 
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
diff --git a/test/main.test.js b/test/main.test.js
index 8ba2249..88b9cda 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -422,4 +422,13 @@ describe('select grammar support', function () {
     testParser('select a as "A A" from z');
     testParser('select a as "A A" from z order by "A A" desc');
   });
+
+  it('support quoted alias', function () {
+    testParser('select a as \'A A\' from z');
+    testParser('select a as \'"A#A\' from z order by \'"A#A\' desc');
+  });
+
+  it.only('test IDENTIFIER', function () {
+    testParser('select `aa#sfs`(a) as \'A A\' from z');
+  });
 });

From b5450ee84ae0926693a50b5ed64f69e1fb9b6910 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 4 Feb 2021 16:36:46 +0800
Subject: [PATCH 30/37] release: v1.4.0

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

diff --git a/package.json b/package.json
index 3b620bc..df03c49 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.3.0",
+  "version": "1.4.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {

From 088e17b4bbaa39d807490a348173aa5fd84ef08f Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 4 Feb 2021 16:43:46 +0800
Subject: [PATCH 31/37] chore: readme & changelog.

---
 CHANGELOG | 1 +
 README.md | 1 +
 2 files changed, 2 insertions(+)

diff --git a/CHANGELOG b/CHANGELOG
index 3d1a64c..243b783 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,3 +8,4 @@
 1.2.1 fix stringify having #29 , pr: #28
 1.2.2 feat: add support for "`" quoted alias, pr: #33
 1.3.0 fix tableFactor alias bug. AST changed in tableFactor. #34
+1.4.0 fix bug `using ' & " for column alias?` #40 #44
diff --git a/README.md b/README.md
index 467f6e6..7f9fd26 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
+- Fix bug `using ' & " for column alias?` since v1.4.0 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
 - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
 - Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33)
 - Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)

From 09b15e42d288a34603999e9be305b7b33231f945 Mon Sep 17 00:00:00 2001
From: Albin Zeng <binwei.zbw@alibaba-inc.com>
Date: Thu, 4 Feb 2021 18:32:10 +0800
Subject: [PATCH 32/37] chore: hotfix support quoted alias: multiple alias and
 orderby support.

---
 CHANGELOG           | 1 +
 README.md           | 2 +-
 package.json        | 2 +-
 src/sqlParser.jison | 2 +-
 test/main.test.js   | 2 +-
 5 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 243b783..35c0dd0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,3 +9,4 @@
 1.2.2 feat: add support for "`" quoted alias, pr: #33
 1.3.0 fix tableFactor alias bug. AST changed in tableFactor. #34
 1.4.0 fix bug `using ' & " for column alias?` #40 #44
+1.4.1 hogfix "support quoted alias: multiple alias and orderby support"
diff --git a/README.md b/README.md
index 7f9fd26..ef48aab 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
-- Fix bug `using ' & " for column alias?` since v1.4.0 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
+- Fix bug `using ' & " for column alias?` since v1.4.1 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
 - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
 - Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33)
 - Fix bug stringify keyword `having` since v1.2.1. [#29](https://github.com/JavaScriptor/js-sql-parser/issues/29)
diff --git a/package.json b/package.json
index df03c49..23bf5b5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.4.0",
+  "version": "1.4.1",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {
diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index cc99391..86e13fc 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -128,7 +128,7 @@ UNION                                                             return 'UNION'
 \.                                                                return 'DOT'
 ["][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["]            return 'STRING'
 ['][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[']            return 'STRING'
-[`].+[`]                                                          return 'IDENTIFIER'
+([`])(?:(?=(\\?))\2.)*?\1                                         return 'IDENTIFIER'
 
 <<EOF>>                                                           return 'EOF'
 .                                                                 return 'INVALID'
diff --git a/test/main.test.js b/test/main.test.js
index 88b9cda..188f57d 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -428,7 +428,7 @@ describe('select grammar support', function () {
     testParser('select a as \'"A#A\' from z order by \'"A#A\' desc');
   });
 
-  it.only('test IDENTIFIER', function () {
+  it('test IDENTIFIER', function () {
     testParser('select `aa#sfs`(a) as \'A A\' from z');
   });
 });

From 8043656061ce969981ebe3b099d862ea82642ea7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A9=AC=E8=82=B1?= <binwei.zbw@antgroup.com>
Date: Mon, 30 Oct 2023 15:46:20 +0800
Subject: [PATCH 33/37] release: v1.5.0

---
 .gitignore          |  1 +
 CHANGELOG           |  1 +
 Makefile            |  5 ++++-
 README.md           | 15 +++++++++++----
 package.json        |  2 +-
 src/sqlParser.jison |  2 +-
 src/stringify.js    | 12 ------------
 test/main.test.js   | 14 ++++++++++++++
 8 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/.gitignore b/.gitignore
index c1119a3..1a8c027 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ package-lock.json
 *.log
 *.swp
 dist/parser/sqlParser.js
+.vscode/
diff --git a/CHANGELOG b/CHANGELOG
index 35c0dd0..dc5dd0a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,3 +10,4 @@
 1.3.0 fix tableFactor alias bug. AST changed in tableFactor. #34
 1.4.0 fix bug `using ' & " for column alias?` #40 #44
 1.4.1 hogfix "support quoted alias: multiple alias and orderby support"
+1.5.0 support feature placeholder.
diff --git a/Makefile b/Makefile
index 51dd70d..ca658ba 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,10 @@
 publish: test
 	@npm publish
 
-test: 
+test:
 	@npm test
 
+test-with-log:
+	@DEBUG=js-sql-parser npm test
+
 .PHONY: publish test
diff --git a/README.md b/README.md
index ef48aab..1960e46 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
+- Support feature `PlaceHolder like ${param}` since v1.5.0 [#43](https://github.com/JavaScriptor/js-sql-parser/pull/43)
 - Fix bug `using ' & " for column alias?` since v1.4.1 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
 - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
 - Add support for "`" quoted alias since v1.2.2. [#33](https://github.com/JavaScriptor/js-sql-parser/issues/33)
@@ -35,6 +36,16 @@ console.log(parser.stringify(ast));
 // SELECT foo FROM bar
 ```
 
+```js
+// placeholder test
+const parser = require('js-sql-parser');
+const ast = parser.parse('select ${a} as a');
+
+ast['value']['selectItems']['value'][0]['value'] = "'value'";
+console.log(parser.stringify(ast));
+// SELECT 'value' AS a
+```
+
 ## script tag
 
 ```js
@@ -60,10 +71,6 @@ var sql = sqlParser.stringify(ast);
 - intervalexpr: Date INTERVAL keyword.   // to support
 - into outfile: INTO OUTFILE keyword.    // to support
 
-## TODO
-
-- ${value} like value place holder support.
-
 ## Build
 
 - Run `npm run build` to build the distributable.
diff --git a/package.json b/package.json
index 23bf5b5..223a2f3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.4.1",
+  "version": "1.5.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {
diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 4a8efbc..ace8618 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -11,7 +11,7 @@
 [#]\s.*\n                                                         /* skip sql comments */
 \s+                                                               /* skip whitespace */
 
-[$][{](.*?)[}]                                                    return 'PLACE_HOLDER'
+[$][{](.+?)[}]                                                    return 'PLACE_HOLDER'
 [`][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[`]            return 'IDENTIFIER'
 [\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]*                   return 'IDENTIFIER'
 [\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]*                         return 'IDENTIFIER'
diff --git a/src/stringify.js b/src/stringify.js
index 635e801..4679850 100644
--- a/src/stringify.js
+++ b/src/stringify.js
@@ -552,18 +552,6 @@ Sql.prototype.travelSelectParenthesized = function(ast) {
   this.appendKeyword(')');
 };
 Sql.prototype.travelPlaceHolder = function (ast) {
-  if (ast.left) {
-    this.travel(ast.left);
-  }
-
-  if (ast.operator) {
-    this.append(ast.operator);
-  }
-  
-  if (ast.right) {
-    this.append(ast.right);
-  }
-
   if (ast.value) {
     this.travel(ast.value);
   }
diff --git a/test/main.test.js b/test/main.test.js
index 2ee9c1b..379f7f4 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -2,6 +2,7 @@
 
 const debug = require('debug')('js-sql-parser');
 const parser = require('../');
+const assert = require('assert');
 
 const testParser = function (sql) {
   let firstAst = parser.parse(sql);
@@ -416,6 +417,19 @@ describe('select grammar support', function () {
     "select sum(quota_value) value, busi_col2 as sh, ${a} as a, YEAR(now()) from der_quota_summary where table_ename = 'gshmyyszje_derivedidx' and cd = (select id from t1 where a = ${t1})"
     )
   });
+  it('place holder support2', function() {
+    testParser(
+    "select sum(quota_value) b, busi_col2 as sh, '${a}' as a, YEAR(now()) from der_quota_summary where table_ename = 'gshmyyszje_derivedidx' and cd = (select id from t1 where a = '${t1}')"
+    )
+  });
+  it('place holder support3', function() {
+    let firstAst = parser.parse('select ${a} as a');
+    firstAst['value']['selectItems']['value'][0]['value'] = "'value'";
+    let firstSql = parser.stringify(firstAst);
+    debug(JSON.stringify(firstAst, null, 2));
+    assert.equal(firstSql.trim().toUpperCase(), "select 'value' as a".toUpperCase());
+    testParser(firstSql);
+  });
 
   it('support quoted alias: multiple alias and orderby support', function () {
     testParser('select a as `A A`, b as `B B` from z');

From 203c5dea8614cb7afe4e3a04effc9131b9f6e54b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A9=AC=E8=82=B1?= <binwei.zbw@antgroup.com>
Date: Tue, 31 Oct 2023 10:51:15 +0800
Subject: [PATCH 34/37] chore: add placeholder Note to readme.

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 1960e46..ffa2b84 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,8 @@ console.log(parser.stringify(ast));
 // SELECT 'value' AS a
 ```
 
+Note: PlaceHolder is an `literal` value but not an `identifier`. Table_name / column_name / function_name are `identifier` thus should NOT be placed with placeholder.
+
 ## script tag
 
 ```js

From 199a1c6ce417defe64e5ccea7fa25d43239f6a5f Mon Sep 17 00:00:00 2001
From: Luiz Zappa <luizzappa@hotmail.com>
Date: Sun, 22 Oct 2023 15:02:06 -0300
Subject: [PATCH 35/37] feat(): unicode extended char support for column name
 or alias

---
 src/sqlParser.jison | 10 +++++-----
 test/main.test.js   | 12 ++++++++++++
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 4a8efbc..1db84b0 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -11,10 +11,10 @@
 [#]\s.*\n                                                         /* skip sql comments */
 \s+                                                               /* skip whitespace */
 
-[$][{](.*?)[}]                                                    return 'PLACE_HOLDER'
-[`][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[`]            return 'IDENTIFIER'
-[\w]+[\u4e00-\u9fa5]+[0-9a-zA-Z_\u4e00-\u9fa5]*                   return 'IDENTIFIER'
-[\u4e00-\u9fa5][0-9a-zA-Z_\u4e00-\u9fa5]*                         return 'IDENTIFIER'
+[$][{](.+?)[}]                                                    return 'PLACE_HOLDER'
+[`][a-zA-Z0-9_\u0080-\uFFFF]*[`]                                  return 'IDENTIFIER'
+[\w]+[\u0080-\uFFFF]+[0-9a-zA-Z_\u0080-\uFFFF]*                   return 'IDENTIFIER'
+[\u0080-\uFFFF][0-9a-zA-Z_\u0080-\uFFFF]*                         return 'IDENTIFIER'
 SELECT                                                            return 'SELECT'
 ALL                                                               return 'ALL'
 ANY                                                               return 'ANY'
@@ -125,7 +125,7 @@ UNION                                                             return 'UNION'
 [-]?[0-9]+(\.[0-9]+)?                                             return 'NUMERIC'
 [-]?[0-9]+(\.[0-9]+)?[eE][-][0-9]+(\.[0-9]+)?                     return 'EXPONENT_NUMERIC'
 
-[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*                  return 'IDENTIFIER'
+[a-zA-Z_\u0080-\uFFFF][a-zA-Z0-9_\u0080-\uFFFF]*                  return 'IDENTIFIER'
 \.                                                                return 'DOT'
 ["][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*["]            return 'STRING'
 ['][a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*[']            return 'STRING'
diff --git a/test/main.test.js b/test/main.test.js
index 2ee9c1b..d7a1496 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -436,4 +436,16 @@ describe('select grammar support', function () {
   it('test IDENTIFIER', function () {
     testParser('select `aa#sfs`(a) as \'A A\' from z');
   });
+
+  it('Support unicode extended char (U+0080..U+FFFF) as column name or alias', function() {
+    testParser(`select 
+      país, 
+      MAX(produção) as maior_produção, 
+      Ĉapelo,
+      Δάσος,
+      Молоко,
+      سلام,
+      かわいい
+    from table`)
+  })
 });

From 14b31468fffa081457dfb15022f9ba720ea4fa4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A9=AC=E8=82=B1?= <binwei.zbw@antgroup.com>
Date: Wed, 20 Mar 2024 15:31:06 +0800
Subject: [PATCH 36/37] feat: support function call in table_factor.

---
 README.md           | 1 +
 package.json        | 2 +-
 src/sqlParser.jison | 1 +
 test/main.test.js   | 6 ++++++
 4 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index ffa2b84..3ce1532 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
+- Unicode extended char support for column name or alias & Function call in `table_factor` since v1.6.0
 - Support feature `PlaceHolder like ${param}` since v1.5.0 [#43](https://github.com/JavaScriptor/js-sql-parser/pull/43)
 - Fix bug `using ' & " for column alias?` since v1.4.1 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
 - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
diff --git a/package.json b/package.json
index 223a2f3..bd3083b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-sql-parser",
-  "version": "1.5.0",
+  "version": "1.6.0",
   "description": "",
   "main": "./dist/parser/sqlParser.js",
   "scripts": {
diff --git a/src/sqlParser.jison b/src/sqlParser.jison
index 1db84b0..271c94a 100644
--- a/src/sqlParser.jison
+++ b/src/sqlParser.jison
@@ -588,6 +588,7 @@ table_factor
   : identifier partitionOpt aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, partition: $2, alias: $3.alias, hasAs: $3.hasAs, indexHintOpt: $4 } }
   | '(' selectClause ')' aliasOpt { $$ = { type: 'TableFactor', value: { type: 'SubQuery', value: $2 }, alias: $4.alias, hasAs: $4.hasAs} }
   | '(' table_references ')' { $$ = $2; $$.hasParentheses = true }
+  | function_call aliasOpt index_hint_list_opt { $$ = { type: 'TableFactor', value: $1, alias: $2.alias, hasAs: $2.hasAs, indexHintOpt: $3 } }
   ;
 place_holder
   : PLACE_HOLDER { $$ = { type: 'PlaceHolder', value: $1, param: $1.slice(2, -1)} }
diff --git a/test/main.test.js b/test/main.test.js
index e4af010..4bf50b6 100644
--- a/test/main.test.js
+++ b/test/main.test.js
@@ -462,4 +462,10 @@ describe('select grammar support', function () {
       かわいい
     from table`)
   })
+
+  it('#60 Call function in FROM clause', function() {
+    testParser(`
+    SELECT one.name, group_concat(j.value, ', ') FROM one, json_each(one.stringArray) AS j GROUP BY one.id
+    `)
+  })
 });

From 4baa4a937450b529fb31799d8912f4bc2f4a5776 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A9=AC=E8=82=B1?= <binwei.zbw@antgroup.com>
Date: Thu, 21 Mar 2024 11:00:50 +0800
Subject: [PATCH 37/37] release: v1.6.0

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 3ce1532..e113d48 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ sql grammar follows https://dev.mysql.com/doc/refman/5.7/en/select.html
 
 ## news
 
-- Unicode extended char support for column name or alias & Function call in `table_factor` since v1.6.0
+- Unicode extended char support for column name or alias & Function call in `table_factor` since v1.6.0 [#58](https://github.com/JavaScriptor/js-sql-parser/pull/58), [#60](https://github.com/JavaScriptor/js-sql-parser/issues/60)
 - Support feature `PlaceHolder like ${param}` since v1.5.0 [#43](https://github.com/JavaScriptor/js-sql-parser/pull/43)
 - Fix bug `using ' & " for column alias?` since v1.4.1 [#40](https://github.com/JavaScriptor/js-sql-parser/issues/40), [#44](https://github.com/JavaScriptor/js-sql-parser/issues/44)
 - Fix bug tableFactor alias since v1.3.0 [#34](https://github.com/JavaScriptor/js-sql-parser/issues/34)
