rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
99 lines • 4.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EscapedIdentifierTokenReader = void 0;
const BaseTokenReader_1 = require("./BaseTokenReader");
const Lexeme_1 = require("../models/Lexeme");
/**
* Reads SQL identifier tokens
*/
class EscapedIdentifierTokenReader extends BaseTokenReader_1.BaseTokenReader {
/**
* Try to read an identifier token
*/
tryRead(previous) {
if (this.isEndOfInput()) {
return null;
}
const char = this.input[this.position];
// MySQL escaped identifier (escape character is backtick)
if (char === '`') {
const identifier = this.readEscapedIdentifier('`');
return this.createLexeme(Lexeme_1.TokenType.Identifier, identifier);
}
// Postgres escaped identifier (escape character is double quote)
if (char === '"') {
const identifier = this.readEscapedIdentifier('"');
return this.createLexeme(Lexeme_1.TokenType.Identifier, identifier);
}
// SQLServer escaped identifier (escape character is square bracket)
if (char === '[' && this.isSqlServerBracketIdentifier(previous)) {
const identifier = this.readEscapedIdentifier(']');
return this.createLexeme(Lexeme_1.TokenType.Identifier, identifier);
}
return null;
}
/**
* Check if the bracket at current position represents a SQL Server bracket identifier
* vs array access notation
*/
isSqlServerBracketIdentifier(previous) {
// Don't treat as SQL Server bracket identifier if previous token suggests array access
if ((previous === null || previous === void 0 ? void 0 : previous.value) === "array") {
return false;
}
// Look ahead to see what's inside the brackets
const start = this.position + 1; // after opening bracket
let pos = start;
// Scan until closing bracket or end of input
while (pos < this.input.length && this.input[pos] !== ']') {
const char = this.input[pos];
// If we find colons, operators, or expressions, it's likely array access
if (char === ':' || char === ',' || char === '+' || char === '-' ||
char === '*' || char === '/' || char === '(' || char === ')') {
return false;
}
pos++;
}
// If we didn't find a closing bracket, it's malformed anyway
if (pos >= this.input.length) {
return false;
}
// Check the content between brackets
const content = this.input.slice(start, pos).trim();
// Empty brackets are never SQL Server identifiers - they should be array access
if (content === '') {
return false;
}
// SQL Server bracket identifiers typically contain simple identifiers with dots
// Array access contains numbers, expressions, colons
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(content);
}
/**
* Read an escaped identifier (surrounded by delimiters)
*/
readEscapedIdentifier(delimiter) {
const start = this.position;
// Skip the opening delimiter
this.position++;
let foundClosing = false;
while (this.canRead()) {
if (this.input[this.position] === delimiter) {
foundClosing = true;
break;
}
this.position++;
}
if (!foundClosing) {
throw new Error(`Closing delimiter is not found. position: ${start}, delimiter: ${delimiter}\n${this.getDebugPositionInfo(start)}`);
}
if (start === this.position) {
throw new Error(`Closing delimiter is not found. position: ${start}, delimiter: ${delimiter}\n${this.getDebugPositionInfo(start)}`);
}
// Skip the closing delimiter
this.position++;
// exclude the delimiter
return this.input.slice(start + 1, this.position - 1);
}
}
exports.EscapedIdentifierTokenReader = EscapedIdentifierTokenReader;
//# sourceMappingURL=EscapedIdentifierTokenReader.js.map