1026 lines
30 KiB
JavaScript
1026 lines
30 KiB
JavaScript
|
const Errors = require('../misc/errors');
|
||
|
|
||
|
const State = {
|
||
|
Normal: 1 /* inside query */,
|
||
|
String: 2 /* inside string */,
|
||
|
SlashStarComment: 3 /* inside slash-star comment */,
|
||
|
Escape: 4 /* found backslash */,
|
||
|
EOLComment: 5 /* # comment, or // comment, or -- comment */,
|
||
|
Backtick: 6 /* found backtick */,
|
||
|
Placeholder: 7 /* found placeholder */
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Split query according to parameters (question mark).
|
||
|
* Question mark in comment are not taken in account
|
||
|
*
|
||
|
* @returns {Array} query separated by parameters
|
||
|
*/
|
||
|
module.exports.splitQuery = function (sql) {
|
||
|
let partList = [];
|
||
|
let state = State.Normal;
|
||
|
let lastChar = '\0';
|
||
|
let singleQuotes = false;
|
||
|
let lastParameterPosition = 0;
|
||
|
let idx = 0;
|
||
|
let car = sql.charAt(idx++);
|
||
|
|
||
|
while (car !== '') {
|
||
|
if (
|
||
|
state === State.Escape &&
|
||
|
!((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
|
||
|
) {
|
||
|
state = State.String;
|
||
|
car = sql.charAt(idx++);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (car) {
|
||
|
case '*':
|
||
|
if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
if (state === State.Normal) state = State.EOLComment;
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
if (state === State.Normal && lastChar == '-') {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
if (state === State.EOLComment) {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '"':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.String && !singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && !singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "'":
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = true;
|
||
|
} else if (state === State.String && singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
if (state === State.String) state = State.Escape;
|
||
|
break;
|
||
|
|
||
|
case '?':
|
||
|
if (state === State.Normal) {
|
||
|
partList.push(sql.substring(lastParameterPosition, idx - 1));
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
break;
|
||
|
case '`':
|
||
|
if (state === State.Backtick) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Normal) {
|
||
|
state = State.Backtick;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
lastChar = car;
|
||
|
|
||
|
car = sql.charAt(idx++);
|
||
|
}
|
||
|
if (lastParameterPosition === 0) {
|
||
|
partList.push(sql);
|
||
|
} else {
|
||
|
partList.push(sql.substring(lastParameterPosition));
|
||
|
}
|
||
|
|
||
|
return partList;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Split query according to parameters using placeholder.
|
||
|
*
|
||
|
* @param sql sql with placeholders
|
||
|
* @param info connection information
|
||
|
* @param initialValues placeholder object
|
||
|
* @param displaySql display sql function
|
||
|
* @returns {{parts: Array, values: Array}}
|
||
|
*/
|
||
|
module.exports.splitQueryPlaceholder = function (sql, info, initialValues, displaySql) {
|
||
|
let partList = [];
|
||
|
let values = [];
|
||
|
let state = State.Normal;
|
||
|
let lastChar = '\0';
|
||
|
|
||
|
let singleQuotes = false;
|
||
|
let lastParameterPosition = 0;
|
||
|
|
||
|
let idx = 0;
|
||
|
let car = sql.charAt(idx++);
|
||
|
let placeholderName;
|
||
|
|
||
|
while (car !== '') {
|
||
|
if (
|
||
|
state === State.Escape &&
|
||
|
!((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
|
||
|
) {
|
||
|
state = State.String;
|
||
|
car = sql.charAt(idx++);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (car) {
|
||
|
case '*':
|
||
|
if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
if (state === State.Normal) state = State.EOLComment;
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
if (state === State.Normal && lastChar == '-') {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
if (state === State.EOLComment) {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '"':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.String && !singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && !singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "'":
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = true;
|
||
|
} else if (state === State.String && singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.Escape && singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
if (state === State.String) state = State.Escape;
|
||
|
break;
|
||
|
|
||
|
case ':':
|
||
|
if (state === State.Normal) {
|
||
|
partList.push(sql.substring(lastParameterPosition, idx - 1));
|
||
|
placeholderName = '';
|
||
|
while (
|
||
|
((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
|
||
|
(car >= 'A' && car <= 'Z') ||
|
||
|
(car >= 'a' && car <= 'z') ||
|
||
|
car === '-' ||
|
||
|
car === '_'
|
||
|
) {
|
||
|
placeholderName += car;
|
||
|
}
|
||
|
idx--;
|
||
|
const val = initialValues[placeholderName];
|
||
|
if (val === undefined) {
|
||
|
throw Errors.createError(
|
||
|
"Placeholder '" + placeholderName + "' is not defined\n" + displaySql.call(),
|
||
|
false,
|
||
|
info,
|
||
|
'HY000',
|
||
|
Errors.ER_PLACEHOLDER_UNDEFINED
|
||
|
);
|
||
|
}
|
||
|
values.push(val);
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
break;
|
||
|
case '`':
|
||
|
if (state === State.Backtick) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Normal) {
|
||
|
state = State.Backtick;
|
||
|
}
|
||
|
}
|
||
|
lastChar = car;
|
||
|
|
||
|
car = sql.charAt(idx++);
|
||
|
}
|
||
|
if (lastParameterPosition === 0) {
|
||
|
partList.push(sql);
|
||
|
} else {
|
||
|
partList.push(sql.substring(lastParameterPosition));
|
||
|
}
|
||
|
|
||
|
return { parts: partList, values: values };
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Split query according to parameters (question mark).
|
||
|
*
|
||
|
* The only rewritten queries follow these notation: INSERT [LOW_PRIORITY | DELAYED |
|
||
|
* HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [PARTITION (partition_list)] [(col,...)] {VALUES |
|
||
|
* VALUE} (...) [ ON DUPLICATE KEY UPDATE col=expr [, col=expr] ... ] With expr without
|
||
|
* parameter.
|
||
|
*
|
||
|
* Query with INSERT ... SELECT / containing LAST_INSERT_ID() will not be rewritten
|
||
|
*
|
||
|
* query parts will be split this way :
|
||
|
* - pre-value part
|
||
|
* - after value part
|
||
|
* [- after parameter part] (after each parameter)
|
||
|
* - ending part
|
||
|
*
|
||
|
* example : INSERT INTO MyTABLE VALUES (9, ?, 5, ?, 8) ON DUPLICATE KEY UPDATE col2=col2+10
|
||
|
* will result in :
|
||
|
* - pre-value : "INSERT INTO MyTABLE VALUES"
|
||
|
* - after value : " (9, "
|
||
|
* - after parameter : ", 5, "
|
||
|
* - after parameter : ", 8)"
|
||
|
* - ending : " ON DUPLICATE KEY UPDATE col2=col2+10"
|
||
|
*
|
||
|
*
|
||
|
* @returns {JSON} query separated by parameters
|
||
|
*/
|
||
|
module.exports.splitRewritableQuery = function (sql) {
|
||
|
let reWritablePrepare = true;
|
||
|
let multipleQueriesPrepare = true;
|
||
|
let partList = [];
|
||
|
let lastChar = '\0';
|
||
|
|
||
|
let lastParameterPosition = 0;
|
||
|
|
||
|
let preValuePart1 = null;
|
||
|
let preValuePart2 = null;
|
||
|
let postValuePart = null;
|
||
|
|
||
|
let singleQuotes = false;
|
||
|
|
||
|
let isInParenthesis = 0;
|
||
|
let isFirstChar = true;
|
||
|
let isInsert = false;
|
||
|
let semicolon = false;
|
||
|
let hasParam = false;
|
||
|
let state = State.Normal;
|
||
|
|
||
|
let idx = 0;
|
||
|
let car = sql.charAt(idx++);
|
||
|
while (car !== '') {
|
||
|
if (
|
||
|
state === State.Escape &&
|
||
|
!((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
|
||
|
) {
|
||
|
state = State.String;
|
||
|
car = sql.charAt(idx++);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (car) {
|
||
|
case '*':
|
||
|
if (state === State.Normal && lastChar == '/') {
|
||
|
state = State.SlashStarComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
if (state === State.SlashStarComment && lastChar == '*') {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
if (state === State.Normal && lastChar == '-') {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
if (state === State.EOLComment) {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '"':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.String && !singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && !singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
case ';':
|
||
|
if (state === State.Normal) {
|
||
|
semicolon = true;
|
||
|
multipleQueriesPrepare = false;
|
||
|
}
|
||
|
break;
|
||
|
case "'":
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = true;
|
||
|
} else if (state === State.String && singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
if (state === State.String) {
|
||
|
state = State.Escape;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '?':
|
||
|
if (state === State.Normal) {
|
||
|
hasParam = true;
|
||
|
let part = sql.substring(lastParameterPosition, idx - 1);
|
||
|
lastParameterPosition = idx;
|
||
|
|
||
|
if (preValuePart1 === null) {
|
||
|
preValuePart1 = part;
|
||
|
preValuePart2 = '';
|
||
|
} else if (preValuePart2 === null) {
|
||
|
preValuePart2 = part;
|
||
|
} else {
|
||
|
if (postValuePart) {
|
||
|
//having parameters after the last ")" of value is not rewritable
|
||
|
reWritablePrepare = false;
|
||
|
partList.push(postValuePart + part);
|
||
|
postValuePart = null;
|
||
|
} else partList.push(part);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case '`':
|
||
|
if (state === State.Backtick) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Normal) {
|
||
|
state = State.Backtick;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
postValuePart === null &&
|
||
|
sql.length > idx + 5 &&
|
||
|
(sql.charAt(idx) === 'e' || sql.charAt(idx) === 'E') &&
|
||
|
(sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
|
||
|
(sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
|
||
|
(sql.charAt(idx + 3) === 'c' || sql.charAt(idx + 3) === 'C') &&
|
||
|
(sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T')
|
||
|
) {
|
||
|
//field/table name might contain 'select'
|
||
|
if (
|
||
|
idx > 1 &&
|
||
|
sql.charAt(idx - 2) > ' ' &&
|
||
|
'();><=-+,'.indexOf(sql.charAt(idx - 2)) === -1
|
||
|
) {
|
||
|
break;
|
||
|
}
|
||
|
if (sql.charAt(idx + 5) > ' ' && '();><=-+,'.indexOf(sql.charAt(idx + 5)) === -1) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//SELECT queries, INSERT FROM SELECT not rewritable
|
||
|
reWritablePrepare = false;
|
||
|
}
|
||
|
break;
|
||
|
case 'v':
|
||
|
case 'V':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
!preValuePart1 &&
|
||
|
(lastChar == ')' || lastChar <= ' ') &&
|
||
|
sql.length > idx + 6 &&
|
||
|
(sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
|
||
|
(sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
|
||
|
(sql.charAt(idx + 2) === 'u' || sql.charAt(idx + 2) === 'U') &&
|
||
|
(sql.charAt(idx + 3) === 'e' || sql.charAt(idx + 3) === 'E') &&
|
||
|
(sql.charAt(idx + 4) === 's' || sql.charAt(idx + 4) === 'S') &&
|
||
|
(sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
|
||
|
) {
|
||
|
idx += 5;
|
||
|
preValuePart1 = sql.substring(lastParameterPosition, idx);
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
break;
|
||
|
case 'l':
|
||
|
case 'L':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
sql.length > idx + 13 &&
|
||
|
(sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
|
||
|
(sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
|
||
|
(sql.charAt(idx + 2) === 't' || sql.charAt(idx + 2) === 'T') &&
|
||
|
sql.charAt(idx + 3) === '_' &&
|
||
|
(sql.charAt(idx + 4) === 'i' || sql.charAt(idx + 4) === 'I') &&
|
||
|
(sql.charAt(idx + 5) === 'n' || sql.charAt(idx + 5) === 'N') &&
|
||
|
(sql.charAt(idx + 6) === 's' || sql.charAt(idx + 6) === 'S') &&
|
||
|
(sql.charAt(idx + 7) === 'e' || sql.charAt(idx + 7) === 'E') &&
|
||
|
(sql.charAt(idx + 8) === 'r' || sql.charAt(idx + 8) === 'R') &&
|
||
|
(sql.charAt(idx + 9) === 't' || sql.charAt(idx + 9) === 'T') &&
|
||
|
sql.charAt(idx + 10) === '_' &&
|
||
|
(sql.charAt(idx + 11) === 'i' || sql.charAt(idx + 11) === 'I') &&
|
||
|
(sql.charAt(idx + 12) === 'd' || sql.charAt(idx + 12) === 'D') &&
|
||
|
sql.charAt(idx + 13) === '('
|
||
|
) {
|
||
|
reWritablePrepare = false;
|
||
|
idx += 13;
|
||
|
}
|
||
|
break;
|
||
|
case '(':
|
||
|
if (state === State.Normal) {
|
||
|
isInParenthesis++;
|
||
|
}
|
||
|
break;
|
||
|
case ')':
|
||
|
if (state === State.Normal) {
|
||
|
isInParenthesis--;
|
||
|
if (isInParenthesis === 0 && preValuePart2 !== null && postValuePart === null) {
|
||
|
postValuePart = sql.substring(lastParameterPosition, idx);
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (state === State.Normal && isFirstChar && car > ' ') {
|
||
|
if (
|
||
|
(car === 'I' || car === 'i') &&
|
||
|
sql.length > idx + 6 &&
|
||
|
(sql.charAt(idx) === 'n' || sql.charAt(idx) === 'N') &&
|
||
|
(sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
|
||
|
(sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
|
||
|
(sql.charAt(idx + 3) === 'r' || sql.charAt(idx + 3) === 'R') &&
|
||
|
(sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T') &&
|
||
|
(sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
|
||
|
) {
|
||
|
isInsert = true;
|
||
|
}
|
||
|
isFirstChar = false;
|
||
|
}
|
||
|
//multiple queries
|
||
|
if (state === State.Normal && semicolon && car >= ' ') {
|
||
|
reWritablePrepare = false;
|
||
|
multipleQueriesPrepare = true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
lastChar = car;
|
||
|
car = sql.charAt(idx++);
|
||
|
}
|
||
|
|
||
|
if (state === State.EOLComment) multipleQueriesPrepare = false;
|
||
|
|
||
|
if (!hasParam) {
|
||
|
//permit to have rewrite without parameter
|
||
|
if (preValuePart1 === null) {
|
||
|
partList.unshift('');
|
||
|
partList.unshift(sql);
|
||
|
} else {
|
||
|
partList.unshift(sql.substring(lastParameterPosition, idx));
|
||
|
partList.unshift(preValuePart1);
|
||
|
}
|
||
|
lastParameterPosition = idx;
|
||
|
} else {
|
||
|
partList.unshift(preValuePart2 !== null ? preValuePart2 : '');
|
||
|
partList.unshift(preValuePart1 !== null ? preValuePart1 : '');
|
||
|
}
|
||
|
|
||
|
if (!isInsert) {
|
||
|
reWritablePrepare = false;
|
||
|
}
|
||
|
|
||
|
//postValuePart is the value after the last parameter and parenthesis
|
||
|
//if no param, don't add to the list.
|
||
|
if (hasParam) {
|
||
|
partList.push(postValuePart !== null ? postValuePart : '');
|
||
|
}
|
||
|
partList.push(sql.substring(lastParameterPosition, idx));
|
||
|
|
||
|
return {
|
||
|
partList: partList,
|
||
|
reWritable: reWritablePrepare,
|
||
|
multipleQueries: multipleQueriesPrepare
|
||
|
};
|
||
|
};
|
||
|
|
||
|
module.exports.searchPlaceholder = function (sql, info, initialValues, displaySql) {
|
||
|
let sqlPlaceHolder = '';
|
||
|
const rowNumber = initialValues.length;
|
||
|
let values = new Array(rowNumber);
|
||
|
for (let i = 0; i < rowNumber; i++) values[i] = [];
|
||
|
let state = State.Normal;
|
||
|
let lastChar = '\0';
|
||
|
|
||
|
let singleQuotes = false;
|
||
|
let lastParameterPosition = 0;
|
||
|
|
||
|
let idx = 0;
|
||
|
let car = sql.charAt(idx++);
|
||
|
let placeholderName;
|
||
|
|
||
|
while (car !== '') {
|
||
|
if (
|
||
|
state === State.Escape &&
|
||
|
!((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
|
||
|
) {
|
||
|
state = State.String;
|
||
|
lastChar = car;
|
||
|
car = sql.charAt(idx++);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (car) {
|
||
|
case '*':
|
||
|
if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
if (state === State.Normal) state = State.EOLComment;
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
if (state === State.Normal && lastChar == '-') {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
if (state === State.EOLComment) {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '"':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.String && !singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && !singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "'":
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = true;
|
||
|
} else if (state === State.String && singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.Escape && singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
if (state === State.String) state = State.Escape;
|
||
|
break;
|
||
|
|
||
|
case ':':
|
||
|
if (state === State.Normal) {
|
||
|
sqlPlaceHolder += sql.substring(lastParameterPosition, idx - 1) + '?';
|
||
|
placeholderName = '';
|
||
|
while (
|
||
|
((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
|
||
|
(car >= 'A' && car <= 'Z') ||
|
||
|
(car >= 'a' && car <= 'z') ||
|
||
|
car === '-' ||
|
||
|
car === '_'
|
||
|
) {
|
||
|
placeholderName += car;
|
||
|
}
|
||
|
idx--;
|
||
|
for (let i = 0; i < rowNumber; i++) {
|
||
|
const val = initialValues[i][placeholderName];
|
||
|
if (val !== undefined) {
|
||
|
values[i].push(val);
|
||
|
} else {
|
||
|
values[i].push(null);
|
||
|
}
|
||
|
}
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
break;
|
||
|
case '`':
|
||
|
if (state === State.Backtick) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Normal) {
|
||
|
state = State.Backtick;
|
||
|
}
|
||
|
}
|
||
|
lastChar = car;
|
||
|
|
||
|
car = sql.charAt(idx++);
|
||
|
}
|
||
|
if (lastParameterPosition === 0) {
|
||
|
sqlPlaceHolder = sql;
|
||
|
} else {
|
||
|
sqlPlaceHolder += sql.substring(lastParameterPosition);
|
||
|
}
|
||
|
|
||
|
return { sql: sqlPlaceHolder, values: values };
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Split query according to named parameters.
|
||
|
*
|
||
|
* The only rewritten queries follow these notation: INSERT [LOW_PRIORITY | DELAYED |
|
||
|
* HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [PARTITION (partition_list)] [(col,...)] {VALUES |
|
||
|
* VALUE} (...) [ ON DUPLICATE KEY UPDATE col=expr [, col=expr] ... ] With expr without
|
||
|
* parameter.
|
||
|
*
|
||
|
* Query with INSERT ... SELECT / containing LAST_INSERT_ID() will not be rewritten
|
||
|
*
|
||
|
* query parts will be split this way :
|
||
|
* - pre-value part
|
||
|
* - after value part
|
||
|
* [- after parameter part] (after each parameter)
|
||
|
* - ending part
|
||
|
*
|
||
|
* example : INSERT INTO MyTABLE VALUES (9, :param1, 5, :param2, 8) ON DUPLICATE KEY UPDATE col2=col2+10
|
||
|
* will result in :
|
||
|
* - pre-value : "INSERT INTO MyTABLE VALUES"
|
||
|
* - after value : " (9, "
|
||
|
* - after parameter : ", 5, "
|
||
|
* - after parameter : ", 8)"
|
||
|
* - ending : " ON DUPLICATE KEY UPDATE col2=col2+10"
|
||
|
*
|
||
|
*
|
||
|
* @returns {JSON} query separated by parameters
|
||
|
*/
|
||
|
module.exports.splitRewritableNamedParameterQuery = function (sql, initialValues) {
|
||
|
let reWritablePrepare = true;
|
||
|
let multipleQueriesPrepare = true;
|
||
|
let partList = [];
|
||
|
let values = new Array(initialValues.length);
|
||
|
for (let i = 0; i < values.length; i++) values[i] = [];
|
||
|
let lastChar = '\0';
|
||
|
|
||
|
let lastParameterPosition = 0;
|
||
|
|
||
|
let preValuePart1 = null;
|
||
|
let preValuePart2 = null;
|
||
|
let postValuePart = null;
|
||
|
|
||
|
let singleQuotes = false;
|
||
|
|
||
|
let isInParenthesis = 0;
|
||
|
let isFirstChar = true;
|
||
|
let isInsert = false;
|
||
|
let semicolon = false;
|
||
|
let hasParam = false;
|
||
|
let placeholderName;
|
||
|
let state = State.Normal;
|
||
|
|
||
|
let idx = 0;
|
||
|
let car = sql.charAt(idx++);
|
||
|
while (car !== '') {
|
||
|
if (
|
||
|
state === State.Escape &&
|
||
|
!((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
|
||
|
) {
|
||
|
state = State.String;
|
||
|
car = sql.charAt(idx++);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (car) {
|
||
|
case '*':
|
||
|
if (state === State.Normal && lastChar == '/') {
|
||
|
state = State.SlashStarComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
if (state === State.SlashStarComment && lastChar == '*') {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
if (state === State.Normal && lastChar == '-') {
|
||
|
state = State.EOLComment;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
if (state === State.EOLComment) {
|
||
|
state = State.Normal;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '"':
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = false;
|
||
|
} else if (state === State.String && !singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && !singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
case ';':
|
||
|
if (state === State.Normal) {
|
||
|
semicolon = true;
|
||
|
multipleQueriesPrepare = false;
|
||
|
}
|
||
|
break;
|
||
|
case "'":
|
||
|
if (state === State.Normal) {
|
||
|
state = State.String;
|
||
|
singleQuotes = true;
|
||
|
} else if (state === State.String && singleQuotes) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Escape && singleQuotes) {
|
||
|
state = State.String;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
if (state === State.String) {
|
||
|
state = State.Escape;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ':':
|
||
|
if (state === State.Normal) {
|
||
|
let part = sql.substring(lastParameterPosition, idx - 1);
|
||
|
placeholderName = '';
|
||
|
while (
|
||
|
((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
|
||
|
(car >= 'A' && car <= 'Z') ||
|
||
|
(car >= 'a' && car <= 'z') ||
|
||
|
car === '-' ||
|
||
|
car === '_'
|
||
|
) {
|
||
|
placeholderName += car;
|
||
|
}
|
||
|
idx--;
|
||
|
hasParam = true;
|
||
|
initialValues.forEach((row, idx) => {
|
||
|
if (row[placeholderName] !== undefined) {
|
||
|
values[idx].push(row[placeholderName]);
|
||
|
} else {
|
||
|
values[idx].push(null);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
lastParameterPosition = idx;
|
||
|
|
||
|
if (preValuePart1 === null) {
|
||
|
preValuePart1 = part;
|
||
|
preValuePart2 = '';
|
||
|
} else if (preValuePart2 === null) {
|
||
|
preValuePart2 = part;
|
||
|
} else {
|
||
|
if (postValuePart) {
|
||
|
//having parameters after the last ")" of value is not rewritable
|
||
|
reWritablePrepare = false;
|
||
|
partList.push(postValuePart + part);
|
||
|
postValuePart = null;
|
||
|
} else partList.push(part);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '`':
|
||
|
if (state === State.Backtick) {
|
||
|
state = State.Normal;
|
||
|
} else if (state === State.Normal) {
|
||
|
state = State.Backtick;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
postValuePart === null &&
|
||
|
sql.length > idx + 5 &&
|
||
|
(sql.charAt(idx) === 'e' || sql.charAt(idx) === 'E') &&
|
||
|
(sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
|
||
|
(sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
|
||
|
(sql.charAt(idx + 3) === 'c' || sql.charAt(idx + 3) === 'C') &&
|
||
|
(sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T')
|
||
|
) {
|
||
|
//field/table name might contain 'select'
|
||
|
if (
|
||
|
idx > 1 &&
|
||
|
sql.charAt(idx - 2) > ' ' &&
|
||
|
'();><=-+,'.indexOf(sql.charAt(idx - 2)) === -1
|
||
|
) {
|
||
|
break;
|
||
|
}
|
||
|
if (sql.charAt(idx + 5) > ' ' && '();><=-+,'.indexOf(sql.charAt(idx + 5)) === -1) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//SELECT queries, INSERT FROM SELECT not rewritable
|
||
|
reWritablePrepare = false;
|
||
|
}
|
||
|
break;
|
||
|
case 'v':
|
||
|
case 'V':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
!preValuePart1 &&
|
||
|
(lastChar == ')' || lastChar <= ' ') &&
|
||
|
sql.length > idx + 6 &&
|
||
|
(sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
|
||
|
(sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
|
||
|
(sql.charAt(idx + 2) === 'u' || sql.charAt(idx + 2) === 'U') &&
|
||
|
(sql.charAt(idx + 3) === 'e' || sql.charAt(idx + 3) === 'E') &&
|
||
|
(sql.charAt(idx + 4) === 's' || sql.charAt(idx + 4) === 'S') &&
|
||
|
(sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
|
||
|
) {
|
||
|
idx += 5;
|
||
|
preValuePart1 = sql.substring(lastParameterPosition, idx);
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
break;
|
||
|
case 'l':
|
||
|
case 'L':
|
||
|
if (
|
||
|
state === State.Normal &&
|
||
|
sql.length > idx + 13 &&
|
||
|
(sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
|
||
|
(sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
|
||
|
(sql.charAt(idx + 2) === 't' || sql.charAt(idx + 2) === 'T') &&
|
||
|
sql.charAt(idx + 3) === '_' &&
|
||
|
(sql.charAt(idx + 4) === 'i' || sql.charAt(idx + 4) === 'I') &&
|
||
|
(sql.charAt(idx + 5) === 'n' || sql.charAt(idx + 5) === 'N') &&
|
||
|
(sql.charAt(idx + 6) === 's' || sql.charAt(idx + 6) === 'S') &&
|
||
|
(sql.charAt(idx + 7) === 'e' || sql.charAt(idx + 7) === 'E') &&
|
||
|
(sql.charAt(idx + 8) === 'r' || sql.charAt(idx + 8) === 'R') &&
|
||
|
(sql.charAt(idx + 9) === 't' || sql.charAt(idx + 9) === 'T') &&
|
||
|
sql.charAt(idx + 10) === '_' &&
|
||
|
(sql.charAt(idx + 11) === 'i' || sql.charAt(idx + 11) === 'I') &&
|
||
|
(sql.charAt(idx + 12) === 'd' || sql.charAt(idx + 12) === 'D') &&
|
||
|
sql.charAt(idx + 13) === '('
|
||
|
) {
|
||
|
reWritablePrepare = false;
|
||
|
idx += 13;
|
||
|
}
|
||
|
break;
|
||
|
case '(':
|
||
|
if (state === State.Normal) {
|
||
|
isInParenthesis++;
|
||
|
}
|
||
|
break;
|
||
|
case ')':
|
||
|
if (state === State.Normal) {
|
||
|
isInParenthesis--;
|
||
|
if (isInParenthesis === 0 && preValuePart2 !== null && postValuePart === null) {
|
||
|
postValuePart = sql.substring(lastParameterPosition, idx);
|
||
|
lastParameterPosition = idx;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (state === State.Normal && isFirstChar && car > ' ') {
|
||
|
if (
|
||
|
(car === 'I' || car === 'i') &&
|
||
|
sql.length > idx + 6 &&
|
||
|
(sql.charAt(idx) === 'n' || sql.charAt(idx) === 'N') &&
|
||
|
(sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
|
||
|
(sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
|
||
|
(sql.charAt(idx + 3) === 'r' || sql.charAt(idx + 3) === 'R') &&
|
||
|
(sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T') &&
|
||
|
(sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
|
||
|
) {
|
||
|
isInsert = true;
|
||
|
}
|
||
|
isFirstChar = false;
|
||
|
}
|
||
|
//multiple queries
|
||
|
if (state === State.Normal && semicolon && car >= ' ') {
|
||
|
reWritablePrepare = false;
|
||
|
multipleQueriesPrepare = true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
lastChar = car;
|
||
|
car = sql.charAt(idx++);
|
||
|
}
|
||
|
|
||
|
if (state === State.EOLComment) multipleQueriesPrepare = false;
|
||
|
|
||
|
if (!hasParam) {
|
||
|
//permit to have rewrite without parameter
|
||
|
if (preValuePart1 === null) {
|
||
|
partList.unshift('');
|
||
|
partList.unshift(sql);
|
||
|
} else {
|
||
|
partList.unshift(sql.substring(lastParameterPosition, idx));
|
||
|
partList.unshift(preValuePart1);
|
||
|
}
|
||
|
lastParameterPosition = idx;
|
||
|
} else {
|
||
|
partList.unshift(preValuePart2 !== null ? preValuePart2 : '');
|
||
|
partList.unshift(preValuePart1 !== null ? preValuePart1 : '');
|
||
|
}
|
||
|
|
||
|
if (!isInsert) {
|
||
|
reWritablePrepare = false;
|
||
|
}
|
||
|
|
||
|
//postValuePart is the value after the last parameter and parenthesis
|
||
|
//if no param, don't add to the list.
|
||
|
if (hasParam) {
|
||
|
partList.push(postValuePart !== null ? postValuePart : '');
|
||
|
}
|
||
|
partList.push(sql.substring(lastParameterPosition, idx));
|
||
|
|
||
|
return {
|
||
|
partList: partList,
|
||
|
reWritable: reWritablePrepare,
|
||
|
multipleQueries: multipleQueriesPrepare,
|
||
|
values: values
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Ensure that filename requested by server corresponds to query
|
||
|
* protocol : https://mariadb.com/kb/en/library/local_infile-packet/
|
||
|
*
|
||
|
* @param sql query
|
||
|
* @param parameters parameters if any
|
||
|
* @param fileName server requested file
|
||
|
* @returns {boolean} is filename corresponding to query
|
||
|
*/
|
||
|
module.exports.validateFileName = function (sql, parameters, fileName) {
|
||
|
let queryValidator = new RegExp(
|
||
|
"^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+'" +
|
||
|
fileName +
|
||
|
"'",
|
||
|
'i'
|
||
|
);
|
||
|
if (queryValidator.test(sql)) return true;
|
||
|
|
||
|
if (parameters != null) {
|
||
|
queryValidator = new RegExp(
|
||
|
'^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+\\?',
|
||
|
'i'
|
||
|
);
|
||
|
if (queryValidator.test(sql) && parameters.length > 0) {
|
||
|
return parameters[0].toLowerCase() === fileName.toLowerCase();
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|