Backend half

This commit is contained in:
2025-07-11 19:56:28 +02:00
parent fa868e7c1d
commit 8600fa7c1d
19426 changed files with 3750448 additions and 8108 deletions
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2015-2024 MariaDB Corporation Ab
'use strict';
class ConnectionInformation {
#redirectFct;
constructor(opts, redirectFct) {
this.threadId = -1;
this.status = null;
this.serverVersion = null;
this.serverCapabilities = null;
this.database = opts.database;
this.port = opts.port;
this.#redirectFct = redirectFct;
this.redirectRequest = null;
}
hasMinVersion(major, minor, patch) {
if (!this.serverVersion) throw new Error('cannot know if server version until connection is established');
if (!major) throw new Error('a major version must be set');
if (!minor) minor = 0;
if (!patch) patch = 0;
let ver = this.serverVersion;
return (
ver.major > major ||
(ver.major === major && ver.minor > minor) ||
(ver.major === major && ver.minor === minor && ver.patch >= patch)
);
}
redirect(value, resolve) {
return this.#redirectFct(value, resolve);
}
isMariaDB() {
if (!this.serverVersion) throw new Error('cannot know if server is MariaDB until connection is established');
return this.serverVersion.mariaDb;
}
/**
* Parse raw info to set server major/minor/patch values
* @param info
*/
static parseVersionString(info) {
let car;
let offset = 0;
let type = 0;
let val = 0;
for (; offset < info.serverVersion.raw.length; offset++) {
car = info.serverVersion.raw.charCodeAt(offset);
if (car < 48 || car > 57) {
switch (type) {
case 0:
info.serverVersion.major = val;
break;
case 1:
info.serverVersion.minor = val;
break;
case 2:
info.serverVersion.patch = val;
return;
}
type++;
val = 0;
} else {
val = val * 10 + car - 48;
}
}
//serverVersion finished by number like "5.5.57", assign patchVersion
if (type === 2) info.serverVersion.patch = val;
}
}
module.exports = ConnectionInformation;
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2015-2025 MariaDB Corporation Ab
'use strict';
const ErrorCodes = require('../const/error-code');
class SqlError extends Error {
constructor(msg, sql, fatal, info, sqlState, errno, additionalStack, addHeader = undefined, cause) {
super(
(addHeader !== false
? `(conn:${info && info.threadId ? info.threadId : -1}, no: ${errno ? errno : -1}, SQLState: ${sqlState}) `
: '') +
msg +
(sql ? '\nsql: ' + sql : ''),
cause
);
this.name = 'SqlError';
this.sqlMessage = msg;
this.sql = sql;
this.fatal = fatal;
this.errno = errno;
this.sqlState = sqlState;
if (errno > 45000 && errno < 46000) {
//driver error
this.code = errByNo[errno] || 'UNKNOWN';
} else {
this.code = ErrorCodes.codes[this.errno] || 'UNKNOWN';
}
if (additionalStack) {
//adding caller stack, removing initial "Error:\n"
this.stack += '\n From event:\n' + additionalStack.substring(additionalStack.indexOf('\n') + 1);
}
}
get text() {
return this.sqlMessage;
}
}
/**
* Error factory, so error get connection information.
*
* @param msg current error message
* @param errno error number
* @param info connection information
* @param sqlState sql state
* @param sql sql command
* @param fatal is error fatal
* @param additionalStack additional stack trace to see
* @param addHeader add connection information
* @param cause add cause
* @returns {Error} the error
*/
module.exports.createError = function (
msg,
errno,
info = null,
sqlState = 'HY000',
sql = null,
fatal = false,
additionalStack = undefined,
addHeader = undefined,
cause = undefined
) {
if (cause) return new SqlError(msg, sql, fatal, info, sqlState, errno, additionalStack, addHeader, { cause: cause });
return new SqlError(msg, sql, fatal, info, sqlState, errno, additionalStack, addHeader, cause);
};
/**
* Fatal error factory, so error get connection information.
*
* @param msg current error message
* @param errno error number
* @param info connection information
* @param sqlState sql state
* @param sql sql command
* @param additionalStack additional stack trace to see
* @param addHeader add connection information
* @returns {Error} the error
*/
module.exports.createFatalError = function (
msg,
errno,
info = null,
sqlState = '08S01',
sql = null,
additionalStack = undefined,
addHeader = undefined
) {
return new SqlError(msg, sql, true, info, sqlState, errno, additionalStack, addHeader);
};
/********************************************************************************
* Driver specific errors
********************************************************************************/
module.exports.ER_CONNECTION_ALREADY_CLOSED = 45001;
module.exports.ER_MYSQL_CHANGE_USER_BUG = 45003;
module.exports.ER_CMD_NOT_EXECUTED_DESTROYED = 45004;
module.exports.ER_NULL_CHAR_ESCAPEID = 45005;
module.exports.ER_NULL_ESCAPEID = 45006;
module.exports.ER_NOT_IMPLEMENTED_FORMAT = 45007;
module.exports.ER_NODE_NOT_SUPPORTED_TLS = 45008;
module.exports.ER_SOCKET_UNEXPECTED_CLOSE = 45009;
module.exports.ER_UNEXPECTED_PACKET = 45011;
module.exports.ER_CONNECTION_TIMEOUT = 45012;
module.exports.ER_CMD_CONNECTION_CLOSED = 45013;
module.exports.ER_CHANGE_USER_BAD_PACKET = 45014;
module.exports.ER_PING_BAD_PACKET = 45015;
module.exports.ER_MISSING_PARAMETER = 45016;
module.exports.ER_PARAMETER_UNDEFINED = 45017;
module.exports.ER_PLACEHOLDER_UNDEFINED = 45018;
module.exports.ER_SOCKET = 45019;
module.exports.ER_EOF_EXPECTED = 45020;
module.exports.ER_LOCAL_INFILE_DISABLED = 45021;
module.exports.ER_LOCAL_INFILE_NOT_READABLE = 45022;
module.exports.ER_SERVER_SSL_DISABLED = 45023;
module.exports.ER_AUTHENTICATION_BAD_PACKET = 45024;
module.exports.ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED = 45025;
module.exports.ER_SOCKET_TIMEOUT = 45026;
module.exports.ER_POOL_ALREADY_CLOSED = 45027;
module.exports.ER_GET_CONNECTION_TIMEOUT = 45028;
module.exports.ER_SETTING_SESSION_ERROR = 45029;
module.exports.ER_INITIAL_SQL_ERROR = 45030;
module.exports.ER_BATCH_WITH_NO_VALUES = 45031;
module.exports.ER_RESET_BAD_PACKET = 45032;
module.exports.ER_WRONG_IANA_TIMEZONE = 45033;
module.exports.ER_LOCAL_INFILE_WRONG_FILENAME = 45034;
module.exports.ER_ADD_CONNECTION_CLOSED_POOL = 45035;
module.exports.ER_WRONG_AUTO_TIMEZONE = 45036;
module.exports.ER_CLOSING_POOL = 45037;
module.exports.ER_TIMEOUT_NOT_SUPPORTED = 45038;
module.exports.ER_INITIAL_TIMEOUT_ERROR = 45039;
module.exports.ER_DUPLICATE_FIELD = 45040;
module.exports.ER_PING_TIMEOUT = 45042;
module.exports.ER_BAD_PARAMETER_VALUE = 45043;
module.exports.ER_CANNOT_RETRIEVE_RSA_KEY = 45044;
module.exports.ER_MINIMUM_NODE_VERSION_REQUIRED = 45045;
module.exports.ER_MAX_ALLOWED_PACKET = 45046;
module.exports.ER_NOT_SUPPORTED_AUTH_PLUGIN = 45047;
module.exports.ER_COMPRESSION_NOT_SUPPORTED = 45048;
module.exports.ER_UNDEFINED_SQL = 45049;
module.exports.ER_PARSING_PRECISION = 45050;
module.exports.ER_PREPARE_CLOSED = 45051;
module.exports.ER_MISSING_SQL_PARAMETER = 45052;
module.exports.ER_MISSING_SQL_FILE = 45053;
module.exports.ER_SQL_FILE_ERROR = 45054;
module.exports.ER_MISSING_DATABASE_PARAMETER = 45055;
module.exports.ER_SELF_SIGNED = 45056;
module.exports.ER_SELF_SIGNED_NO_PWD = 45057;
module.exports.ER_PRIVATE_FIELDS_USE = 45058;
module.exports.ER_TLS_IDENTITY_ERROR = 45059;
module.exports.ER_POOL_NOT_INITIALIZED = 45060;
module.exports.ER_POOL_NO_CONNECTION = 45061;
module.exports.ER_SELF_SIGNED_BAD_PLUGIN = 45062;
const keys = Object.keys(module.exports);
const errByNo = {};
for (let i = 0; i < keys.length; i++) {
const keyName = keys[i];
if (keyName !== 'createError') {
errByNo[module.exports[keyName]] = keyName;
}
}
module.exports.SqlError = SqlError;
+531
View File
@@ -0,0 +1,531 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2015-2025 MariaDB Corporation Ab
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 */
};
const SLASH_BYTE = '/'.charCodeAt(0);
const STAR_BYTE = '*'.charCodeAt(0);
const BACKSLASH_BYTE = '\\'.charCodeAt(0);
const HASH_BYTE = '#'.charCodeAt(0);
const MINUS_BYTE = '-'.charCodeAt(0);
const LINE_FEED_BYTE = '\n'.charCodeAt(0);
const DBL_QUOTE_BYTE = '"'.charCodeAt(0);
const QUOTE_BYTE = "'".charCodeAt(0);
const RADICAL_BYTE = '`'.charCodeAt(0);
const QUESTION_MARK_BYTE = '?'.charCodeAt(0);
const COLON_BYTE = ':'.charCodeAt(0);
const SEMICOLON_BYTE = ';'.charCodeAt(0);
/**
* Set question mark position (question mark).
* Question mark in comment are not taken in account
*
* @returns {Array} question mark position
*/
module.exports.splitQuery = function (query) {
let paramPositions = [];
let state = State.Normal;
let lastChar = 0x00;
let singleQuotes = false;
let currentChar;
const len = query.length;
for (let i = 0; i < len; i++) {
currentChar = query[i];
if (
state === State.Escape &&
!((currentChar === QUOTE_BYTE && singleQuotes) || (currentChar === DBL_QUOTE_BYTE && !singleQuotes))
) {
state = State.String;
lastChar = currentChar;
continue;
}
switch (currentChar) {
case STAR_BYTE:
if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.SlashStarComment;
}
break;
case SLASH_BYTE:
if (state === State.SlashStarComment && lastChar === STAR_BYTE) {
state = State.Normal;
} else if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.EOLComment;
}
break;
case HASH_BYTE:
if (state === State.Normal) {
state = State.EOLComment;
}
break;
case MINUS_BYTE:
if (state === State.Normal && lastChar === MINUS_BYTE) {
state = State.EOLComment;
}
break;
case LINE_FEED_BYTE:
if (state === State.EOLComment) {
state = State.Normal;
}
break;
case DBL_QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = false;
} else if (state === State.String && !singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = true;
} else if (state === State.String && singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case BACKSLASH_BYTE:
if (state === State.String) {
state = State.Escape;
}
break;
case QUESTION_MARK_BYTE:
if (state === State.Normal) {
paramPositions.push(i, ++i);
}
break;
case RADICAL_BYTE:
if (state === State.Backtick) {
state = State.Normal;
} else if (state === State.Normal) {
state = State.Backtick;
}
break;
}
lastChar = currentChar;
}
return paramPositions;
};
/**
* Split query according to parameters using placeholder.
*
* @param query query bytes
* @param info connection information
* @param initialValues placeholder object
* @param displaySql display sql function
* @returns {{paramPositions: Array, values: Array}}
*/
module.exports.splitQueryPlaceholder = function (query, info, initialValues, displaySql) {
let placeholderValues = Object.assign({}, initialValues);
let paramPositions = [];
let values = [];
let state = State.Normal;
let lastChar = 0x00;
let singleQuotes = false;
let car;
const len = query.length;
for (let i = 0; i < len; i++) {
car = query[i];
if (
state === State.Escape &&
!((car === QUOTE_BYTE && singleQuotes) || (car === DBL_QUOTE_BYTE && !singleQuotes))
) {
state = State.String;
lastChar = car;
continue;
}
switch (car) {
case STAR_BYTE:
if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.SlashStarComment;
}
break;
case SLASH_BYTE:
if (state === State.SlashStarComment && lastChar === STAR_BYTE) {
state = State.Normal;
} else if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.EOLComment;
}
break;
case HASH_BYTE:
if (state === State.Normal) {
state = State.EOLComment;
}
break;
case MINUS_BYTE:
if (state === State.Normal && lastChar === MINUS_BYTE) {
state = State.EOLComment;
}
break;
case LINE_FEED_BYTE:
if (state === State.EOLComment) {
state = State.Normal;
}
break;
case DBL_QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = false;
} else if (state === State.String && !singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = true;
} else if (state === State.String && singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case BACKSLASH_BYTE:
if (state === State.String) {
state = State.Escape;
}
break;
case QUESTION_MARK_BYTE:
if (state === State.Normal) {
const key = Object.keys(placeholderValues)[0];
values.push(placeholderValues[key]);
delete placeholderValues[key];
paramPositions.push(i);
paramPositions.push(++i);
}
break;
case COLON_BYTE:
if (state === State.Normal) {
let j = 1;
while (
(i + j < len && query[i + j] >= '0'.charCodeAt(0) && query[i + j] <= '9'.charCodeAt(0)) ||
(query[i + j] >= 'A'.charCodeAt(0) && query[i + j] <= 'Z'.charCodeAt(0)) ||
(query[i + j] >= 'a'.charCodeAt(0) && query[i + j] <= 'z'.charCodeAt(0)) ||
query[i + j] === '-'.charCodeAt(0) ||
query[i + j] === '_'.charCodeAt(0)
) {
j++;
}
paramPositions.push(i, i + j);
const placeholderName = query.toString('utf8', i + 1, i + j);
i += j;
let val;
if (placeholderName in placeholderValues) {
val = placeholderValues[placeholderName];
delete placeholderValues[placeholderName];
} else {
// value is already used
val = initialValues[placeholderName];
}
if (val === undefined) {
throw Errors.createError(
`Placeholder '${placeholderName}' is not defined`,
Errors.ER_PLACEHOLDER_UNDEFINED,
info,
'HY000',
displaySql.call()
);
}
values.push(val);
}
break;
case RADICAL_BYTE:
if (state === State.Backtick) {
state = State.Normal;
} else if (state === State.Normal) {
state = State.Backtick;
}
break;
}
lastChar = car;
}
return { paramPositions: paramPositions, values: values };
};
module.exports.searchPlaceholder = function (sql) {
let sqlPlaceHolder = '';
let placeHolderIndex = [];
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--;
placeHolderIndex.push(placeholderName);
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, placeHolderIndex: placeHolderIndex };
};
/**
* 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) {
// in case of windows, file name in query are escaped
// so for example LOAD DATA LOCAL INFILE 'C:\\Temp\\myFile.txt' ...
// but server return 'C:\Temp\myFile.txt'
// so with regex escaped, must test LOAD DATA LOCAL INFILE 'C:\\\\Temp\\\\myFile.txt'
let queryValidator = new RegExp(
"^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+'" +
fileName.replace(/\\/g, '\\\\\\\\').replace('.', '\\.') +
"'",
'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) {
if (Array.isArray(parameters)) {
return parameters[0].toLowerCase() === fileName.toLowerCase();
}
return parameters.toLowerCase() === fileName.toLowerCase();
}
}
return false;
};
/**
* Parse commands from buffer, returns queries separated by ';'
* (last one is not parsed)
*
* @param bufState buffer
* @returns {*[]} array of queries contained in buffer
*/
module.exports.parseQueries = function (bufState) {
let state = State.Normal;
let lastChar = 0x00;
let currByte;
let queries = [];
let singleQuotes = false;
for (let i = bufState.offset; i < bufState.end; i++) {
currByte = bufState.buffer[i];
if (
state === State.Escape &&
!((currByte === QUOTE_BYTE && singleQuotes) || (currByte === DBL_QUOTE_BYTE && !singleQuotes))
) {
state = State.String;
lastChar = currByte;
continue;
}
switch (currByte) {
case STAR_BYTE:
if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.SlashStarComment;
}
break;
case SLASH_BYTE:
if (state === State.SlashStarComment && lastChar === STAR_BYTE) {
state = State.Normal;
} else if (state === State.Normal && lastChar === SLASH_BYTE) {
state = State.EOLComment;
}
break;
case HASH_BYTE:
if (state === State.Normal) {
state = State.EOLComment;
}
break;
case MINUS_BYTE:
if (state === State.Normal && lastChar === MINUS_BYTE) {
state = State.EOLComment;
}
break;
case LINE_FEED_BYTE:
if (state === State.EOLComment) {
state = State.Normal;
}
break;
case DBL_QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = false;
} else if (state === State.String && !singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case QUOTE_BYTE:
if (state === State.Normal) {
state = State.String;
singleQuotes = true;
} else if (state === State.String && singleQuotes) {
state = State.Normal;
} else if (state === State.Escape) {
state = State.String;
}
break;
case BACKSLASH_BYTE:
if (state === State.String) {
state = State.Escape;
}
break;
case SEMICOLON_BYTE:
if (state === State.Normal) {
queries.push(bufState.buffer.toString('utf8', bufState.offset, i));
bufState.offset = i + 1;
}
break;
case RADICAL_BYTE:
if (state === State.Backtick) {
state = State.Normal;
} else if (state === State.Normal) {
state = State.Backtick;
}
break;
}
lastChar = currByte;
}
return queries;
};
+270
View File
@@ -0,0 +1,270 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2015-2024 MariaDB Corporation Ab
'use strict';
const hexArray = '0123456789ABCDEF'.split('');
const Errors = require('../misc/errors');
const Iconv = require('iconv-lite');
const TextEncoder = require('../cmd/encoder/text-encoder');
/**
* Write bytes/hexadecimal value of a byte array to a string.
* String output example :
* 38 00 00 00 03 63 72 65 61 74 65 20 74 61 62 6C 8....create tabl
* 65 20 42 6C 6F 62 54 65 73 74 63 6C 6F 62 74 65 e BlobTestclobte
* 73 74 32 20 28 73 74 72 6D 20 74 65 78 74 29 20 st2 (strm text)
* 43 48 41 52 53 45 54 20 75 74 66 38 CHARSET utf8
*/
module.exports.log = function (opts, buf, off, end, header) {
let out = [];
if (!buf) return '';
if (off === undefined || off === null) off = 0;
if (end === undefined || end === null) end = buf.length;
let asciiValue = new Array(16);
asciiValue[8] = ' ';
let useHeader = header !== undefined;
let offset = off || 0;
const maxLgh = Math.min(useHeader ? opts.debugLen - header.length : opts.debugLen, end - offset);
const isLimited = end - offset > maxLgh;
let byteValue;
let posHexa = 0;
let pos = 0;
out.push(
'+--------------------------------------------------+\n' +
'| 0 1 2 3 4 5 6 7 8 9 a b c d e f |\n' +
'+--------------------------------------------------+------------------+\n'
);
if (useHeader) {
while (pos < header.length) {
if (posHexa === 0) out.push('| ');
byteValue = header[pos++] & 0xff;
out.push(hexArray[byteValue >>> 4], hexArray[byteValue & 0x0f], ' ');
asciiValue[posHexa++] = byteValue > 31 && byteValue < 127 ? String.fromCharCode(byteValue) : '.';
if (posHexa === 8) out.push(' ');
}
}
pos = offset;
while (pos < maxLgh + offset) {
if (posHexa === 0) out.push('| ');
byteValue = buf[pos] & 0xff;
out.push(hexArray[byteValue >>> 4], hexArray[byteValue & 0x0f], ' ');
asciiValue[posHexa++] = byteValue > 31 && byteValue < 127 ? String.fromCharCode(byteValue) : '.';
if (posHexa === 8) out.push(' ');
if (posHexa === 16) {
out.push('| ', asciiValue.join(''), ' |\n');
posHexa = 0;
}
pos++;
}
let remaining = posHexa;
if (remaining > 0) {
if (remaining < 8) {
for (; remaining < 8; remaining++) {
out.push(' ');
asciiValue[posHexa++] = ' ';
}
out.push(' ');
}
for (; remaining < 16; remaining++) {
out.push(' ');
asciiValue[posHexa++] = ' ';
}
out.push('| ', asciiValue.join(''), isLimited ? ' |...\n' : ' |\n');
} else if (isLimited) {
out[out.length - 1] = ' |...\n';
}
out.push('+--------------------------------------------------+------------------+\n');
return out.join('');
};
module.exports.toHexString = (bytes) => {
return Array.from(bytes, (byte) => {
return ('0' + (byte & 0xff).toString(16)).slice(-2);
}).join('');
};
module.exports.escapeId = (opts, info, value) => {
if (!value || value === '') {
throw Errors.createError('Cannot escape empty ID value', Errors.ER_NULL_ESCAPEID, info, '0A000');
}
if (value.includes('\u0000')) {
throw Errors.createError(
'Cannot escape ID with null character (u0000)',
Errors.ER_NULL_CHAR_ESCAPEID,
info,
'0A000'
);
}
// always return escaped value, even when there is no special characters
// to permit working with reserved words
return '`' + value.replace(/`/g, '``') + '`';
};
const escapeParameters = (opts, info, value) => {
if (value == null) return 'NULL';
switch (typeof value) {
case 'boolean':
return value ? 'true' : 'false';
case 'bigint':
case 'number':
return `${value}`;
case 'object':
if (Object.prototype.toString.call(value) === '[object Date]') {
return TextEncoder.getFixedFormatDate(value);
} else if (Buffer.isBuffer(value)) {
let stValue;
if (Buffer.isEncoding(info.collation.charset)) {
stValue = value.toString(info.collation.charset, 0, value.length);
} else {
stValue = Iconv.decode(value, info.collation.charset);
}
return "_binary'" + escapeString(stValue) + "'";
} else if (typeof value.toSqlString === 'function') {
return "'" + escapeString(String(value.toSqlString())) + "'";
} else if (Array.isArray(value)) {
let out = opts.arrayParenthesis ? '(' : '';
for (let i = 0; i < value.length; i++) {
if (i !== 0) out += ',';
out += escapeParameters(opts, info, value[i]);
}
if (opts.arrayParenthesis) out += ')';
return out;
} else {
if (
value.type != null &&
[
'Point',
'LineString',
'Polygon',
'MultiPoint',
'MultiLineString',
'MultiPolygon',
'GeometryCollection'
].includes(value.type)
) {
//GeoJSON format.
let prefix =
info &&
((info.isMariaDB() && info.hasMinVersion(10, 1, 4)) || (!info.isMariaDB() && info.hasMinVersion(5, 7, 6)))
? 'ST_'
: '';
switch (value.type) {
case 'Point':
return prefix + "PointFromText('POINT(" + TextEncoder.geoPointToString(value.coordinates) + ")')";
case 'LineString':
return (
prefix + "LineFromText('LINESTRING(" + TextEncoder.geoArrayPointToString(value.coordinates) + ")')"
);
case 'Polygon':
return (
prefix + "PolygonFromText('POLYGON(" + TextEncoder.geoMultiArrayPointToString(value.coordinates) + ")')"
);
case 'MultiPoint':
return (
prefix +
"MULTIPOINTFROMTEXT('MULTIPOINT(" +
TextEncoder.geoArrayPointToString(value.coordinates) +
")')"
);
case 'MultiLineString':
return (
prefix +
"MLineFromText('MULTILINESTRING(" +
TextEncoder.geoMultiArrayPointToString(value.coordinates) +
")')"
);
case 'MultiPolygon':
return (
prefix + "MPolyFromText('MULTIPOLYGON(" + TextEncoder.geoMultiPolygonToString(value.coordinates) + ")')"
);
case 'GeometryCollection':
return (
prefix +
"GeomCollFromText('GEOMETRYCOLLECTION(" +
TextEncoder.geometricCollectionToString(value.geometries) +
")')"
);
}
} else {
if (opts.permitSetMultiParamEntries) {
let out = '';
let first = true;
for (let key in value) {
const val = value[key];
if (typeof val === 'function') continue;
if (first) {
first = false;
} else {
out += ',';
}
out += '`' + key + '`=';
out += this.escape(opts, info, val);
}
if (out === '') return "'" + escapeString(JSON.stringify(value)) + "'";
return out;
} else {
return "'" + escapeString(JSON.stringify(value)) + "'";
}
}
}
default:
return "'" + escapeString(value) + "'";
}
};
// see https://mariadb.com/kb/en/library/string-literals/
const LITTERAL_ESCAPE = {
'\u0000': '\\0',
"'": "\\'",
'"': '\\"',
'\b': '\\b',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\u001A': '\\Z',
'\\': '\\\\'
};
const CHARS_GLOBAL_REGEXP = /[\000\032"'\\\b\n\r\t]/g;
const escapeString = (val) => {
let offset = 0;
let escaped = '';
let match;
while ((match = CHARS_GLOBAL_REGEXP.exec(val))) {
escaped += val.substring(offset, match.index);
escaped += LITTERAL_ESCAPE[match[0]];
offset = CHARS_GLOBAL_REGEXP.lastIndex;
}
if (offset === 0) {
return val;
}
if (offset < val.length) {
escaped += val.substring(offset);
}
return escaped;
};
module.exports.escape = escapeParameters;