sql-ddl-to-json-schema
Version:
Parse and convert SQL DDL statements to a JSON Schema.
194 lines (193 loc) • 6.26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
const nearley_1 = require("nearley");
const utils_1 = require("./shared/utils");
const language_1 = require("./mysql/language");
const compact_1 = require("./mysql/formatter/compact");
const json_schema_1 = require("./mysql/formatter/json-schema");
/**
* Main Parser class, wraps nearley parser main methods.
*/
class Parser {
compiledGrammar;
parser;
jsonSchemaFormatter;
compactFormatter;
/**
* Parsed statements.
*/
statements = [];
/**
* Remains of string feed, after last parsed statement.
*/
remains = '';
/**
* Whether preparser is currently escaped.
*/
escaped = false;
/**
* Current quote char of preparser.
*/
quoted = '';
/**
* Parser constructor.
* Default dialect is 'mysql'.
*
* @param dialect SQL dialect ('mysql' or 'mariadb' currently supported).
*/
constructor(dialect = 'mysql') {
if (!dialect || dialect === 'mysql' || dialect === 'mariadb') {
this.compiledGrammar = nearley_1.Grammar.fromCompiled(language_1.Grammar);
this.compactFormatter = compact_1.format;
this.jsonSchemaFormatter = json_schema_1.format;
}
else {
throw new TypeError(`Unsupported SQL dialect given to parser: '${dialect}. ` +
"Please provide 'mysql', 'mariadb' or none to use default.");
}
this.resetParser();
}
/**
* Feed chunk of string into parser.
*
* @param chunk Chunk of string to be parsed.
*/
feed(chunk) {
let i;
let char;
let parsed = '';
let lastStatementIndex = 0;
for (i = 0; i < chunk.length; i += 1) {
char = chunk[i];
parsed += char;
if (char === '\\') {
this.escaped = !this.escaped;
}
else {
if (!this.escaped && Parser.isQuoteChar(char)) {
if (this.quoted) {
if (this.quoted === char) {
this.quoted = '';
}
}
else {
this.quoted = char;
}
}
else if (char === ';' && !this.quoted) {
const statement = this.remains + parsed.substr(lastStatementIndex, i + 1);
this.statements.push(statement);
this.remains = '';
lastStatementIndex = i + 1;
}
this.escaped = false;
}
}
this.remains += parsed.substr(lastStatementIndex);
return this;
}
/**
* Recreates NearleyParser using grammar given in constructor.
*/
resetParser() {
this.parser = new nearley_1.Parser(this.compiledGrammar);
}
/**
* Checks whether character is a quotation character.
*
* @param char Character to be evaluated.
*/
static isQuoteChar(char) {
return char === '"' || char === "'" || char === '`';
}
/**
* Tidy parser results.
*
* @param results Parser results.
*/
static tidy(results) {
return results[0];
}
/**
* Parser results getter. Will run nearley parser on string fed to this parser.
*/
get results() {
let lineCount = 1;
let statement = this.statements.shift();
const results = [];
/**
* Since we separate the statements, if there is a parse error in a block among
* several statements in a stream, the parser will throw the error with line
* 0 counting from the beginning of the statement, not the stream. So we
* need to catch and correct line count incrementally along the stream.
*
* https://github.com/duartealexf/sql-ddl-to-json-schema/issues/20
*/
try {
while (statement) {
this.parser.feed(statement);
lineCount += (statement.match(/\r\n|\r|\n/g) ?? []).length;
results.push(Parser.tidy(this.parser.results));
statement = this.statements.shift();
this.resetParser();
}
}
catch (e) {
const error = e;
/**
* Apply line count correction.
*/
if (error.message && (0, utils_1.isString)(error.message)) {
const matches = error.message.match(/at line (\d+)/);
if (matches && Array.isArray(matches) && matches.length > 1) {
const errorLine = Number(matches[1]);
const newCount = lineCount + errorLine - 1;
error.message = error.message.replace(/\d+/, newCount.toString());
}
}
/**
* Reset everything to not affect next feed.
*/
this.resetParser();
this.statements = [];
this.remains = '';
this.escaped = false;
this.quoted = '';
throw error;
}
/**
* Reset remains to not affect next feed.
*/
this.remains = '';
this.escaped = false;
this.quoted = '';
return {
id: 'MAIN',
def: results,
};
}
/**
* Formats given parsed JSON to a compact format.
* If no JSON is given, will use currently parsed SQL.
*
* @param json Parsed JSON format.
*/
toCompactJson(json) {
return this.compactFormatter(json ?? this.results);
}
/**
* Formats parsed SQL to an array of JSON Schema documents,
* where each item is the JSON Schema of a table. If no
* tables are given, will use currently parsed SQL.
*
* @param tables Array of tables in compact JSON format.
* @param options Options available to format as JSON Schema.
*/
toJsonSchemaArray(options = {
useRef: true,
}, tables) {
return this.jsonSchemaFormatter(tables ?? this.toCompactJson(), options);
}
}
exports.Parser = Parser;