UNPKG

mariadb

Version:
323 lines (297 loc) 12.2 kB
// SPDX-License-Identifier: LGPL-2.1-or-later // Copyright (c) 2015-2025 MariaDB Corporation Ab 'use strict'; const Collations = require('../const/collations.js'); const urlFormat = /mariadb:\/\/(([^/@:]+)?(:([^/]+))?@)?(([^/:]+)(:([0-9]+))?)\/([^?]+)(\?(.*))?$/; /** * Default option similar to mysql driver. * known differences * - no queryFormat option. Permitting client to parse is a security risk. Best is to give SQL + parameters * Only possible Objects are : * - Buffer * - Date * - Object that implement toSqlString function * - JSON object * + rowsAsArray (in mysql2) permit to have rows by index, not by name. Avoiding to parsing metadata string => faster */ class ConnectionOptions { constructor(opts) { if (typeof opts === 'string') { opts = ConnectionOptions.parse(opts); } if (!opts) opts = {}; this.host = opts.host || 'localhost'; this.port = opts.port ? Number(opts.port) : 3306; this.keepEof = Boolean(opts.keepEof) || false; this.user = opts.user || process.env.USERNAME; this.password = opts.password; this.database = opts.database; this.stream = opts.stream; this.fullResult = opts.fullResult; // log this.debug = Boolean(opts.debug) || false; this.debugCompress = Boolean(opts.debugCompress) || false; this.debugLen = opts.debugLen ? Number(opts.debugLen) : 256; this.logParam = opts.logParam === undefined ? true : Boolean(opts.logParam); if (opts.logger) { if (typeof opts.logger === 'function') { this.logger = { network: opts.logger, query: opts.logger, error: opts.logger, warning: opts.logger }; } else { this.logger = { network: opts.logger.network, query: opts.logger.query, error: opts.logger.error, warning: opts.logger.warning || console.log }; if (opts.logger.logParam !== undefined) this.logParam = Boolean(opts.logger.logParam); } } else { this.logger = { network: this.debug || this.debugCompress ? console.log : null, query: null, error: null, warning: console.log }; } this.debug = !!this.logger.network; if (opts.charset && typeof opts.charset === 'string') { this.collation = Collations.fromCharset(opts.charset.toLowerCase()); if (this.collation === undefined) { this.collation = Collations.fromName(opts.charset.toUpperCase()); if (this.collation !== undefined) { this.logger.warning( "warning: please use option 'collation' " + "in replacement of 'charset' when using a collation name ('" + opts.charset + "')\n" + "(collation looks like 'UTF8MB4_UNICODE_CI', charset like 'utf8')." ); } else { this.charset = opts.charset; } } } else if (opts.collation && typeof opts.collation === 'string') { this.collation = Collations.fromName(opts.collation.toUpperCase()); if (this.collation === undefined) throw new RangeError("Unknown collation '" + opts.collation + "'"); } else { this.collation = opts.charsetNumber ? Collations.fromIndex(Number(opts.charsetNumber)) : undefined; } // connection options this.initSql = opts.initSql; this.connectTimeout = opts.connectTimeout === undefined ? 1000 : Number(opts.connectTimeout); this.connectAttributes = opts.connectAttributes || false; this.compress = Boolean(opts.compress) || false; this.rsaPublicKey = opts.rsaPublicKey; this.cachingRsaPublicKey = opts.cachingRsaPublicKey; this.allowPublicKeyRetrieval = Boolean(opts.allowPublicKeyRetrieval) || false; this.forceVersionCheck = Boolean(opts.forceVersionCheck) || false; this.maxAllowedPacket = opts.maxAllowedPacket ? Number(opts.maxAllowedPacket) : undefined; this.permitConnectionWhenExpired = Boolean(opts.permitConnectionWhenExpired) || false; this.pipelining = opts.pipelining; this.timezone = opts.timezone || 'local'; this.socketPath = opts.socketPath; this.sessionVariables = opts.sessionVariables; this.infileStreamFactory = opts.infileStreamFactory; this.ssl = opts.ssl; if (opts.ssl) { if (typeof opts.ssl !== 'boolean' && typeof opts.ssl !== 'string') { this.ssl.rejectUnauthorized = opts.ssl.rejectUnauthorized !== false; } } this.permitRedirect = opts.permitRedirect === undefined ? !!this.ssl && this.ssl.rejectUnauthorized !== false : Boolean(opts.permitRedirect); // socket this.queryTimeout = isNaN(opts.queryTimeout) || Number(opts.queryTimeout) < 0 ? 0 : Number(opts.queryTimeout); this.socketTimeout = isNaN(opts.socketTimeout) || Number(opts.socketTimeout) < 0 ? 0 : Number(opts.socketTimeout); this.keepAliveDelay = opts.keepAliveDelay === undefined ? 0 : Number(opts.keepAliveDelay); if (!opts.keepAliveDelay) { // for mysql2 compatibility, check keepAliveInitialDelay/enableKeepAlive options. if (opts.enableKeepAlive === true && opts.keepAliveInitialDelay !== undefined) { this.keepAliveDelay = Number(opts.keepAliveInitialDelay); } } this.trace = Boolean(opts.trace) || false; // result-set this.checkDuplicate = opts.checkDuplicate === undefined ? true : Boolean(opts.checkDuplicate); this.dateStrings = Boolean(opts.dateStrings) || false; this.foundRows = opts.foundRows === undefined || Boolean(opts.foundRows); this.metaAsArray = Boolean(opts.metaAsArray) || false; this.metaEnumerable = Boolean(opts.metaEnumerable) || false; this.multipleStatements = Boolean(opts.multipleStatements) || false; this.namedPlaceholders = Boolean(opts.namedPlaceholders) || false; this.nestTables = opts.nestTables; this.autoJsonMap = opts.autoJsonMap === undefined ? true : Boolean(opts.autoJsonMap); this.jsonStrings = Boolean(opts.jsonStrings) || false; if (opts.jsonStrings !== undefined) { this.autoJsonMap = !this.jsonStrings; } this.bitOneIsBoolean = opts.bitOneIsBoolean === undefined ? true : Boolean(opts.bitOneIsBoolean); this.arrayParenthesis = Boolean(opts.arrayParenthesis) || false; this.permitSetMultiParamEntries = Boolean(opts.permitSetMultiParamEntries) || false; this.rowsAsArray = Boolean(opts.rowsAsArray) || false; this.typeCast = opts.typeCast; if (this.typeCast !== undefined && typeof this.typeCast !== 'function') { this.typeCast = undefined; } this.bulk = opts.bulk === undefined || Boolean(opts.bulk); this.checkNumberRange = Boolean(opts.checkNumberRange) || false; // coherence check if (opts.pipelining === undefined) { this.permitLocalInfile = Boolean(opts.permitLocalInfile) || false; this.pipelining = !this.permitLocalInfile; } else { this.pipelining = Boolean(opts.pipelining); if (opts.permitLocalInfile === true && this.pipelining) { throw new Error( 'enabling options `permitLocalInfile` and `pipelining` is not possible, options are incompatible.' ); } this.permitLocalInfile = this.pipelining ? false : Boolean(opts.permitLocalInfile) || false; } this.prepareCacheLength = opts.prepareCacheLength === undefined ? 256 : Number(opts.prepareCacheLength); this.restrictedAuth = opts.restrictedAuth; if (this.restrictedAuth != null) { if (!Array.isArray(this.restrictedAuth)) { this.restrictedAuth = this.restrictedAuth.split(','); } } // for compatibility with 2.x version and mysql/mysql2 this.bigIntAsNumber = Boolean(opts.bigIntAsNumber) || false; this.insertIdAsNumber = Boolean(opts.insertIdAsNumber) || false; this.decimalAsNumber = Boolean(opts.decimalAsNumber) || false; this.supportBigNumbers = Boolean(opts.supportBigNumbers) || false; this.bigNumberStrings = Boolean(opts.bigNumberStrings) || false; if (opts.maxAllowedPacket && isNaN(this.maxAllowedPacket)) { throw new RangeError(`maxAllowedPacket must be an integer. was '${opts.maxAllowedPacket}'`); } } /** * When parsing from String, correcting type. * * @param {object} opts - options * @return {object} options with corrected data types */ static parseOptionDataType(opts) { // Convert boolean strings to boolean values const booleanOptions = [ 'bulk', 'allowPublicKeyRetrieval', 'insertIdAsNumber', 'decimalAsNumber', 'bigIntAsNumber', 'permitRedirect', 'logParam', 'compress', 'dateStrings', 'debug', 'autoJsonMap', 'arrayParenthesis', 'checkDuplicate', 'debugCompress', 'foundRows', 'metaAsArray', 'metaEnumerable', 'multipleStatements', 'namedPlaceholders', 'nestTables', 'permitSetMultiParamEntries', 'pipelining', 'forceVersionCheck', 'rowsAsArray', 'trace', 'bitOneIsBoolean', 'jsonStrings', 'enableKeepAlive', 'supportBigNumbers', 'bigNumberStrings', 'keepEof', 'permitLocalInfile', 'permitConnectionWhenExpired' ]; booleanOptions.forEach((option) => { if (opts[option] !== undefined && typeof opts[option] === 'string') { opts[option] = opts[option] === 'true'; } }); // Convert numeric strings to numbers const numericOptions = [ 'charsetNumber', 'connectTimeout', 'keepAliveDelay', 'socketTimeout', 'debugLen', 'prepareCacheLength', 'queryTimeout', 'maxAllowedPacket', 'keepAliveInitialDelay', 'port' ]; numericOptions.forEach((option) => { if (opts[option] !== undefined && typeof opts[option] === 'string') { const parsedValue = parseInt(opts[option], 10); if (!isNaN(parsedValue)) { opts[option] = parsedValue; } } }); // Handle special case for SSL if (opts.ssl !== undefined && typeof opts.ssl === 'string') { opts.ssl = opts.ssl === 'true'; } // Handle special case for connectAttributes (JSON parsing) if (opts.connectAttributes !== undefined && typeof opts.connectAttributes === 'string') { try { opts.connectAttributes = JSON.parse(opts.connectAttributes); } catch (e) { throw new Error(`Failed to parse connectAttributes as JSON: ${e.message}`); } } // Handle special case for sessionVariables (JSON parsing if it's a string and looks like JSON) if (opts.sessionVariables !== undefined && typeof opts.sessionVariables === 'string') { if (opts.sessionVariables.trim().startsWith('{')) { try { opts.sessionVariables = JSON.parse(opts.sessionVariables); } catch (e) { // If it fails to parse, keep it as a string } } } return opts; } static parse(opts) { const matchResults = opts.match(urlFormat); if (!matchResults) { throw new Error( `error parsing connection string '${opts}'. format must be 'mariadb://[<user>[:<password>]@]<host>[:<port>]/[<db>[?<opt1>=<value1>[&<opt2>=<value2>]]]'` ); } const options = { user: matchResults[2] ? decodeURIComponent(matchResults[2]) : undefined, password: matchResults[4] ? decodeURIComponent(matchResults[4]) : undefined, host: matchResults[6] ? decodeURIComponent(matchResults[6]) : matchResults[6], port: matchResults[8] ? parseInt(matchResults[8]) : undefined, database: matchResults[9] ? decodeURIComponent(matchResults[9]) : matchResults[9] }; const variousOptsString = matchResults[11]; if (variousOptsString) { const keyValues = variousOptsString.split('&'); keyValues.forEach(function (keyVal) { const equalIdx = keyVal.indexOf('='); if (equalIdx !== 1) { let val = keyVal.substring(equalIdx + 1); val = val ? decodeURIComponent(val) : undefined; options[keyVal.substring(0, equalIdx)] = val; } }); } return this.parseOptionDataType(options); } } module.exports = ConnectionOptions;