| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- "use strict";
- /**
- * Adapted from https://github.com/mysqljs/sqlstring/blob/cd528556b4b6bcf300c3db515026935dedf7cfa1/lib/SqlString.js
- * MIT LICENSE: https://github.com/mysqljs/sqlstring/blob/cd528556b4b6bcf300c3db515026935dedf7cfa1/LICENSE
- */
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.raw = exports.format = exports.escape = exports.arrayToList = exports.bufferToString = exports.objectToValues = exports.escapeId = exports.dateToString = void 0;
- const node_buffer_1 = require("node:buffer");
- const regex = {
- backtick: /`/g,
- dot: /\./g,
- timezone: /([+\-\s])(\d\d):?(\d\d)?/,
- escapeChars: /[\0\b\t\n\r\x1a"'\\]/g,
- };
- const CHARS_ESCAPE_MAP = {
- '\0': '\\0',
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\r': '\\r',
- '\x1a': '\\Z',
- '"': '\\"',
- "'": "\\'",
- '\\': '\\\\',
- };
- const charCode = {
- singleQuote: 39,
- backtick: 96,
- backslash: 92,
- dash: 45,
- slash: 47,
- asterisk: 42,
- questionMark: 63,
- newline: 10,
- space: 32,
- tab: 9,
- carriageReturn: 13,
- };
- const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
- const isWordChar = (code) => (code >= 65 && code <= 90) ||
- (code >= 97 && code <= 122) ||
- (code >= 48 && code <= 57) ||
- code === 95;
- const isWhitespace = (code) => code === charCode.space ||
- code === charCode.tab ||
- code === charCode.newline ||
- code === charCode.carriageReturn;
- const hasOnlyWhitespaceBetween = (sql, start, end) => {
- if (start >= end)
- return true;
- for (let i = start; i < end; i++) {
- const code = sql.charCodeAt(i);
- if (code !== charCode.space &&
- code !== charCode.tab &&
- code !== charCode.newline &&
- code !== charCode.carriageReturn)
- return false;
- }
- return true;
- };
- const toLower = (code) => code | 32;
- const matchesWord = (sql, position, word, length) => {
- for (let offset = 0; offset < word.length; offset++)
- if (toLower(sql.charCodeAt(position + offset)) !== word.charCodeAt(offset))
- return false;
- return ((position === 0 || !isWordChar(sql.charCodeAt(position - 1))) &&
- (position + word.length >= length ||
- !isWordChar(sql.charCodeAt(position + word.length))));
- };
- const skipSqlContext = (sql, position) => {
- const currentChar = sql.charCodeAt(position);
- const nextChar = sql.charCodeAt(position + 1);
- if (currentChar === charCode.singleQuote) {
- for (let cursor = position + 1; cursor < sql.length; cursor++) {
- if (sql.charCodeAt(cursor) === charCode.backslash)
- cursor++;
- else if (sql.charCodeAt(cursor) === charCode.singleQuote)
- return cursor + 1;
- }
- return sql.length;
- }
- if (currentChar === charCode.backtick) {
- const length = sql.length;
- for (let cursor = position + 1; cursor < length; cursor++) {
- if (sql.charCodeAt(cursor) !== charCode.backtick)
- continue;
- if (sql.charCodeAt(cursor + 1) === charCode.backtick) {
- cursor++;
- continue;
- }
- return cursor + 1;
- }
- return length;
- }
- if (currentChar === charCode.dash && nextChar === charCode.dash) {
- const lineBreak = sql.indexOf('\n', position + 2);
- return lineBreak === -1 ? sql.length : lineBreak + 1;
- }
- if (currentChar === charCode.slash && nextChar === charCode.asterisk) {
- const commentEnd = sql.indexOf('*/', position + 2);
- return commentEnd === -1 ? sql.length : commentEnd + 2;
- }
- return -1;
- };
- const findNextPlaceholder = (sql, start) => {
- const sqlLength = sql.length;
- for (let position = start; position < sqlLength; position++) {
- const code = sql.charCodeAt(position);
- if (code === charCode.questionMark)
- return position;
- if (code === charCode.singleQuote ||
- code === charCode.backtick ||
- code === charCode.dash ||
- code === charCode.slash) {
- const contextEnd = skipSqlContext(sql, position);
- if (contextEnd !== -1)
- position = contextEnd - 1;
- }
- }
- return -1;
- };
- const findSetKeyword = (sql, startFrom = 0) => {
- const length = sql.length;
- for (let position = startFrom; position < length; position++) {
- const code = sql.charCodeAt(position);
- const lower = code | 32;
- if (code === charCode.singleQuote ||
- code === charCode.backtick ||
- code === charCode.dash ||
- code === charCode.slash) {
- const contextEnd = skipSqlContext(sql, position);
- if (contextEnd !== -1) {
- position = contextEnd - 1;
- continue;
- }
- }
- if (lower === 115 && matchesWord(sql, position, 'set', length))
- return position + 3;
- if (lower === 107 && matchesWord(sql, position, 'key', length)) {
- let cursor = position + 3;
- while (cursor < length && isWhitespace(sql.charCodeAt(cursor)))
- cursor++;
- if (matchesWord(sql, cursor, 'update', length))
- return cursor + 6;
- }
- }
- return -1;
- };
- const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
- const hasSqlString = (value) => typeof value === 'object' &&
- value !== null &&
- 'toSqlString' in value &&
- typeof value.toSqlString === 'function';
- const escapeString = (value) => {
- regex.escapeChars.lastIndex = 0;
- let chunkIndex = 0;
- let escapedValue = '';
- let match;
- for (match = regex.escapeChars.exec(value); match !== null; match = regex.escapeChars.exec(value)) {
- escapedValue += value.slice(chunkIndex, match.index);
- escapedValue += CHARS_ESCAPE_MAP[match[0]];
- chunkIndex = regex.escapeChars.lastIndex;
- }
- if (chunkIndex === 0)
- return `'${value}'`;
- if (chunkIndex < value.length)
- return `'${escapedValue}${value.slice(chunkIndex)}'`;
- return `'${escapedValue}'`;
- };
- const pad2 = (value) => (value < 10 ? '0' + value : '' + value);
- const pad3 = (value) => value < 10 ? '00' + value : value < 100 ? '0' + value : '' + value;
- const pad4 = (value) => value < 10
- ? '000' + value
- : value < 100
- ? '00' + value
- : value < 1000
- ? '0' + value
- : '' + value;
- const convertTimezone = (tz) => {
- if (tz === 'Z')
- return 0;
- const timezoneMatch = tz.match(regex.timezone);
- if (timezoneMatch)
- return ((timezoneMatch[1] === '-' ? -1 : 1) *
- (Number.parseInt(timezoneMatch[2], 10) +
- (timezoneMatch[3] ? Number.parseInt(timezoneMatch[3], 10) : 0) / 60) *
- 60);
- return false;
- };
- const dateToString = (date, timezone) => {
- if (Number.isNaN(date.getTime()))
- return 'NULL';
- let year;
- let month;
- let day;
- let hour;
- let minute;
- let second;
- let millisecond;
- if (timezone === 'local') {
- year = date.getFullYear();
- month = date.getMonth() + 1;
- day = date.getDate();
- hour = date.getHours();
- minute = date.getMinutes();
- second = date.getSeconds();
- millisecond = date.getMilliseconds();
- }
- else {
- const timezoneOffsetMinutes = convertTimezone(timezone);
- let time = date.getTime();
- if (timezoneOffsetMinutes !== false && timezoneOffsetMinutes !== 0)
- time += timezoneOffsetMinutes * 60000;
- const adjustedDate = new Date(time);
- year = adjustedDate.getUTCFullYear();
- month = adjustedDate.getUTCMonth() + 1;
- day = adjustedDate.getUTCDate();
- hour = adjustedDate.getUTCHours();
- minute = adjustedDate.getUTCMinutes();
- second = adjustedDate.getUTCSeconds();
- millisecond = adjustedDate.getUTCMilliseconds();
- }
- // YYYY-MM-DD HH:mm:ss.mmm
- return escapeString(pad4(year) +
- '-' +
- pad2(month) +
- '-' +
- pad2(day) +
- ' ' +
- pad2(hour) +
- ':' +
- pad2(minute) +
- ':' +
- pad2(second) +
- '.' +
- pad3(millisecond));
- };
- exports.dateToString = dateToString;
- const escapeId = (value, forbidQualified) => {
- if (Array.isArray(value)) {
- const length = value.length;
- const parts = new Array(length);
- for (let i = 0; i < length; i++)
- parts[i] = (0, exports.escapeId)(value[i], forbidQualified);
- return parts.join(', ');
- }
- const identifier = String(value);
- const hasJsonOperator = identifier.indexOf('->') !== -1;
- if (forbidQualified || hasJsonOperator) {
- if (identifier.indexOf('`') === -1)
- return `\`${identifier}\``;
- return `\`${identifier.replace(regex.backtick, '``')}\``;
- }
- if (identifier.indexOf('`') === -1 && identifier.indexOf('.') === -1)
- return `\`${identifier}\``;
- return `\`${identifier
- .replace(regex.backtick, '``')
- .replace(regex.dot, '`.`')}\``;
- };
- exports.escapeId = escapeId;
- const objectToValues = (object, timezone) => {
- const keys = Object.keys(object);
- const keysLength = keys.length;
- if (keysLength === 0)
- return '';
- let sql = '';
- for (let i = 0; i < keysLength; i++) {
- const key = keys[i];
- const value = object[key];
- if (typeof value === 'function')
- continue;
- if (sql.length > 0)
- sql += ', ';
- sql += (0, exports.escapeId)(key);
- sql += ' = ';
- sql += (0, exports.escape)(value, true, timezone);
- }
- return sql;
- };
- exports.objectToValues = objectToValues;
- const bufferToString = (buffer) => `X${escapeString(buffer.toString('hex'))}`;
- exports.bufferToString = bufferToString;
- const arrayToList = (array, timezone) => {
- const length = array.length;
- const parts = new Array(length);
- for (let i = 0; i < length; i++) {
- const value = array[i];
- if (Array.isArray(value))
- parts[i] = `(${(0, exports.arrayToList)(value, timezone)})`;
- else
- parts[i] = (0, exports.escape)(value, true, timezone);
- }
- return parts.join(', ');
- };
- exports.arrayToList = arrayToList;
- const escape = (value, stringifyObjects, timezone) => {
- if (value === undefined || value === null)
- return 'NULL';
- switch (typeof value) {
- case 'boolean':
- return value ? 'true' : 'false';
- case 'number':
- case 'bigint':
- return value + '';
- case 'object': {
- if (isDate(value))
- return (0, exports.dateToString)(value, timezone || 'local');
- if (Array.isArray(value))
- return (0, exports.arrayToList)(value, timezone);
- if (node_buffer_1.Buffer.isBuffer(value))
- return (0, exports.bufferToString)(value);
- if (value instanceof Uint8Array)
- return (0, exports.bufferToString)(node_buffer_1.Buffer.from(value));
- if (hasSqlString(value))
- return String(value.toSqlString());
- if (!(stringifyObjects === undefined || stringifyObjects === null))
- return escapeString(String(value));
- if (isRecord(value))
- return (0, exports.objectToValues)(value, timezone);
- return escapeString(String(value));
- }
- case 'string':
- return escapeString(value);
- default:
- return escapeString(String(value));
- }
- };
- exports.escape = escape;
- const format = (sql, values, stringifyObjects, timezone) => {
- if (values === undefined || values === null)
- return sql;
- const valuesArray = Array.isArray(values) ? values : [values];
- const length = valuesArray.length;
- let setIndex = -2; // -2 = not yet computed, -1 = no SET found
- let result = '';
- let chunkIndex = 0;
- let valuesIndex = 0;
- let placeholderPosition = findNextPlaceholder(sql, 0);
- while (valuesIndex < length && placeholderPosition !== -1) {
- // Count consecutive question marks to detect ? vs ?? vs ???+
- let placeholderEnd = placeholderPosition + 1;
- let escapedValue;
- while (sql.charCodeAt(placeholderEnd) === 63)
- placeholderEnd++;
- const placeholderLength = placeholderEnd - placeholderPosition;
- const currentValue = valuesArray[valuesIndex];
- if (placeholderLength > 2) {
- placeholderPosition = findNextPlaceholder(sql, placeholderEnd);
- continue;
- }
- if (placeholderLength === 2)
- escapedValue = (0, exports.escapeId)(currentValue);
- else if (typeof currentValue === 'number')
- escapedValue = `${currentValue}`;
- else if (typeof currentValue === 'object' &&
- currentValue !== null &&
- !stringifyObjects) {
- // Lazy: compute SET position only when we first encounter an object
- if (setIndex === -2)
- setIndex = findSetKeyword(sql);
- if (setIndex !== -1 &&
- setIndex <= placeholderPosition &&
- hasOnlyWhitespaceBetween(sql, setIndex, placeholderPosition) &&
- !hasSqlString(currentValue) &&
- !Array.isArray(currentValue) &&
- !node_buffer_1.Buffer.isBuffer(currentValue) &&
- !(currentValue instanceof Uint8Array) &&
- !isDate(currentValue) &&
- isRecord(currentValue)) {
- escapedValue = (0, exports.objectToValues)(currentValue, timezone);
- setIndex = findSetKeyword(sql, placeholderEnd);
- }
- else
- escapedValue = (0, exports.escape)(currentValue, true, timezone);
- }
- else
- escapedValue = (0, exports.escape)(currentValue, stringifyObjects, timezone);
- result += sql.slice(chunkIndex, placeholderPosition);
- result += escapedValue;
- chunkIndex = placeholderEnd;
- valuesIndex++;
- placeholderPosition = findNextPlaceholder(sql, placeholderEnd);
- }
- if (chunkIndex === 0)
- return sql;
- if (chunkIndex < sql.length)
- return result + sql.slice(chunkIndex);
- return result;
- };
- exports.format = format;
- const raw = (sql) => {
- if (typeof sql !== 'string')
- throw new TypeError('argument sql must be a string');
- return {
- toSqlString: () => sql,
- };
- };
- exports.raw = raw;
|