UNPKG

sql-ddl-to-json-schema

Version:

Parse and convert SQL DDL statements to a JSON Schema.

194 lines (193 loc) 6.26 kB
"use strict"; 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;