diff --git a/src/index.ts b/src/index.ts index 6d87c17..4eec148 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,4 +10,5 @@ export * from './lib/plsql/PlSqlParserListener'; export * from './lib/plsql/PlSqlParserVisitor'; export * from './lib/spark/SparkSqlVisitor'; export * from './lib/spark/SparkSqlListener'; - +export * from './lib/pgsql/PostgreSQLParserListener'; +export * from './lib/pgsql/PostgreSQLParserVisitor'; diff --git a/src/lib/pgsql/base/LexerDispatchingErrorListener.java b/src/lib/pgsql/base.es6/LexerDispatchingErrorListener.java similarity index 100% rename from src/lib/pgsql/base/LexerDispatchingErrorListener.java rename to src/lib/pgsql/base.es6/LexerDispatchingErrorListener.java diff --git a/src/lib/pgsql/base/ParserDispatchingErrorListener.java b/src/lib/pgsql/base.es6/ParserDispatchingErrorListener.java similarity index 100% rename from src/lib/pgsql/base/ParserDispatchingErrorListener.java rename to src/lib/pgsql/base.es6/ParserDispatchingErrorListener.java diff --git a/src/lib/pgsql/base.es6/PostgreSQLLexerBase.js b/src/lib/pgsql/base.es6/PostgreSQLLexerBase.js new file mode 100644 index 0000000..6d2672b --- /dev/null +++ b/src/lib/pgsql/base.es6/PostgreSQLLexerBase.js @@ -0,0 +1,67 @@ +const antlr4 = require('antlr4/index'); +const Lexer = antlr4.Lexer; +function isLetter(str) { + return str.length === 1 && str.match(/[a-z]/i); +} +export class PostgreSQLLexerBase extends Lexer { + tags = []; + + constructor(input) { + super(input); + } + + pushTag() { + this.tags.push(getText()); + } + + isTag() { + return this.getText().equals(this.tags.peek()); + } + + popTag() { + tags.pop(); + } + + getInputStream() { + return this._input; + } + checkLA( c) { + // eslint-disable-next-line new-cap + return this.getInputStream().LA(1) !== c; + } + + charIsLetter() { + // eslint-disable-next-line new-cap + return isLetter(this.getInputStream().LA(-1)); + } + + HandleNumericFail() { + this.getInputStream().seek(this.getInputStream().index() - 2); + const Integral = 535; + this.setType(Integral); + } + + HandleLessLessGreaterGreater() { + const LESS_LESS = 18; + const GREATER_GREATER = 19; + if (this.getText() === '<<') this.setType(LESS_LESS); + if (this.getText() === '>>') this.setType(GREATER_GREATER); + } + + UnterminatedBlockCommentDebugAssert() { + // Debug.Assert(InputStream.LA(1) == -1 /*EOF*/); + } + + CheckIfUtf32Letter() { + // eslint-disable-next-line new-cap + let codePoint = this.getInputStream().LA(-2) << 8 + this.getInputStream().LA(-1); + let c; + if (codePoint < 0x10000) { + c = String.fromCharCode(codePoint); + } else { + codePoint -= 0x10000; + c = String.fromCharCode(codePoint / 0x400 + 0xd800, codePoint % 0x400 + 0xdc00); + } + return isLetter(c[0]); + } +} diff --git a/src/lib/pgsql/base.es6/PostgreSQLParserBase.js b/src/lib/pgsql/base.es6/PostgreSQLParserBase.js new file mode 100644 index 0000000..91b6263 --- /dev/null +++ b/src/lib/pgsql/base.es6/PostgreSQLParserBase.js @@ -0,0 +1,114 @@ +/* eslint-disable new-cap */ +import { PostgreSQLLexer } from '../PostgreSQLLexer'; +import { PostgreSQLParser } from '../PostgreSQLParser'; + + +const antlr4 = require('antlr4/index'); +const CharStreams = antlr4.CharStreams; +const CommonTokenStream = antlr4.CommonTokenStream; + + +// @ts-ignore +export class PostgreSQLParserBase extends antlr4.Parser { + constructor( input) { + super(input); + } + + GetParsedSqlTree( script, line) { + const ph = this.getPostgreSQLParser(script); + return ph.program(); + } + + ParseRoutineBody( _localctx) { + let lang = null; + for (const coi of _localctx.createfunc_opt_item()) { + // eslint-disable-next-line new-cap + if (!coi.LANGUAGE()) { + if (!coi.nonreservedword_or_sconst()) { + if (!coi.nonreservedword_or_sconst().nonreservedword()) { + if (!coi.nonreservedword_or_sconst().nonreservedword().identifier()) { + // eslint-disable-next-line new-cap + if (!coi.nonreservedword_or_sconst().nonreservedword().identifier().Identifier()) { + // eslint-disable-next-line new-cap + lang = coi.nonreservedword_or_sconst().nonreservedword().identifier().Identifier().getText(); + break; + } + } + } + } + } + } + if (!lang) return; + // eslint-disable-next-line camelcase + let func_as = null; + for (const a of _localctx.createfunc_opt_item()) { + if (!a.func_as()) { + // eslint-disable-next-line camelcase + func_as = a; + break; + } + } + // eslint-disable-next-line camelcase + if (!func_as) { + const txt = this.GetRoutineBodyString(func_as.func_as().sconst(0)); + const line = func_as.func_as().sconst(0).start.getLine(); + const ph = this.getPostgreSQLParser(txt); + switch (lang) { + case 'plpgsql': + func_as.func_as().Definition = ph.plsqlroot(); + break; + case 'sql': + func_as.func_as().Definition = ph.program(); + break; + } + } + } + + TrimQuotes( s) { + return (!s) ? s : s.substring(1, s.length() - 1); + } + + unquote( s) { + const slength = s.length(); + const r = ''; + let i = 0; + while (i < slength) { + const c = s.charAt(i); + r.append(c); + if (c === '\'' && i < slength - 1 && (s.charAt(i + 1) === '\'')) i++; + i++; + } + return r.toString(); + } + + GetRoutineBodyString( rule) { + const anysconst = rule.anysconst(); + // eslint-disable-next-line new-cap + const StringConstant = anysconst.StringConstant(); + if (null !== StringConstant) return this.unquote(this.TrimQuotes(StringConstant.getText())); + const UnicodeEscapeStringConstant = anysconst.UnicodeEscapeStringConstant(); + if (null !== UnicodeEscapeStringConstant) return this.TrimQuotes(UnicodeEscapeStringConstant.getText()); + const EscapeStringConstant = anysconst.EscapeStringConstant(); + if (null !== EscapeStringConstant) return this.TrimQuotes(EscapeStringConstant.getText()); + let result = ''; + const dollartext = anysconst.DollarText(); + for (const s of dollartext) { + result += s.getText(); + } + return result; + } + + static getPostgreSQLParser( script) { + const charStream = CharStreams.fromString(script); + const lexer = new PostgreSQLLexer(charStream); + const tokens = new CommonTokenStream(lexer); + const parser = new PostgreSQLParser(tokens); + lexer.removeErrorListeners(); + parser.removeErrorListeners(); + // LexerDispatchingErrorListener listener_lexer = new LexerDispatchingErrorListener((Lexer)(((CommonTokenStream)(this.getInputStream())).getTokenSource())); + // ParserDispatchingErrorListener listener_parser = new ParserDispatchingErrorListener(this); + // lexer.addErrorListener(listener_lexer); + // parser.addErrorListener(listener_parser); + return parser; + } +} diff --git a/src/lib/pgsql/base/PostgreSQLLexerBase.js b/src/lib/pgsql/base/PostgreSQLLexerBase.js index 6d2672b..6d53dff 100644 --- a/src/lib/pgsql/base/PostgreSQLLexerBase.js +++ b/src/lib/pgsql/base/PostgreSQLLexerBase.js @@ -1,67 +1,82 @@ -const antlr4 = require('antlr4/index'); -const Lexer = antlr4.Lexer; +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PostgreSQLLexerBase = void 0; +var antlr4 = require('antlr4/index'); +var Lexer = antlr4.Lexer; function isLetter(str) { return str.length === 1 && str.match(/[a-z]/i); } -export class PostgreSQLLexerBase extends Lexer { - tags = []; - - constructor(input) { - super(input); +var PostgreSQLLexerBase = /** @class */ (function (_super) { + __extends(PostgreSQLLexerBase, _super); + function PostgreSQLLexerBase(input) { + var _this = _super.call(this, input) || this; + _this.tags = []; + return _this; } - - pushTag() { + PostgreSQLLexerBase.prototype.pushTag = function () { this.tags.push(getText()); - } - - isTag() { + }; + PostgreSQLLexerBase.prototype.isTag = function () { return this.getText().equals(this.tags.peek()); - } - - popTag() { + }; + PostgreSQLLexerBase.prototype.popTag = function () { tags.pop(); - } - - getInputStream() { + }; + PostgreSQLLexerBase.prototype.getInputStream = function () { return this._input; - } - checkLA( c) { + }; + PostgreSQLLexerBase.prototype.checkLA = function (c) { // eslint-disable-next-line new-cap return this.getInputStream().LA(1) !== c; - } - - charIsLetter() { + }; + PostgreSQLLexerBase.prototype.charIsLetter = function () { // eslint-disable-next-line new-cap return isLetter(this.getInputStream().LA(-1)); - } - - HandleNumericFail() { + }; + PostgreSQLLexerBase.prototype.HandleNumericFail = function () { this.getInputStream().seek(this.getInputStream().index() - 2); - const Integral = 535; + var Integral = 535; this.setType(Integral); - } - - HandleLessLessGreaterGreater() { - const LESS_LESS = 18; - const GREATER_GREATER = 19; - if (this.getText() === '<<') this.setType(LESS_LESS); - if (this.getText() === '>>') this.setType(GREATER_GREATER); - } - - UnterminatedBlockCommentDebugAssert() { + }; + PostgreSQLLexerBase.prototype.HandleLessLessGreaterGreater = function () { + var LESS_LESS = 18; + var GREATER_GREATER = 19; + if (this.getText() === '<<') + this.setType(LESS_LESS); + if (this.getText() === '>>') + this.setType(GREATER_GREATER); + }; + PostgreSQLLexerBase.prototype.UnterminatedBlockCommentDebugAssert = function () { // Debug.Assert(InputStream.LA(1) == -1 /*EOF*/); - } - - CheckIfUtf32Letter() { + }; + PostgreSQLLexerBase.prototype.CheckIfUtf32Letter = function () { // eslint-disable-next-line new-cap - let codePoint = this.getInputStream().LA(-2) << 8 + this.getInputStream().LA(-1); - let c; + var codePoint = this.getInputStream().LA(-2) << 8 + this.getInputStream().LA(-1); + var c; if (codePoint < 0x10000) { c = String.fromCharCode(codePoint); - } else { + } + else { codePoint -= 0x10000; c = String.fromCharCode(codePoint / 0x400 + 0xd800, codePoint % 0x400 + 0xdc00); } return isLetter(c[0]); - } -} + }; + return PostgreSQLLexerBase; +}(Lexer)); +exports.PostgreSQLLexerBase = PostgreSQLLexerBase; \ No newline at end of file diff --git a/src/lib/pgsql/base/PostgreSQLParserBase.js b/src/lib/pgsql/base/PostgreSQLParserBase.js index 91b6263..22efa6e 100644 --- a/src/lib/pgsql/base/PostgreSQLParserBase.js +++ b/src/lib/pgsql/base/PostgreSQLParserBase.js @@ -1,27 +1,41 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PostgreSQLParserBase = void 0; /* eslint-disable new-cap */ -import { PostgreSQLLexer } from '../PostgreSQLLexer'; -import { PostgreSQLParser } from '../PostgreSQLParser'; - - -const antlr4 = require('antlr4/index'); -const CharStreams = antlr4.CharStreams; -const CommonTokenStream = antlr4.CommonTokenStream; - - +var PostgreSQLLexer_1 = require("../PostgreSQLLexer"); +var PostgreSQLParser_1 = require("../PostgreSQLParser"); +var antlr4 = require('antlr4/index'); +var CharStreams = antlr4.CharStreams; +var CommonTokenStream = antlr4.CommonTokenStream; // @ts-ignore -export class PostgreSQLParserBase extends antlr4.Parser { - constructor( input) { - super(input); +var PostgreSQLParserBase = /** @class */ (function (_super) { + __extends(PostgreSQLParserBase, _super); + function PostgreSQLParserBase(input) { + return _super.call(this, input) || this; } - - GetParsedSqlTree( script, line) { - const ph = this.getPostgreSQLParser(script); + PostgreSQLParserBase.prototype.GetParsedSqlTree = function (script, line) { + var ph = this.getPostgreSQLParser(script); return ph.program(); - } - - ParseRoutineBody( _localctx) { - let lang = null; - for (const coi of _localctx.createfunc_opt_item()) { + }; + PostgreSQLParserBase.prototype.ParseRoutineBody = function (_localctx) { + var lang = null; + for (var _i = 0, _a = _localctx.createfunc_opt_item(); _i < _a.length; _i++) { + var coi = _a[_i]; // eslint-disable-next-line new-cap if (!coi.LANGUAGE()) { if (!coi.nonreservedword_or_sconst()) { @@ -38,10 +52,12 @@ export class PostgreSQLParserBase extends antlr4.Parser { } } } - if (!lang) return; + if (!lang) + return; // eslint-disable-next-line camelcase - let func_as = null; - for (const a of _localctx.createfunc_opt_item()) { + var func_as = null; + for (var _b = 0, _c = _localctx.createfunc_opt_item(); _b < _c.length; _b++) { + var a = _c[_b]; if (!a.func_as()) { // eslint-disable-next-line camelcase func_as = a; @@ -50,59 +66,60 @@ export class PostgreSQLParserBase extends antlr4.Parser { } // eslint-disable-next-line camelcase if (!func_as) { - const txt = this.GetRoutineBodyString(func_as.func_as().sconst(0)); - const line = func_as.func_as().sconst(0).start.getLine(); - const ph = this.getPostgreSQLParser(txt); + var txt = this.GetRoutineBodyString(func_as.func_as().sconst(0)); + var line = func_as.func_as().sconst(0).start.getLine(); + var ph = this.getPostgreSQLParser(txt); switch (lang) { - case 'plpgsql': - func_as.func_as().Definition = ph.plsqlroot(); - break; - case 'sql': - func_as.func_as().Definition = ph.program(); - break; + case 'plpgsql': + func_as.func_as().Definition = ph.plsqlroot(); + break; + case 'sql': + func_as.func_as().Definition = ph.program(); + break; } } - } - - TrimQuotes( s) { + }; + PostgreSQLParserBase.prototype.TrimQuotes = function (s) { return (!s) ? s : s.substring(1, s.length() - 1); - } - - unquote( s) { - const slength = s.length(); - const r = ''; - let i = 0; + }; + PostgreSQLParserBase.prototype.unquote = function (s) { + var slength = s.length(); + var r = ''; + var i = 0; while (i < slength) { - const c = s.charAt(i); + var c = s.charAt(i); r.append(c); - if (c === '\'' && i < slength - 1 && (s.charAt(i + 1) === '\'')) i++; + if (c === '\'' && i < slength - 1 && (s.charAt(i + 1) === '\'')) + i++; i++; } return r.toString(); - } - - GetRoutineBodyString( rule) { - const anysconst = rule.anysconst(); + }; + PostgreSQLParserBase.prototype.GetRoutineBodyString = function (rule) { + var anysconst = rule.anysconst(); // eslint-disable-next-line new-cap - const StringConstant = anysconst.StringConstant(); - if (null !== StringConstant) return this.unquote(this.TrimQuotes(StringConstant.getText())); - const UnicodeEscapeStringConstant = anysconst.UnicodeEscapeStringConstant(); - if (null !== UnicodeEscapeStringConstant) return this.TrimQuotes(UnicodeEscapeStringConstant.getText()); - const EscapeStringConstant = anysconst.EscapeStringConstant(); - if (null !== EscapeStringConstant) return this.TrimQuotes(EscapeStringConstant.getText()); - let result = ''; - const dollartext = anysconst.DollarText(); - for (const s of dollartext) { + var StringConstant = anysconst.StringConstant(); + if (null !== StringConstant) + return this.unquote(this.TrimQuotes(StringConstant.getText())); + var UnicodeEscapeStringConstant = anysconst.UnicodeEscapeStringConstant(); + if (null !== UnicodeEscapeStringConstant) + return this.TrimQuotes(UnicodeEscapeStringConstant.getText()); + var EscapeStringConstant = anysconst.EscapeStringConstant(); + if (null !== EscapeStringConstant) + return this.TrimQuotes(EscapeStringConstant.getText()); + var result = ''; + var dollartext = anysconst.DollarText(); + for (var _i = 0, dollartext_1 = dollartext; _i < dollartext_1.length; _i++) { + var s = dollartext_1[_i]; result += s.getText(); } return result; - } - - static getPostgreSQLParser( script) { - const charStream = CharStreams.fromString(script); - const lexer = new PostgreSQLLexer(charStream); - const tokens = new CommonTokenStream(lexer); - const parser = new PostgreSQLParser(tokens); + }; + PostgreSQLParserBase.getPostgreSQLParser = function (script) { + var charStream = CharStreams.fromString(script); + var lexer = new PostgreSQLLexer_1.PostgreSQLLexer(charStream); + var tokens = new CommonTokenStream(lexer); + var parser = new PostgreSQLParser_1.PostgreSQLParser(tokens); lexer.removeErrorListeners(); parser.removeErrorListeners(); // LexerDispatchingErrorListener listener_lexer = new LexerDispatchingErrorListener((Lexer)(((CommonTokenStream)(this.getInputStream())).getTokenSource())); @@ -110,5 +127,7 @@ export class PostgreSQLParserBase extends antlr4.Parser { // lexer.addErrorListener(listener_lexer); // parser.addErrorListener(listener_parser); return parser; - } -} + }; + return PostgreSQLParserBase; +}(antlr4.Parser)); +exports.PostgreSQLParserBase = PostgreSQLParserBase; \ No newline at end of file diff --git a/src/parser/index.ts b/src/parser/index.ts index a541081..0f3c888 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -3,3 +3,4 @@ export { default as PLSQL } from './plsql'; export { default as HiveSQL } from './hive'; export { default as FlinkSQL } from './flinksql'; export { default as SparkSQL } from './spark'; +export { default as PostgresSQL } from './pgsql'; diff --git a/src/parser/pgsql.ts b/src/parser/pgsql.ts index 61a4314..66a3714 100644 --- a/src/parser/pgsql.ts +++ b/src/parser/pgsql.ts @@ -4,7 +4,7 @@ import { PostgreSQLParser } from '../lib/pgsql/PostgreSQLParser'; import BasicParser from './common/basicParser'; -export default class PLSQLParser extends BasicParser { +export default class PostgresSQL extends BasicParser { public createLexer(input: string): Lexer { const chars = new InputStream(input.toUpperCase()); const lexer = new PostgreSQLLexer(chars) as Lexer; diff --git a/test/parser/pgsql/lexer.test.ts b/test/parser/pgsql/lexer.test.ts new file mode 100644 index 0000000..e8a804f --- /dev/null +++ b/test/parser/pgsql/lexer.test.ts @@ -0,0 +1,12 @@ +import { PostgresSQL } from '../../../src/'; + +describe('PostgresSQL Lexer tests', () => { + const mysqlParser = new PostgresSQL(); + + const sql = 'select id,name,sex from user1;'; + const tokens = mysqlParser.getAllTokens(sql); + + test('token counts', () => { + expect(tokens.length).toBe(12); + }); +}); diff --git a/test/parser/pgsql/listener.test.ts b/test/parser/pgsql/listener.test.ts new file mode 100644 index 0000000..7b964dc --- /dev/null +++ b/test/parser/pgsql/listener.test.ts @@ -0,0 +1,25 @@ +import { PostgresSQL, PostgreSQLParserListener } from '../../../src'; + +describe('PostgresSQL Listener Tests', () => { + const expectTableName = 'user1'; + const sql = `select id,name,sex from ${expectTableName};`; + const parser = new PostgresSQL(); + + const parserTree = parser.parse(sql); + + console.log('Parser tree string:', parserTree); + + test('Listener enterTableName', async () => { + let result = ''; + class MyListener extends PostgreSQLParserListener { + // eslint-disable-next-line camelcase + enterTable_ref(ctx): void { + result = ctx.getText().toLowerCase(); + } + } + const listenTableName: any = new MyListener(); + + await parser.listen(listenTableName, parserTree); + expect(result).toBe(expectTableName); + }); +}); diff --git a/test/parser/pgsql/syntax.test.ts b/test/parser/pgsql/syntax.test.ts new file mode 100644 index 0000000..c80ee73 --- /dev/null +++ b/test/parser/pgsql/syntax.test.ts @@ -0,0 +1,18 @@ +import { PostgresSQL } from '../../../src'; + +describe('Generic SQL Syntax Tests', () => { + const parser = new PostgresSQL(); + + test('Select Statement', () => { + const sql = 'select id,name from user1;'; + const result = parser.validate(sql); + + expect(result.length).toBe(0); + }); + + test('Select 1+1', () => { + const sql = 'SELECT 1+1;'; + const result = parser.validate(sql); + expect(result.length).toBe(0); + }); +}); diff --git a/test/parser/pgsql/visitor.test.ts b/test/parser/pgsql/visitor.test.ts new file mode 100644 index 0000000..a32fef9 --- /dev/null +++ b/test/parser/pgsql/visitor.test.ts @@ -0,0 +1,26 @@ +import { PostgresSQL, PostgreSQLParserVisitor } from '../../../src'; + +describe('Generic SQL Visitor Tests', () => { + const expectTableName = 'user1'; + const sql = `select id,name,sex from ${expectTableName};`; + const parser = new PostgresSQL(); + + const parserTree = parser.parse(sql, (error) => { + console.log('Parse error:', error); + }); + + test('Visitor visitTableName', () => { + let result = ''; + class MyVisitor extends PostgreSQLParserVisitor { + // eslint-disable-next-line camelcase + visitTable_ref(ctx): void { + result = ctx.getText().toLowerCase(); + super.visitTable_ref(ctx); + } + } + const visitor: any = new MyVisitor(); + visitor.visit(parserTree); + + expect(result).toBe(expectTableName); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 507a25f..f42337b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "./dist/", "sourceMap": true, "allowJs":true, - "target": "es5", + "target": "es6", "module": "commonjs", "declaration": true, "noUnusedLocals": true,