in gui/frontend/src/parsing/mysql/MySQLParsingServices.ts [370:737]
public determineStatementRanges(sql: string, delimiter: string, serverVersion: number): IStatementSpan[] {
const result: IStatementSpan[] = [];
if (sql.length === 0) {
return result;
}
let start = 0; // Start of the current statement.
let head = start; // Tracks the current content position in the current token.
let tail = head;
const end = head + sql.length;
let haveContent = false; // Set when anything else but comments were found for the current statement.
/**
* Checks the current tail position if that touches a delimiter. If that's the case then the current statement
* is finished and a new one starts.
*
* @returns True if a delimiter was found, otherwise false.
*/
const checkDelimiter = (): boolean => {
if (sql[tail] === delimiter[0]) {
// Found possible start of the delimiter. Check if it really is.
if (delimiter.length === 1) {
// Most common case.
++tail;
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.Complete,
});
head = tail;
start = head;
haveContent = false;
return true;
} else {
// Multi character delimiter?
const candidate = sql.substring(tail, tail + delimiter.length);
if (candidate === delimiter) {
// Multi char delimiter is complete. Tail still points to the start of the delimiter.
tail += delimiter.length;
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.Complete,
});
head = tail;
start = head;
haveContent = false;
return true;
}
}
}
return false;
};
/**
* Strings Comments and Delimiter switch case handler
*/
const handleStringsCommentsAndDelimiter = (): void => {
switch (sql[tail]) {
case "/": { // Possible multi line comment or hidden (conditional) command.
if (sql[tail + 1] === "*") {
if (sql[tail + 2] === "!") { // Hidden command.
if (!haveContent) {
haveContent = true;
head = tail;
}
++tail;
}
tail += 2;
while (true) {
while (tail < end && sql[tail] !== "*") {
++tail;
}
if (tail === end) { // Unfinished multiline comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
} else {
if (sql[++tail] === "/") {
++tail; // Skip the slash too.
break;
}
}
}
if (!haveContent) {
head = tail; // Skip over the comment.
}
} else {
++tail;
haveContent = true;
}
break;
}
case "-": { // Possible single line comment.
const temp = tail + 2;
if (sql[tail + 1] === "-" && (sql[temp] === " " || sql[temp] === "\t" ||
sql[temp] === "\n")) {
// Skip everything until the end of the line.
tail += 2;
while (tail < end && sql[tail] !== "\n") {
++tail;
}
if (tail === end) { // Unfinished single line comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
}
if (!haveContent) {
head = tail;
}
} else {
++tail;
haveContent = true;
}
break;
}
case "#": { // MySQL single line comment.
while (tail < end && sql[tail] !== "\n") {
++tail;
}
if (tail === end) { // Unfinished single line comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
}
if (!haveContent) {
head = tail;
}
break;
}
case '"':
case "'":
case "`": { // Quoted string/id. Skip this in a local loop.
haveContent = true;
const quote = sql[tail++];
while (tail < end && sql[tail] !== quote) {
// Skip any escaped character too.
if (sql[tail] === "\\") {
++tail;
}
++tail;
}
if (sql[tail] === quote) {
++tail; // Skip trailing quote char if one was there.
} else { // Unfinished single string.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.OpenString,
});
start = tail;
head = tail;
}
break;
}
case "d":
case "D": {
// Possible start of the DELIMITER word. Also consider the mandatory space char.
if (tail + 10 >= end) {
if (!haveContent) {
haveContent = true;
head = tail;
}
++tail;
break; // Not enough input for that.
}
const candidate = sql.substring(tail, tail + 10);
if (candidate.match(MySQLParsingServices.delimiterKeyword)) {
// Delimiter keyword found - get the new delimiter (all consecutive letters).
// But first push anything we found so far and haven't pushed yet.
if (haveContent && tail > start) {
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: head,
state: StatementFinishState.NoDelimiter,
});
start = tail;
}
head = tail;
tail += 10;
let run = tail;
// Skip leading spaces + tabs.
while (run < end && (sql[run] === " " || sql[run] === "\t")) {
++run;
}
tail = run;
// Forward to the first whitespace after the current position (on this line).
while (run < end && sql[run] !== "\n" && sql[run] !== " " && sql[run] !== "\t") {
++run;
}
delimiter = sql.substring(tail, run);
const length = delimiter.length;
if (length > 0) {
tail += length - delimiter.length;
result.push({
delimiter,
span: { start, length: run - start },
contentStart: head,
state: StatementFinishState.DelimiterChange,
});
tail = run;
head = tail;
start = head;
haveContent = false;
} else {
haveContent = true;
head = tail;
}
} else {
++tail;
if (!haveContent) {
haveContent = true;
head = tail;
}
}
break;
}
default:
if (!haveContent && sql[tail] > " ") {
haveContent = true;
head = tail;
}
++tail;
break;
}
};
if (serverVersion >= 80100) {
while (tail < end) {
if (!checkDelimiter()) {
// dollar-quoted string
if (sql[tail] === "$") {
haveContent = true;
const quote = "$";
let dollarQuoteStartStr = "";
let dollarQuoteEndStr = "";
// To find the string content between two dollar sign after AS keyword
const dollarQuoteStartIndex = sql.indexOf("$", tail + 1);
if (dollarQuoteStartIndex > 0) {
dollarQuoteStartStr = sql.substring(tail, dollarQuoteStartIndex + 1);
tail = dollarQuoteStartIndex + 1;
}
while (tail < end) {
if (sql[tail] === quote) {
// To find the string content between two dollar sign in the body or end
const dollarQuoteEndIndex = sql.indexOf("$", tail + 1);
if (dollarQuoteEndIndex > 0) {
dollarQuoteEndStr = sql.substring(tail, dollarQuoteEndIndex + 1);
tail = dollarQuoteEndIndex;
}
if (dollarQuoteStartStr !== dollarQuoteEndStr) {
dollarQuoteEndStr = "";
} else {
break;
}
}
++tail;
}
if (sql[tail] === quote) {
++tail;
} else {
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start,
state: StatementFinishState.OpenString,
});
start = tail;
head = tail;
}
} else {
handleStringsCommentsAndDelimiter();
}
}
}
} else {
while (tail < end) {
if (!checkDelimiter()) {
handleStringsCommentsAndDelimiter();
}
}
}
// Add remaining text to the range list.
if (head < end) {
result.push({
span: { start, length: end - start },
contentStart: haveContent ? head : start - 1, // -1 to indicate no content
state: StatementFinishState.NoDelimiter,
});
} else if (head > start) {
// Last statement consists solely of whitespaces and/or comments.
// Which also means haveContent is false.
result.push({
span: { start, length: end - start },
contentStart: start - 1,
state: StatementFinishState.NoDelimiter,
});
}
return result;
}