Comparing version 3.0.0 to 3.0.1
@@ -55,12 +55,10 @@ 'use strict'; | ||
end() { | ||
const cluster = this; | ||
this.#cachedPatterns = {}; | ||
const poolEndPromise = []; | ||
Object.keys(this.#nodes).forEach( | ||
function (pool) { | ||
const res = this.#nodes[pool].end(); | ||
if (res) poolEndPromise.push(); | ||
delete this.#nodes[pool]; | ||
}.bind(this) | ||
); | ||
Object.keys(this.#nodes).forEach((pool) => { | ||
const res = cluster.#nodes[pool].end(); | ||
if (res) poolEndPromise.push(res); | ||
}); | ||
this.#nodes = null; | ||
return Promise.all(poolEndPromise); | ||
@@ -121,6 +119,7 @@ } | ||
* @param lastError last error | ||
* @param remainingRetry remaining possible retry | ||
* @return {Promise} | ||
* @private | ||
*/ | ||
_getConnection(pattern, selector, avoidNodeKey, lastError) { | ||
_getConnection(pattern, selector, remainingRetry, avoidNodeKey, lastError) { | ||
const matchingNodeList = this._matchingNodes(pattern || /^/); | ||
@@ -141,3 +140,5 @@ | ||
const retry = this._getConnection.bind(this, pattern, selector); | ||
if (remainingRetry === undefined) remainingRetry = matchingNodeList.length; | ||
const retry = --remainingRetry >= 0 ? this._getConnection.bind(this, pattern, selector, remainingRetry) : null; | ||
try { | ||
@@ -232,5 +233,4 @@ const nodeKey = this._selectPool(matchingNodeList, selector, avoidNodeKey); | ||
const selector = selectorParam || this.#opts.defaultSelector; | ||
let retry = 0; | ||
let selectorFct; | ||
let nodeKey; | ||
switch (selector) { | ||
@@ -253,10 +253,23 @@ case 'RR': | ||
nodeKey = selectorFct(nodeList, retry); | ||
let nodeIdx = 0; | ||
let nodeKey = selectorFct(nodeList, nodeIdx); | ||
// first loop : search for node not blacklisted AND not the avoided key | ||
while ( | ||
(avoidNodeKey === nodeKey || this.#nodes[nodeKey].blacklistedUntil > Date.now()) && | ||
retry < nodeList.length - 1 | ||
(avoidNodeKey === nodeKey || | ||
(this.#nodes[nodeKey].blacklistedUntil && this.#nodes[nodeKey].blacklistedUntil > Date.now())) && | ||
nodeIdx < nodeList.length - 1 | ||
) { | ||
retry++; | ||
nodeKey = selectorFct(nodeList, retry); | ||
nodeIdx++; | ||
nodeKey = selectorFct(nodeList, nodeIdx); | ||
} | ||
if (avoidNodeKey === nodeKey) { | ||
// second loop, search even in blacklisted node in order to choose a different node than to be avoided | ||
nodeIdx = 0; | ||
while (avoidNodeKey === nodeKey && nodeIdx < nodeList.length - 1) { | ||
nodeIdx++; | ||
nodeKey = selectorFct(nodeList, nodeIdx); | ||
} | ||
} | ||
return nodeKey; | ||
@@ -275,2 +288,3 @@ } | ||
_handleConnectionError(nodeList, nodeKey, retryFct) { | ||
const cluster = this; | ||
const node = this.#nodes[nodeKey]; | ||
@@ -280,34 +294,30 @@ return node | ||
.then((conn) => { | ||
node.blacklistedUntil = null; | ||
node.errorCount = 0; | ||
return Promise.resolve(conn); | ||
}) | ||
.catch( | ||
function (err) { | ||
node.errorCount = node.errorCount ? node.errorCount + 1 : 1; | ||
node.blacklistedUntil = Date.now() + this.#opts.restoreNodeTimeout; | ||
if ( | ||
this.#opts.removeNodeErrorCount && | ||
node.errorCount >= this.#opts.removeNodeErrorCount && | ||
this.#nodes[nodeKey] | ||
) { | ||
delete this.#nodes[nodeKey]; | ||
this.#cachedPatterns = {}; | ||
delete nodeList.lastRrIdx; | ||
process.nextTick( | ||
function () { | ||
this.emit('remove', nodeKey); | ||
}.bind(this) | ||
); | ||
//remove node from configuration if not already removed | ||
node.end().catch((err) => { | ||
// dismiss error | ||
}); | ||
} | ||
.catch((err) => { | ||
node.errorCount = node.errorCount ? node.errorCount + 1 : 1; | ||
node.blacklistedUntil = Date.now() + cluster.#opts.restoreNodeTimeout; | ||
if ( | ||
cluster.#opts.removeNodeErrorCount && | ||
node.errorCount >= cluster.#opts.removeNodeErrorCount && | ||
cluster.#nodes[nodeKey] | ||
) { | ||
delete cluster.#nodes[nodeKey]; | ||
cluster.#cachedPatterns = {}; | ||
delete nodeList.lastRrIdx; | ||
setImmediate(cluster.emit.bind(cluster, 'remove', nodeKey)); | ||
if (nodeList.length !== 0 && this.#opts.canRetry) { | ||
return retryFct(nodeKey, err); | ||
} | ||
return Promise.reject(err); | ||
}.bind(this) | ||
); | ||
//remove node from configuration if not already removed | ||
node.end().catch((err) => { | ||
// dismiss error | ||
}); | ||
} | ||
if (nodeList.length !== 0 && cluster.#opts.canRetry && retryFct) { | ||
return retryFct(nodeKey, err); | ||
} | ||
return Promise.reject(err); | ||
}); | ||
} | ||
@@ -325,36 +335,34 @@ | ||
_handleConnectionCallbackError(nodeList, nodeKey, retryFct, callback) { | ||
const cluster = this; | ||
const node = this.#nodes[nodeKey]; | ||
node.getConnection( | ||
function (err, conn) { | ||
if (err) { | ||
node.errorCount = node.errorCount ? node.errorCount + 1 : 1; | ||
node.blacklistedUntil = Date.now() + this.#opts.restoreNodeTimeout; | ||
if ( | ||
this.#opts.removeNodeErrorCount && | ||
node.errorCount >= this.#opts.removeNodeErrorCount && | ||
this.#nodes[nodeKey] | ||
) { | ||
delete this.#nodes[nodeKey]; | ||
this.#cachedPatterns = {}; | ||
delete nodeList.lastRrIdx; | ||
process.nextTick( | ||
function () { | ||
this.emit('remove', nodeKey); | ||
}.bind(this) | ||
); | ||
//remove node from configuration if not already removed | ||
node.end(() => { | ||
//dismiss error | ||
}); | ||
if (nodeList.length === 0) return callback(err); | ||
} | ||
node.getConnection((err, conn) => { | ||
if (err) { | ||
node.errorCount = node.errorCount ? node.errorCount + 1 : 1; | ||
node.blacklistedUntil = Date.now() + cluster.#opts.restoreNodeTimeout; | ||
if ( | ||
cluster.#opts.removeNodeErrorCount && | ||
node.errorCount >= cluster.#opts.removeNodeErrorCount && | ||
cluster.#nodes[nodeKey] | ||
) { | ||
delete cluster.#nodes[nodeKey]; | ||
cluster.#cachedPatterns = {}; | ||
delete nodeList.lastRrIdx; | ||
setImmediate(cluster.emit.bind(cluster, 'remove', nodeKey)); | ||
if (this.#opts.canRetry) return retryFct(nodeKey, err); | ||
callback(err); | ||
} else { | ||
node.errorCount = 0; | ||
callback(null, conn); | ||
//remove node from configuration if not already removed | ||
node.end(() => { | ||
//dismiss error | ||
}); | ||
} | ||
}.bind(this) | ||
); | ||
if (nodeList.length !== 0 && cluster.#opts.canRetry && retryFct) { | ||
return retryFct(nodeKey, err); | ||
} | ||
callback(err); | ||
} else { | ||
node.errorCount = 0; | ||
callback(null, conn); | ||
} | ||
}); | ||
} | ||
@@ -361,0 +369,0 @@ |
@@ -14,4 +14,4 @@ 'use strict'; | ||
class BatchBulk extends Parser { | ||
constructor(resolve, reject, options, connOpts, prepare, values) { | ||
super(resolve, reject, options, connOpts, prepare.query, values); | ||
constructor(resolve, reject, connOpts, prepare, cmdParam) { | ||
super(resolve, reject, connOpts, cmdParam); | ||
this.encoder = new BinaryEncoder(this.opts); | ||
@@ -35,2 +35,4 @@ this.binary = true; | ||
if (this.opts.timeout) { | ||
this.bulkPacketNo = 1; | ||
this.sending = false; | ||
const err = Errors.createError( | ||
@@ -305,4 +307,2 @@ 'Cannot use timeout for Batch statement', | ||
} | ||
// if > max_allowed_packet, need to flush before mark | ||
//TODO | ||
@@ -324,3 +324,4 @@ if (!out.bufIsDataAfterMark() && !out.isMarked() && out.hasFlushed()) { | ||
if (out.isMarked() && out.bufIsAfterMaxPacketLength()) { | ||
// buffer > max_allowed_packet, so flush until mark, and create new packet. | ||
// for max_allowed_packet < 16Mb | ||
// packet length was ok at last mark, but won't with new data | ||
out.flushBufferStopAtMark(); | ||
@@ -361,22 +362,19 @@ out.mark(); | ||
displaySql() { | ||
if (this.opts && this.initialValues) { | ||
if (this.sql.length > this.opts.debugLen) { | ||
return this.sql.substring(0, this.opts.debugLen) + '...'; | ||
} | ||
if (this.sql.length > this.opts.debugLen) { | ||
return this.sql.substring(0, this.opts.debugLen) + '...'; | ||
} | ||
let sqlMsg = this.sql + ' - parameters:'; | ||
sqlMsg += '['; | ||
for (let i = 0; i < this.initialValues.length; i++) { | ||
if (i !== 0) sqlMsg += ','; | ||
let param = this.initialValues[i]; | ||
sqlMsg = this.logParameters(sqlMsg, param); | ||
if (sqlMsg.length > this.opts.debugLen) { | ||
sqlMsg = sqlMsg.substr(0, this.opts.debugLen) + '...'; | ||
break; | ||
} | ||
let sqlMsg = this.sql + ' - parameters:'; | ||
sqlMsg += '['; | ||
for (let i = 0; i < this.initialValues.length; i++) { | ||
if (i !== 0) sqlMsg += ','; | ||
let param = this.initialValues[i]; | ||
sqlMsg = this.logParameters(sqlMsg, param); | ||
if (sqlMsg.length > this.opts.debugLen) { | ||
sqlMsg = sqlMsg.substr(0, this.opts.debugLen) + '...'; | ||
break; | ||
} | ||
sqlMsg += ']'; | ||
return sqlMsg; | ||
} | ||
return this.sql + ' - parameters:[]'; | ||
sqlMsg += ']'; | ||
return sqlMsg; | ||
} | ||
@@ -383,0 +381,0 @@ |
@@ -17,5 +17,5 @@ // noinspection JSBitwiseOperatorUsage | ||
class ChangeUser extends Handshake { | ||
constructor(options, connOpts, resolve, reject, addCommand) { | ||
super(resolve, reject, () => {}, addCommand); | ||
this.configAssign(connOpts, options); | ||
constructor(cmdParam, connOpts, resolve, reject, addCommand) { | ||
super(cmdParam, resolve, reject, () => {}, addCommand); | ||
this.configAssign(connOpts, cmdParam.opts); | ||
} | ||
@@ -83,3 +83,3 @@ | ||
const encoding = this.opts.collation.charset; | ||
const encoding = info.collation.charset; | ||
@@ -125,4 +125,2 @@ writeParam(out, '_client_name', encoding); | ||
this.opts = cmdOpts ? Object.assign({}, connOpts, cmdOpts) : connOpts; | ||
this.opts.database = cmdOpts.database ? cmdOpts.database : connOpts.database; | ||
this.opts.connectAttributes = cmdOpts.connectAttributes ? cmdOpts.connectAttributes : connOpts.connectAttributes; | ||
@@ -129,0 +127,0 @@ if (cmdOpts.charset && typeof cmdOpts.charset === 'string') { |
@@ -10,4 +10,4 @@ 'use strict'; | ||
class CachedPrepareResultPacket extends PrepareResultPacket { | ||
constructor(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter, isCallback) { | ||
super(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter, isCallback); | ||
constructor(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter, conOpts) { | ||
super(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter, conOpts); | ||
this.cached = true; | ||
@@ -14,0 +14,0 @@ this.use = 1; |
'use strict'; | ||
const CommandParameter = require('../../command-parameter'); | ||
@@ -9,3 +10,3 @@ /** | ||
#connExecutePromise; | ||
constructor(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter) { | ||
constructor(statementId, parameters, columns, database, sql, placeHolderIndex, executePromise, emitter, conOpts) { | ||
this.id = statementId; | ||
@@ -20,5 +21,6 @@ this.parameters = parameters; | ||
this.emitter = emitter; | ||
this.conOpts = conOpts; | ||
} | ||
execute(values, opts, cb) { | ||
execute(values, opts, cb, stack) { | ||
let _opts = opts, | ||
@@ -31,4 +33,5 @@ _cb = cb; | ||
} | ||
const promise = new Promise((resolve, reject) => this.#connExecutePromise(values, opts, this, resolve, reject)); | ||
const cmdParam = new CommandParameter(this.query, values, _opts, cb); | ||
if (stack) cmdParam.stack = stack; | ||
const promise = new Promise((resolve, reject) => this.#connExecutePromise(cmdParam, this, resolve, reject)); | ||
if (!_cb) { | ||
@@ -35,0 +38,0 @@ return promise; |
@@ -10,4 +10,4 @@ 'use strict'; | ||
class ClosePrepare extends Command { | ||
constructor(resolve, reject, prepare) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject, prepare) { | ||
super(cmdParam, resolve, reject); | ||
this.prepare = prepare; | ||
@@ -14,0 +14,0 @@ } |
@@ -14,4 +14,5 @@ 'use strict'; | ||
class ColumnDef { | ||
constructor(packet, info) { | ||
this._stringParser = new StringParser(packet); | ||
#stringParser; | ||
constructor(packet, info, skipName) { | ||
this.#stringParser = skipName ? new StringParser(packet) : new StringParserWithName(packet); | ||
if (info.serverCapabilities & Capabilities.MARIADB_CLIENT_EXTENDED_TYPE_INFO) { | ||
@@ -49,234 +50,2 @@ const subPacket = packet.subPacketLengthEncoded(); | ||
__parser(binary, opts) { | ||
// set reader function read(packet, index, nullBitmap, opts) | ||
// this permit for multi-row result-set to avoid resolving type parsing each data. | ||
if (binary) { | ||
switch (this.columnType) { | ||
case FieldType.TINY: | ||
if (this.signed()) { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readInt8()); | ||
} else { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readUInt8()); | ||
} | ||
case FieldType.YEAR: | ||
case FieldType.SHORT: | ||
if (this.signed()) { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readInt16()); | ||
} else { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readUInt16()); | ||
} | ||
case FieldType.INT24: | ||
if (this.signed()) { | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return null; | ||
} | ||
const result = packet.readInt24(); | ||
packet.skip(1); // MEDIUMINT is encoded on 4 bytes in exchanges ! | ||
return result; | ||
}; | ||
} else { | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return null; | ||
} | ||
const result = packet.readUInt24(); | ||
packet.skip(1); // MEDIUMINT is encoded on 4 bytes in exchanges ! | ||
return result; | ||
}; | ||
} | ||
case FieldType.INT: | ||
if (this.signed()) { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readInt32()); | ||
} else { | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readUInt32()); | ||
} | ||
case FieldType.FLOAT: | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readFloat()); | ||
case FieldType.DOUBLE: | ||
return (packet, index, nullBitmap, opts) => (isNullBitmap(index, nullBitmap) ? null : packet.readDouble()); | ||
case FieldType.BIGINT: | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const val = this.signed() ? packet.readBigInt64() : packet.readBigUInt64(); | ||
if (val != null && (opts.bigIntAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(val)))) { | ||
return val.toString(); | ||
} | ||
return Number(val); | ||
} | ||
return val; | ||
}; | ||
case FieldType.DATE: | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryDate(opts); | ||
case FieldType.DATETIME: | ||
case FieldType.TIMESTAMP: | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryDateTime(opts, this); | ||
case FieldType.TIME: | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryTime(); | ||
case FieldType.DECIMAL: | ||
case FieldType.NEWDECIMAL: | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
case FieldType.GEOMETRY: | ||
let defaultVal = this.__getDefaultGeomVal(); | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return defaultVal; | ||
} | ||
return packet.readGeometry(defaultVal); | ||
}; | ||
case FieldType.JSON: | ||
//for mysql only => parse string as JSON object | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : JSON.parse(packet.readStringLengthEncoded()); | ||
case FieldType.BIT: | ||
if (this.columnLength === 1 && opts.bitOneIsBoolean) { | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBufferLengthEncoded()[0] === 1; | ||
} | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBufferLengthEncoded(); | ||
default: | ||
if (this.dataTypeFormat && this.dataTypeFormat === 'json' && opts.autoJsonMap) { | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : JSON.parse(packet.readStringLengthEncoded()); | ||
} | ||
if (this.collation.index === 63) { | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBufferLengthEncoded(); | ||
} | ||
if (this.isSet()) { | ||
return (packet, index, nullBitmap, opts) => { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const string = packet.readStringLengthEncoded(); | ||
return string == null ? null : string === '' ? [] : string.split(','); | ||
}; | ||
} | ||
return (packet, index, nullBitmap, opts) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readStringLengthEncoded(); | ||
} | ||
} else { | ||
switch (this.columnType) { | ||
case FieldType.TINY: | ||
case FieldType.SHORT: | ||
case FieldType.INT: | ||
case FieldType.INT24: | ||
case FieldType.YEAR: | ||
return (packet, index, nullBitmap, opts) => packet.readIntLengthEncoded(); | ||
case FieldType.FLOAT: | ||
case FieldType.DOUBLE: | ||
return (packet, index, nullBitmap, opts) => packet.readFloatLengthCoded(); | ||
case FieldType.BIGINT: | ||
return (packet, index, nullBitmap, opts) => { | ||
const val = packet.readBigIntLengthEncoded(); | ||
if (val != null && (opts.bigIntAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(val)))) { | ||
return val.toString(); | ||
} | ||
return Number(val); | ||
} | ||
return val; | ||
}; | ||
case FieldType.DECIMAL: | ||
case FieldType.NEWDECIMAL: | ||
return (packet, index, nullBitmap, opts) => { | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
case FieldType.DATE: | ||
return (packet, index, nullBitmap, opts) => { | ||
if (opts.dateStrings) { | ||
return packet.readAsciiStringLengthEncoded(); | ||
} | ||
return packet.readDate(); | ||
}; | ||
case FieldType.DATETIME: | ||
case FieldType.TIMESTAMP: | ||
return (packet, index, nullBitmap, opts) => { | ||
if (opts.dateStrings) { | ||
return packet.readAsciiStringLengthEncoded(); | ||
} | ||
return packet.readDateTime(opts); | ||
}; | ||
case FieldType.TIME: | ||
return (packet, index, nullBitmap, opts) => packet.readAsciiStringLengthEncoded(); | ||
case FieldType.GEOMETRY: | ||
let defaultVal = this.__getDefaultGeomVal(); | ||
return (packet, index, nullBitmap, opts) => packet.readGeometry(defaultVal); | ||
case FieldType.JSON: | ||
//for mysql only => parse string as JSON object | ||
return (packet, index, nullBitmap, opts) => JSON.parse(packet.readStringLengthEncoded()); | ||
case FieldType.BIT: | ||
if (this.columnLength === 1 && opts.bitOneIsBoolean) { | ||
return (packet, index, nullBitmap, opts) => { | ||
const val = packet.readBufferLengthEncoded(); | ||
return val == null ? null : val[0] === 1; | ||
}; | ||
} | ||
return (packet, index, nullBitmap, opts) => packet.readBufferLengthEncoded(); | ||
default: | ||
if (this.dataTypeFormat && this.dataTypeFormat === 'json' && opts.autoJsonMap) { | ||
return (packet, index, nullBitmap, opts) => JSON.parse(packet.readStringLengthEncoded()); | ||
} | ||
if (this.collation.index === 63) { | ||
return (packet, index, nullBitmap, opts) => packet.readBufferLengthEncoded(); | ||
} | ||
if (this.isSet()) { | ||
return (packet, index, nullBitmap, opts) => { | ||
const string = packet.readStringLengthEncoded(); | ||
return string == null ? null : string === '' ? [] : string.split(','); | ||
}; | ||
} | ||
return (packet, index, nullBitmap, opts) => packet.readStringLengthEncoded(); | ||
} | ||
} | ||
} | ||
__getDefaultGeomVal() { | ||
@@ -303,24 +72,25 @@ if (this.dataTypeName) { | ||
} | ||
db() { | ||
return this._stringParser.packet.readString(this._stringParser.dbOffset, this._stringParser.dbLength); | ||
return this.#stringParser.packet.readString(this.#stringParser.dbOffset, this.#stringParser.dbLength); | ||
} | ||
schema() { | ||
return this._stringParser.packet.readString(this._stringParser.dbOffset, this._stringParser.dbLength); | ||
return this.#stringParser.packet.readString(this.#stringParser.dbOffset, this.#stringParser.dbLength); | ||
} | ||
table() { | ||
return this._stringParser.packet.readString(this._stringParser.tableOffset, this._stringParser.tableLength); | ||
return this.#stringParser.packet.readString(this.#stringParser.tableOffset, this.#stringParser.tableLength); | ||
} | ||
orgTable() { | ||
return this._stringParser.packet.readString(this._stringParser.orgTableOffset, this._stringParser.orgTableLength); | ||
return this.#stringParser.packet.readString(this.#stringParser.orgTableOffset, this.#stringParser.orgTableLength); | ||
} | ||
name() { | ||
return this._stringParser.packet.readString(this._stringParser.nameOffset, this._stringParser.nameLength); | ||
return this.#stringParser.name(); | ||
} | ||
orgName() { | ||
return this._stringParser.packet.readString(this._stringParser.orgNameOffset, this._stringParser.orgNameLength); | ||
return this.#stringParser.packet.readString(this.#stringParser.orgNameOffset, this.#stringParser.orgNameLength); | ||
} | ||
@@ -337,6 +107,2 @@ | ||
const isNullBitmap = (index, nullBitmap) => { | ||
return (nullBitmap[Math.floor((index + 2) / 8)] & (1 << (index + 2) % 8)) > 0; | ||
}; | ||
/** | ||
@@ -371,4 +137,39 @@ * String parser. | ||
} | ||
name = function () { | ||
return this.packet.readString(this.nameOffset, this.nameLength); | ||
}; | ||
} | ||
/** | ||
* String parser. | ||
* This object permits to avoid listing all private information to metadata object. | ||
*/ | ||
class StringParserWithName { | ||
constructor(packet) { | ||
packet.skip(4); // skip 'def' | ||
this.dbLength = packet.readUnsignedLength(); | ||
this.dbOffset = packet.pos; | ||
packet.skip(this.dbLength); | ||
this.tableLength = packet.readUnsignedLength(); | ||
this.tableOffset = packet.pos; | ||
packet.skip(this.tableLength); | ||
this.orgTableLength = packet.readUnsignedLength(); | ||
this.orgTableOffset = packet.pos; | ||
packet.skip(this.orgTableLength); | ||
this.colName = packet.readStringLengthEncoded(); | ||
this.orgNameLength = packet.readUnsignedLength(); | ||
this.orgNameOffset = packet.pos; | ||
packet.skip(this.orgNameLength); | ||
this.packet = packet; | ||
} | ||
name = function () { | ||
return this.colName; | ||
}; | ||
} | ||
module.exports = ColumnDef; |
@@ -14,4 +14,5 @@ 'use strict'; | ||
class Command extends EventEmitter { | ||
constructor(resolve, reject) { | ||
constructor(cmdParam, resolve, reject) { | ||
super(); | ||
this.cmdParam = cmdParam; | ||
this.sequenceNo = -1; | ||
@@ -22,2 +23,3 @@ this.compressSequenceNo = -1; | ||
this.sending = false; | ||
this.unexpectedError = this.throwUnexpectedError.bind(this); | ||
} | ||
@@ -30,3 +32,3 @@ | ||
/** | ||
* Throw an an unexpected error. | ||
* Throw an unexpected error. | ||
* server exchange will still be read to keep connection in a good state, but promise will be rejected. | ||
@@ -41,3 +43,12 @@ * | ||
throwUnexpectedError(msg, fatal, info, sqlState, errno) { | ||
const err = Errors.createError(msg, errno, info, sqlState, this.displaySql(), fatal, this.stack, false); | ||
const err = Errors.createError( | ||
msg, | ||
errno, | ||
info, | ||
sqlState, | ||
this.displaySql(), | ||
fatal, | ||
this.cmdParam ? this.cmdParam.stack : null, | ||
false | ||
); | ||
if (this.reject) { | ||
@@ -78,3 +89,3 @@ process.nextTick(this.reject, err); | ||
if (this.reject) { | ||
if (this.stack) { | ||
if (this.cmdParam && this.cmdParam.stack) { | ||
err = Errors.createError( | ||
@@ -87,3 +98,3 @@ err.text ? err.text : err.message, | ||
err.fatal, | ||
this.stack, | ||
this.cmdParam.stack, | ||
false | ||
@@ -115,3 +126,3 @@ ); | ||
parseOkPacket(packet, out, opts, info) { | ||
parseOkPacket(packet, out, opts, connectionOpts, info) { | ||
packet.skip(1); //skip header | ||
@@ -121,3 +132,7 @@ | ||
let insertId = packet.readSignedLengthBigInt(); | ||
info.status = packet.readUInt16(); | ||
if (insertId != null && (opts.supportBigNumbers || opts.insertIdAsNumber)) { | ||
if (opts.insertIdAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(insertId))) { | ||
throw new Error(`last insert id value ${insertId} can't safely be converted to number`); | ||
} | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(insertId)))) { | ||
@@ -127,3 +142,2 @@ insertId = insertId.toString(); | ||
} | ||
info.status = packet.readUInt16(); | ||
@@ -151,3 +165,3 @@ const okPacket = new OkPacket(affectedRows, insertId, packet.readUInt16()); | ||
} | ||
opts.emit('collation', info.collation); | ||
connectionOpts.emit('collation', info.collation); | ||
break; | ||
@@ -154,0 +168,0 @@ |
'use strict'; | ||
const FieldType = require('../../const/field-type'); | ||
const Errors = require('../../misc/errors'); | ||
class BinaryDecoder { | ||
@@ -54,5 +57,71 @@ static newRow(packet, columns) { | ||
} | ||
return column.readGeometry(defaultVal); | ||
return packet.readGeometry(defaultVal); | ||
}; | ||
} | ||
static parser(col, opts) { | ||
// set reader function read(col, packet, index, nullBitmap, opts, throwUnexpectedError) | ||
// this permit for multi-row result-set to avoid resolving type parsing each data. | ||
switch (col.columnType) { | ||
case FieldType.TINY: | ||
return col.signed() ? readTinyBinarySigned : readTinyBinaryUnsigned; | ||
case FieldType.YEAR: | ||
case FieldType.SHORT: | ||
return col.signed() ? readShortBinarySigned : readShortBinaryUnsigned; | ||
case FieldType.INT24: | ||
return col.signed() ? readMediumBinarySigned : readMediumBinaryUnsigned; | ||
case FieldType.INT: | ||
return col.signed() ? readIntBinarySigned : readIntBinaryUnsigned; | ||
case FieldType.FLOAT: | ||
return readFloatBinary; | ||
case FieldType.DOUBLE: | ||
return readDoubleBinary; | ||
case FieldType.BIGINT: | ||
return opts.bigIntAsNumber || opts.supportBigNumbers ? readBigintAsIntBinary : readBigintBinary; | ||
case FieldType.DATE: | ||
return readDateBinary; | ||
case FieldType.DATETIME: | ||
case FieldType.TIMESTAMP: | ||
return opts.dateStrings ? readTimestampStringBinary : readTimestampBinary; | ||
case FieldType.TIME: | ||
return readTimeBinary; | ||
case FieldType.DECIMAL: | ||
case FieldType.NEWDECIMAL: | ||
return col.scale == 0 ? readDecimalAsIntBinary : readDecimalBinary; | ||
case FieldType.GEOMETRY: | ||
return readGeometryBinary; | ||
case FieldType.JSON: | ||
//for mysql only => parse string as JSON object | ||
return readJsonBinary; | ||
case FieldType.BIT: | ||
if (col.columnLength === 1 && opts.bitOneIsBoolean) { | ||
return readBitBinaryBoolean; | ||
} | ||
return readBinaryBuffer; | ||
default: | ||
if (col.dataTypeFormat && col.dataTypeFormat === 'json' && opts.autoJsonMap) { | ||
return readJsonBinary; | ||
} | ||
if (col.collation.index === 63) { | ||
return readBinaryBuffer; | ||
} | ||
if (col.isSet()) { | ||
return readBinarySet; | ||
} | ||
return readStringBinary; | ||
} | ||
} | ||
} | ||
@@ -62,2 +131,119 @@ const isNullBitmap = (index, nullBitmap) => { | ||
}; | ||
module.exports = BinaryDecoder; | ||
const readTinyBinarySigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readInt8(); | ||
const readTinyBinaryUnsigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readUInt8(); | ||
const readShortBinarySigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readInt16(); | ||
const readShortBinaryUnsigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readUInt16(); | ||
const readMediumBinarySigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return null; | ||
} | ||
const result = packet.readInt24(); | ||
packet.skip(1); // MEDIUMINT is encoded on 4 bytes in exchanges ! | ||
return result; | ||
}; | ||
const readMediumBinaryUnsigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return null; | ||
} | ||
const result = packet.readInt24(); | ||
packet.skip(1); // MEDIUMINT is encoded on 4 bytes in exchanges ! | ||
return result; | ||
}; | ||
const readIntBinarySigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readInt32(); | ||
const readIntBinaryUnsigned = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readUInt32(); | ||
const readFloatBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readFloat(); | ||
const readDoubleBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readDouble(); | ||
const readBigintBinary = function (col, packet, index, nullBitmap, opts, throwUnexpectedError) { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
return col.signed() ? packet.readBigInt64() : packet.readBigUInt64(); | ||
}; | ||
const readBigintAsIntBinary = function (col, packet, index, nullBitmap, opts, throwUnexpectedError) { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const val = col.signed() ? packet.readBigInt64() : packet.readBigUInt64(); | ||
if (opts.bigIntAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(val))) { | ||
return throwUnexpectedError( | ||
`value ${val} can't safely be converted to number`, | ||
false, | ||
null, | ||
'42000', | ||
Errors.ER_PARSING_PRECISION | ||
); | ||
} | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(val)))) { | ||
return val.toString(); | ||
} | ||
return Number(val); | ||
}; | ||
const readGeometryBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
let defaultVal = col.__getDefaultGeomVal(); | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return defaultVal; | ||
} | ||
return packet.readGeometry(defaultVal); | ||
}; | ||
const readDateBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryDate(opts); | ||
const readTimestampBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryDateTime(opts); | ||
const readTimestampStringBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryDateTimeAsString(col.scale); | ||
const readTimeBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBinaryTime(); | ||
const readDecimalAsIntBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
//checkNumberRange additional check is only done when | ||
// resulting value is an integer | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.decimalAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(valDec))) { | ||
return throwUnexpectedError( | ||
`value ${valDec} can't safely be converted to number`, | ||
false, | ||
null, | ||
'42000', | ||
Errors.ER_PARSING_PRECISION | ||
); | ||
} | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
const readDecimalBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
const readJsonBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : JSON.parse(packet.readStringLengthEncoded()); | ||
const readBitBinaryBoolean = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBufferLengthEncoded()[0] === 1; | ||
const readBinaryBuffer = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readBufferLengthEncoded(); | ||
const readBinarySet = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (isNullBitmap(index, nullBitmap)) return null; | ||
const string = packet.readStringLengthEncoded(); | ||
return string == null ? null : string === '' ? [] : string.split(','); | ||
}; | ||
const readStringBinary = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
isNullBitmap(index, nullBitmap) ? null : packet.readStringLengthEncoded(); |
'use strict'; | ||
const FieldType = require('../../const/field-type'); | ||
const Errors = require('../../misc/errors'); | ||
class TextDecoder { | ||
@@ -44,10 +47,152 @@ static castWrapper(column, packet, index, nullBitmap, opts) { | ||
if (isNullBitmap(index, nullBitmap)) { | ||
return defaultVal; | ||
} | ||
return column.readGeometry(defaultVal); | ||
return packet.readGeometry(defaultVal); | ||
}; | ||
} | ||
static parser(col, opts) { | ||
// set reader function read(col, packet, index, nullBitmap, opts, throwUnexpectedError) | ||
// this permit for multi-row result-set to avoid resolving type parsing each data. | ||
switch (col.columnType) { | ||
case FieldType.TINY: | ||
case FieldType.SHORT: | ||
case FieldType.INT: | ||
case FieldType.INT24: | ||
case FieldType.YEAR: | ||
return readIntLengthEncoded; | ||
case FieldType.FLOAT: | ||
case FieldType.DOUBLE: | ||
return readFloatLengthCoded; | ||
case FieldType.BIGINT: | ||
if (opts.bigIntAsNumber || opts.supportBigNumbers) return readBigIntAsNumberLengthCoded; | ||
return readBigIntLengthCoded; | ||
case FieldType.DECIMAL: | ||
case FieldType.NEWDECIMAL: | ||
return col.scale == 0 ? readDecimalAsIntLengthCoded : readDecimalLengthCoded; | ||
case FieldType.DATE: | ||
return readDate; | ||
case FieldType.DATETIME: | ||
case FieldType.TIMESTAMP: | ||
return readTimestamp; | ||
case FieldType.TIME: | ||
return readAsciiStringLengthEncoded; | ||
case FieldType.GEOMETRY: | ||
return readGeometry; | ||
case FieldType.JSON: | ||
//for mysql only => parse string as JSON object | ||
return readJson; | ||
case FieldType.BIT: | ||
if (col.columnLength === 1 && opts.bitOneIsBoolean) { | ||
return readBitAsBoolean; | ||
} | ||
return readBufferLengthEncoded; | ||
default: | ||
if (col.dataTypeFormat && col.dataTypeFormat === 'json' && opts.autoJsonMap) { | ||
return readJson; | ||
} | ||
if (col.collation.index === 63) { | ||
return readBufferLengthEncoded; | ||
} | ||
if (col.isSet()) { | ||
return readSet; | ||
} | ||
return readStringLengthEncoded; | ||
} | ||
} | ||
} | ||
module.exports = TextDecoder; | ||
const readGeometry = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
let defaultVal = col.__getDefaultGeomVal(); | ||
return packet.readGeometry(defaultVal); | ||
}; | ||
const readIntLengthEncoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readIntLengthEncoded(); | ||
const readStringLengthEncoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readStringLengthEncoded(); | ||
const readFloatLengthCoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readFloatLengthCoded(); | ||
const readBigIntLengthCoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readBigIntLengthEncoded(); | ||
const readBigIntAsNumberLengthCoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
const val = packet.readBigIntLengthEncoded(); | ||
if (val == null) return null; | ||
if (opts.bigIntAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(val))) { | ||
return throwUnexpectedError( | ||
`value ${val} can't safely be converted to number`, | ||
false, | ||
null, | ||
'42000', | ||
Errors.ER_PARSING_PRECISION | ||
); | ||
} | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(val)))) { | ||
return val.toString(); | ||
} | ||
return Number(val); | ||
}; | ||
const readDecimalAsIntLengthCoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.decimalAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(valDec))) { | ||
return throwUnexpectedError( | ||
`value ${valDec} can't safely be converted to number`, | ||
false, | ||
null, | ||
'42000', | ||
Errors.ER_PARSING_PRECISION | ||
); | ||
} | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
const readDecimalLengthCoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
const valDec = packet.readDecimalLengthEncoded(); | ||
if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) { | ||
if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) { | ||
return valDec.toString(); | ||
} | ||
return Number(valDec); | ||
} | ||
return valDec; | ||
}; | ||
const readDate = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (opts.dateStrings) { | ||
return packet.readAsciiStringLengthEncoded(); | ||
} | ||
return packet.readDate(); | ||
}; | ||
const readTimestamp = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => { | ||
if (opts.dateStrings) { | ||
return packet.readAsciiStringLengthEncoded(); | ||
} | ||
return packet.readDateTime(opts); | ||
}; | ||
const readAsciiStringLengthEncoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readAsciiStringLengthEncoded(); | ||
const readBitAsBoolean = (col, packet, index, nullBitmap, opts) => { | ||
const val = packet.readBufferLengthEncoded(); | ||
return val == null ? null : val[0] === 1; | ||
}; | ||
const readBufferLengthEncoded = (col, packet, index, nullBitmap, opts, throwUnexpectedError) => | ||
packet.readBufferLengthEncoded(); | ||
const readJson = (col, packet, index, nullBitmap, opts) => JSON.parse(packet.readStringLengthEncoded()); | ||
const readSet = (col, packet, index, nullBitmap, opts) => { | ||
const string = packet.readStringLengthEncoded(); | ||
return string == null ? null : string === '' ? [] : string.split(','); | ||
}; |
@@ -32,2 +32,3 @@ 'use strict'; | ||
writeParam(out, value, opts, info) { | ||
// GEOJSON are not checked, because change to null/Buffer on parameter validation | ||
switch (typeof value) { | ||
@@ -65,23 +66,3 @@ case 'boolean': | ||
} else { | ||
if ( | ||
value.type != null && | ||
[ | ||
'Point', | ||
'LineString', | ||
'Polygon', | ||
'MultiPoint', | ||
'MultiLineString', | ||
'MultiPolygon', | ||
'GeometryCollection' | ||
].includes(value.type) | ||
) { | ||
const geoBuff = BinaryEncoder.getBufferFromGeometryValue(value); | ||
const completeBuf = Buffer.concat([ | ||
Buffer.from([0, 0, 0, 0]), // SRID | ||
geoBuff // WKB | ||
]); | ||
out.writeLengthEncodedBuffer(completeBuf); | ||
} else { | ||
out.writeLengthEncodedString(JSON.stringify(value)); | ||
} | ||
out.writeLengthEncodedString(JSON.stringify(value)); | ||
} | ||
@@ -88,0 +69,0 @@ break; |
@@ -14,4 +14,4 @@ 'use strict'; | ||
class Execute extends Parser { | ||
constructor(resolve, reject, options, connOpts, prepare, values) { | ||
super(resolve, reject, options, connOpts, prepare.query, values); | ||
constructor(resolve, reject, connOpts, cmdParam, prepare) { | ||
super(resolve, reject, connOpts, cmdParam); | ||
this.encoder = new BinaryEncoder(this.opts); | ||
@@ -223,3 +223,3 @@ this.binary = true; | ||
if (Object.prototype.toString.call(val) === '[object Date]') { | ||
out.writeInt8(FieldType.TIMESTAMP); | ||
out.writeInt8(FieldType.DATETIME); | ||
} else if (Buffer.isBuffer(val)) { | ||
@@ -226,0 +226,0 @@ out.writeInt8(FieldType.BLOB); |
@@ -19,4 +19,4 @@ const PluginAuth = require('./plugin-auth'); | ||
class CachingSha2PasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.pluginData = pluginData; | ||
@@ -23,0 +23,0 @@ this.sequenceNo = packSeq; |
@@ -8,4 +8,4 @@ const PluginAuth = require('./plugin-auth'); | ||
class ClearPasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.sequenceNo = packSeq; | ||
@@ -16,3 +16,10 @@ } | ||
out.startPacket(this); | ||
if (opts.password) out.writeString(opts.password); | ||
const pwd = opts.password; | ||
if (pwd) { | ||
if (Array.isArray(pwd)) { | ||
out.writeString(pwd[0]); | ||
} else { | ||
out.writeString(pwd); | ||
} | ||
} | ||
out.writeInt8(0); | ||
@@ -19,0 +26,0 @@ out.flushPacket(); |
@@ -10,4 +10,4 @@ 'use strict'; | ||
class Ed25519PasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.pluginData = pluginData; | ||
@@ -14,0 +14,0 @@ this.sequenceNo = packSeq; |
@@ -10,4 +10,4 @@ 'use strict'; | ||
class NativePasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.pluginData = pluginData; | ||
@@ -14,0 +14,0 @@ this.sequenceNo = packSeq; |
@@ -7,4 +7,4 @@ const PluginAuth = require('./plugin-auth'); | ||
class PamPasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.pluginData = pluginData; | ||
@@ -11,0 +11,0 @@ this.sequenceNo = packSeq; |
@@ -9,4 +9,4 @@ 'use strict'; | ||
class PluginAuth extends Command { | ||
constructor(resolve, reject, multiAuthResolver) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject); | ||
this.multiAuthResolver = multiAuthResolver; | ||
@@ -13,0 +13,0 @@ } |
@@ -10,4 +10,4 @@ const PluginAuth = require('./plugin-auth'); | ||
class Sha256PasswordAuth extends PluginAuth { | ||
constructor(packSeq, compressPackSeq, pluginData, resolve, reject, multiAuthResolver) { | ||
super(resolve, reject, multiAuthResolver); | ||
constructor(packSeq, compressPackSeq, pluginData, cmdParam, resolve, reject, multiAuthResolver) { | ||
super(cmdParam, resolve, reject, multiAuthResolver); | ||
this.pluginData = pluginData; | ||
@@ -14,0 +14,0 @@ this.sequenceNo = packSeq; |
@@ -18,4 +18,5 @@ 'use strict'; | ||
class Handshake extends Command { | ||
constructor(resolve, reject, _createSecureContext, _addCommand, getSocket) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject, _createSecureContext, _addCommand, getSocket) { | ||
super(cmdParam, resolve, reject); | ||
this.cmdParam = cmdParam; | ||
this._createSecureContext = _createSecureContext; | ||
@@ -28,24 +29,2 @@ this._addCommand = _addCommand; | ||
ensureOptionCompatibility(opts, info) { | ||
if (opts.multipleStatements && (info.serverCapabilities & Capabilities.MULTI_STATEMENTS) === 0) { | ||
return this.throwNewError( | ||
"Option `multipleStatements` enable, but server doesn'permits multi-statment", | ||
true, | ||
info, | ||
'08S01', | ||
Errors.ER_CLIENT_OPTION_INCOMPATIBILITY | ||
); | ||
} | ||
if (opts.permitLocalInfile && (info.serverCapabilities & Capabilities.LOCAL_FILES) === 0) { | ||
return this.throwNewError( | ||
"Option `permitLocalInfile` enable, but server doesn'permits using local file", | ||
true, | ||
info, | ||
'08S01', | ||
Errors.ER_CLIENT_OPTION_INCOMPATIBILITY | ||
); | ||
} | ||
} | ||
parseHandshakeInit(packet, out, opts, info) { | ||
@@ -60,3 +39,2 @@ if (packet.peek() === 0xff) { | ||
let handshake = new InitialHandshake(packet, info); | ||
this.ensureOptionCompatibility(opts, info); | ||
@@ -70,7 +48,5 @@ // handle default collation. | ||
} | ||
} else { | ||
} else if (info.collation.charset !== 'utf8' || info.collation.maxLength === 3) { | ||
// if not utf8mb4 and no configuration, force to use UTF8MB4_UNICODE_CI | ||
if (info.collation.charset !== 'utf8' || info.collation.maxLength === 3) { | ||
info.collation = Collations.fromIndex(224); | ||
} | ||
info.collation = Collations.fromIndex(224); | ||
} | ||
@@ -186,5 +162,6 @@ | ||
} else { | ||
pluginName = packet.readStringNullEnded('cesu8'); | ||
pluginName = packet.readStringNullEnded('ascii'); | ||
pluginData = packet.readBufferRemaining(); | ||
} | ||
if (opts.restrictedAuth && !opts.restrictedAuth.includes(pluginName)) { | ||
@@ -209,2 +186,3 @@ this.throwNewError( | ||
out, | ||
this.cmdParam, | ||
this.resolve, | ||
@@ -219,14 +197,3 @@ this.reject, | ||
if (!this.plugin) { | ||
this.reject( | ||
Errors.createFatalError( | ||
"Client does not support authentication protocol '" + pluginName + "' requested by server. ", | ||
Errors.ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED, | ||
info, | ||
'08004' | ||
) | ||
); | ||
} else { | ||
this._addCommand(this.plugin); | ||
} | ||
this._addCommand(this.plugin); | ||
} | ||
@@ -242,2 +209,3 @@ | ||
out, | ||
cmdParam, | ||
authResolve, | ||
@@ -266,10 +234,2 @@ authReject, | ||
case 'sha256_password': | ||
if (!Handshake.ensureNodeVersion(11, 6, 0)) { | ||
throw Errors.createFatalError( | ||
'sha256_password authentication plugin require node 11.6+', | ||
Errors.ER_MINIMUM_NODE_VERSION_REQUIRED, | ||
info, | ||
'08004' | ||
); | ||
} | ||
pluginAuth = require('./auth/sha256-password-auth.js'); | ||
@@ -279,10 +239,2 @@ break; | ||
case 'caching_sha2_password': | ||
if (!Handshake.ensureNodeVersion(11, 6, 0)) { | ||
throw Errors.createFatalError( | ||
'caching_sha2_password authentication plugin require node 11.6+', | ||
Errors.ER_MINIMUM_NODE_VERSION_REQUIRED, | ||
info, | ||
'08004' | ||
); | ||
} | ||
pluginAuth = require('./auth/caching-sha2-password-auth.js'); | ||
@@ -294,17 +246,13 @@ break; | ||
default: | ||
return null; | ||
throw Errors.createFatalError( | ||
`Client does not support authentication protocol '${pluginName}' requested by server.`, | ||
Errors.ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED, | ||
info, | ||
'08004' | ||
); | ||
} | ||
return new pluginAuth(packSeq, compressPackSeq, pluginData, authResolve, authReject, multiAuthResolver); | ||
return new pluginAuth(packSeq, compressPackSeq, pluginData, cmdParam, authResolve, authReject, multiAuthResolver); | ||
} | ||
static ensureNodeVersion(major, minor, patch) { | ||
const ver = process.versions.node.split('.'); | ||
return ( | ||
ver[0] > major || | ||
(ver[0] === major && ver[1] > minor) || | ||
(ver[0] === major && ver[1] === minor && ver[2] >= patch) | ||
); | ||
} | ||
} | ||
module.exports = Handshake; |
@@ -17,9 +17,9 @@ 'use strict'; | ||
class Parser extends Command { | ||
constructor(resolve, reject, cmdOpts, connOpts, sql, values) { | ||
super(resolve, reject); | ||
constructor(resolve, reject, connOpts, cmdParam) { | ||
super(cmdParam, resolve, reject); | ||
this._responseIndex = 0; | ||
this._rows = []; | ||
this.opts = cmdOpts ? Object.assign({}, connOpts, cmdOpts) : connOpts; | ||
this.sql = sql; | ||
this.initialValues = values; | ||
this.opts = cmdParam.opts ? Object.assign({}, connOpts, cmdParam.opts) : connOpts; | ||
this.sql = cmdParam.sql; | ||
this.initialValues = cmdParam.values; | ||
this.canSkipMeta = false; | ||
@@ -109,10 +109,16 @@ } | ||
readOKPacket(packet, out, opts, info) { | ||
const okPacket = this.parseOkPacket(packet, out, this.opts, info); | ||
this._rows.push(okPacket); | ||
try { | ||
const okPacket = this.parseOkPacket(packet, out, this.opts, opts, info); | ||
this._rows.push(okPacket); | ||
if (info.status & ServerStatus.MORE_RESULTS_EXISTS) { | ||
this._responseIndex++; | ||
return (this.onPacketReceive = this.readResponsePacket); | ||
if (info.status & ServerStatus.MORE_RESULTS_EXISTS) { | ||
this._responseIndex++; | ||
return (this.onPacketReceive = this.readResponsePacket); | ||
} | ||
this.success(this._responseIndex === 0 ? this._rows[0] : this._rows); | ||
} catch (e) { | ||
this.onPacketReceive = info.status & ServerStatus.MORE_RESULTS_EXISTS ? this.readResponsePacket : null; | ||
this.throwUnexpectedError(e.message, false, info, '42000', Errors.ER_PARSING_PRECISION); | ||
return null; | ||
} | ||
this.success(this._responseIndex === 0 ? this._rows[0] : this._rows); | ||
} | ||
@@ -169,3 +175,3 @@ | ||
this.columnNo--; | ||
this._columnsPrepare.push(new ColumnDefinition(packet.clone(), info)); | ||
this._columnsPrepare.push(new ColumnDefinition(packet, info, opts.rowsAsArray)); | ||
if (this.columnNo === 0) { | ||
@@ -186,3 +192,3 @@ if (info.eofDeprecated) { | ||
this.parameterNo--; | ||
this._parameterPrepare.push(new ColumnDefinition(packet.clone(), info)); | ||
this._parameterPrepare.push(new ColumnDefinition(packet, info)); | ||
if (this.parameterNo === 0) { | ||
@@ -214,3 +220,3 @@ if (info.eofDeprecated) { | ||
readColumn(packet, out, opts, info) { | ||
this._columns.push(new ColumnDefinition(packet.clone(), info)); | ||
this._columns.push(new ColumnDefinition(packet, info, this.opts.rowsAsArray)); | ||
@@ -235,7 +241,8 @@ // last column | ||
for (let i = 0; i < this._columnCount; i++) { | ||
this._parseFonction[i] = this.readCastValue.bind(this, this._columns[i]); | ||
this._parseFonction[i] = this.readCastValue.bind(this); | ||
} | ||
} else { | ||
const dataParser = this.binary ? BinaryDecoder.parser : TextDecoder.parser; | ||
for (let i = 0; i < this._columnCount; i++) { | ||
this._parseFonction[i] = this._columns[i].__parser(this.binary, this.opts); | ||
this._parseFonction[i] = dataParser(this._columns[i], this.opts); | ||
} | ||
@@ -471,3 +478,3 @@ } | ||
for (let i = 0; i < this._columnCount; i++) { | ||
row[i] = this._parseFonction[i].call(null, packet, i, nullBitMap, this.opts); | ||
row[i] = this._parseFonction[i].call(null, columns[i], packet, i, nullBitMap, this.opts, this.unexpectedError); | ||
} | ||
@@ -484,6 +491,8 @@ return row; | ||
null, | ||
columns[i], | ||
packet, | ||
i, | ||
nullBitMap, | ||
this.opts | ||
this.opts, | ||
this.unexpectedError | ||
); | ||
@@ -497,3 +506,3 @@ } | ||
for (let i = 0; i < this._columnCount; i++) { | ||
row[this.tableHeader[i]] = this._parseFonction[i].call(null, packet, i, null, this.opts); | ||
row[this.tableHeader[i]] = this._parseFonction[i](columns[i], packet, i, null, this.opts, this.unexpectedError); | ||
} | ||
@@ -507,3 +516,11 @@ return row; | ||
for (let i = 0; i < this._columnCount; i++) { | ||
row[this.tableHeader[i]] = this._parseFonction[i].call(null, packet, i, nullBitMap, this.opts); | ||
row[this.tableHeader[i]] = this._parseFonction[i].call( | ||
null, | ||
columns[i], | ||
packet, | ||
i, | ||
nullBitMap, | ||
this.opts, | ||
this.unexpectedError | ||
); | ||
} | ||
@@ -519,3 +536,7 @@ return row; | ||
} | ||
return opts.typeCast(column, column.__parser(this.binary, opts).bind(column, packet, index, nullBitmap, opts)); | ||
const dataParser = this.binary ? BinaryDecoder.parser : TextDecoder.parser; | ||
return opts.typeCast( | ||
column, | ||
dataParser(column, opts).bind(null, column, packet, index, nullBitmap, opts, this.unexpectedError) | ||
); | ||
} | ||
@@ -522,0 +543,0 @@ |
@@ -11,4 +11,4 @@ 'use strict'; | ||
class Ping extends Command { | ||
constructor(resolve, reject) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject) { | ||
super(cmdParam, resolve, reject); | ||
} | ||
@@ -37,5 +37,2 @@ | ||
readPingResponsePacket(packet, out, opts, info) { | ||
if (packet.peek() !== 0x00) { | ||
return this.throwNewError('unexpected packet', false, info, '42000', Errors.ER_PING_BAD_PACKET); | ||
} | ||
packet.skip(1); //skip header | ||
@@ -42,0 +39,0 @@ packet.skipLengthCodedNumber(); //affected rows |
@@ -13,4 +13,4 @@ 'use strict'; | ||
class Prepare extends Parser { | ||
constructor(resolve, reject, opts, connOpts, sql, executePromise, emitter) { | ||
super(resolve, reject, opts, connOpts, sql, null); | ||
constructor(resolve, reject, connOpts, cmdParam, executePromise, emitter) { | ||
super(resolve, reject, connOpts, cmdParam); | ||
this.encoder = new BinaryEncoder(this.opts); | ||
@@ -68,3 +68,4 @@ this.binary = true; | ||
this.executePromise, | ||
this.emitter | ||
this.emitter, | ||
opts | ||
); | ||
@@ -81,3 +82,4 @@ info._prepareCache.set(key, prepare); | ||
this.executePromise, | ||
this.emitter | ||
this.emitter, | ||
opts | ||
); | ||
@@ -84,0 +86,0 @@ } |
@@ -15,4 +15,4 @@ 'use strict'; | ||
class Query extends Parser { | ||
constructor(resolve, reject, options, connOpts, sql, values) { | ||
super(resolve, reject, options, connOpts, sql, values); | ||
constructor(resolve, reject, connOpts, cmdParam) { | ||
super(resolve, reject, connOpts, cmdParam); | ||
this.encoder = new TextEncoder(this.opts); | ||
@@ -19,0 +19,0 @@ this.binary = false; |
@@ -10,4 +10,4 @@ 'use strict'; | ||
class Quit extends Command { | ||
constructor(resolve, reject) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject) { | ||
super(cmdParam, resolve, reject); | ||
} | ||
@@ -14,0 +14,0 @@ |
@@ -11,4 +11,4 @@ 'use strict'; | ||
class Reset extends Command { | ||
constructor(resolve, reject) { | ||
super(resolve, reject); | ||
constructor(cmdParam, resolve, reject) { | ||
super(cmdParam, resolve, reject); | ||
} | ||
@@ -15,0 +15,0 @@ |
@@ -11,10 +11,8 @@ 'use strict'; | ||
class Stream extends Query { | ||
constructor(cmdOpts, connOpts, sql, values, socket) { | ||
constructor(cmdParam, connOpts, socket) { | ||
super( | ||
() => {}, | ||
() => {}, | ||
cmdOpts, | ||
connOpts, | ||
sql, | ||
values | ||
cmdParam | ||
); | ||
@@ -54,8 +52,4 @@ this.socket = socket; | ||
handleNewRows(row) { | ||
try { | ||
if (!this.inStream.push(row)) { | ||
this.socket.pause(); | ||
} | ||
} catch (err) { | ||
this.socket.resume(); | ||
if (!this.inStream.push(row)) { | ||
this.socket.pause(); | ||
} | ||
@@ -62,0 +56,0 @@ } |
@@ -51,3 +51,3 @@ 'use strict'; | ||
} else { | ||
this.collation = Collations.fromIndex(opts.charsetNumber); | ||
this.collation = opts.charsetNumber ? Collations.fromIndex(opts.charsetNumber) : undefined; | ||
} | ||
@@ -132,2 +132,3 @@ | ||
this.bulk = opts.bulk === undefined || opts.bulk; | ||
this.checkNumberRange = opts.checkNumberRange || false; | ||
@@ -134,0 +135,0 @@ // coherence check |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const Query = require('./cmd/query'); | ||
const CommandParameter = require('./command-parameter'); | ||
@@ -25,8 +26,15 @@ class ConnectionCallback { | ||
get status() { | ||
return this.#conn.status; | ||
} | ||
#noop = () => {}; | ||
release = (cb) => { | ||
this.#conn.release( | ||
() => { | ||
if (cb) cb(); | ||
}, | ||
(err) => { | ||
if (cb) cb(err); | ||
} | ||
); | ||
}; | ||
/** | ||
@@ -49,8 +57,10 @@ * Permit to change user during connection. | ||
} | ||
const cmdParam = new CommandParameter(null, null, _options, _cb); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
new Promise(this.#conn.changeUser.bind(this.#conn, _options)) | ||
new Promise(this.#conn.changeUser.bind(this.#conn, cmdParam)) | ||
.then(() => { | ||
if (_cb) _cb(null, null, null); | ||
if (cmdParam.callback) cmdParam.callback(null, null, null); | ||
}) | ||
.catch(_cb || this.#noop); | ||
.catch(cmdParam.callback || this.#noop); | ||
} | ||
@@ -64,3 +74,3 @@ | ||
beginTransaction(callback) { | ||
this.query('START TRANSACTION', null, callback); | ||
this.query(new CommandParameter('START TRANSACTION'), null, callback); | ||
} | ||
@@ -75,3 +85,3 @@ | ||
this.#conn.changeTransaction( | ||
'COMMIT', | ||
new CommandParameter('COMMIT'), | ||
() => { | ||
@@ -91,3 +101,3 @@ if (callback) callback(null, null, null); | ||
this.#conn.changeTransaction( | ||
'ROLLBACK', | ||
new CommandParameter('ROLLBACK'), | ||
() => { | ||
@@ -111,37 +121,18 @@ if (callback) callback(null, null, null); | ||
query(sql, values, callback) { | ||
return ConnectionCallback._QUERY_CMD(this.#conn, sql, values, callback); | ||
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback); | ||
return ConnectionCallback._QUERY_CMD(this.#conn, cmdParam); | ||
} | ||
static _QUERY_CMD(conn, sql, values, callback) { | ||
let _cmdOpts, | ||
_sql, | ||
_values = values, | ||
_cb = callback; | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
if (typeof sql === 'object') { | ||
_cmdOpts = sql; | ||
_sql = _cmdOpts.sql; | ||
if (sql.values) _values = sql.values; | ||
} else { | ||
_sql = sql; | ||
} | ||
static _QUERY_CMD(conn, cmdParam) { | ||
const cmd = new Query( | ||
_cb | ||
cmdParam.callback | ||
? (rows) => { | ||
const meta = rows.meta; | ||
delete rows.meta; | ||
_cb(null, rows, meta); | ||
cmdParam.callback(null, rows, meta); | ||
} | ||
: () => {}, | ||
_cb ? _cb : () => {}, | ||
_cmdOpts, | ||
cmdParam.callback ? cmdParam.callback : () => {}, | ||
conn.opts, | ||
_sql, | ||
_values | ||
cmdParam | ||
); | ||
@@ -154,3 +145,2 @@ | ||
if (conn.opts.trace) Error.captureStackTrace(cmd); | ||
conn.addCommand(cmd); | ||
@@ -161,6 +151,7 @@ return cmd; | ||
execute(sql, values, callback) { | ||
return ConnectionCallback._EXECUTE_CMD(this.#conn, sql, values, callback); | ||
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback); | ||
return ConnectionCallback._EXECUTE_CMD(this.#conn, cmdParam); | ||
} | ||
static _EXECUTE_CMD(conn, sql, values, callback) { | ||
static _PARAM(options, sql, values, callback) { | ||
let _cmdOpt, | ||
@@ -170,2 +161,6 @@ _sql, | ||
_cb = callback; | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
if (typeof sql === 'object') { | ||
@@ -178,15 +173,16 @@ _cmdOpt = sql; | ||
} | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
const cmdParam = new CommandParameter(_sql, _values, _cmdOpt, _cb); | ||
if (options.trace) Error.captureStackTrace(cmdParam); | ||
return cmdParam; | ||
} | ||
new Promise(conn.prepare.bind(conn, _cmdOpt, _sql, conn.executePromise.bind(conn))) | ||
static _EXECUTE_CMD(conn, cmdParam) { | ||
new Promise(conn.prepare.bind(conn, cmdParam, conn.executePromise.bind(conn))) | ||
.then((prepare) => { | ||
return prepare.execute(_values, _cmdOpt).then((res) => { | ||
return prepare.execute(cmdParam.values, cmdParam.opts, null, cmdParam.stack).then((res) => { | ||
prepare.close(); | ||
if (_cb) { | ||
if (cmdParam.callback) { | ||
const meta = res.meta; | ||
delete res.meta; | ||
_cb(null, res, meta); | ||
cmdParam.callback(null, res, meta); | ||
} | ||
@@ -197,3 +193,3 @@ }); | ||
if (conn.opts.logger.error) conn.opts.logger.error(err); | ||
if (_cb) _cb(err); | ||
if (cmdParam.callback) cmdParam.callback(err); | ||
}); | ||
@@ -210,3 +206,5 @@ } | ||
} | ||
return new Promise(this.#conn.prepare.bind(this.#conn, _cmdOpt, _sql, this.#conn.executePromise.bind(this.#conn))) | ||
const cmdParam = new CommandParameter(_sql, null, _cmdOpt, callback); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.prepare.bind(this.#conn, cmdParam, this.#conn.executePromise.bind(this.#conn))) | ||
.then((prepare) => { | ||
@@ -228,30 +226,15 @@ if (callback) callback(null, prepare, null); | ||
batch(sql, values, callback) { | ||
return ConnectionCallback._BATCH_CMD(this.#conn, sql, values, callback); | ||
const cmdParam = ConnectionCallback._PARAM(this.#conn.opts, sql, values, callback); | ||
return ConnectionCallback._BATCH_CMD(this.#conn, cmdParam); | ||
} | ||
static _BATCH_CMD(conn, sql, values, callback) { | ||
let _options, | ||
_sql, | ||
_values = values, | ||
_cb = callback; | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
if (typeof sql === 'object') { | ||
_options = sql; | ||
_sql = _options.sql; | ||
if (_options.values) _values = _options.values; | ||
} else { | ||
_sql = sql; | ||
} | ||
static _BATCH_CMD(conn, cmdParam) { | ||
conn | ||
.batch(_sql, _options, _values) | ||
.batch(cmdParam) | ||
.then((res) => { | ||
if (_cb) _cb(null, res); | ||
if (cmdParam.callback) cmdParam.callback(null, res); | ||
}) | ||
.catch((err) => { | ||
if (conn.opts.logger.error) conn.opts.logger.error(err); | ||
if (_cb) _cb(err); | ||
if (cmdParam.callback) cmdParam.callback(err); | ||
}); | ||
@@ -266,11 +249,13 @@ } | ||
ping(timeout, callback) { | ||
let _timeout, _cb; | ||
let _cmdOpt = {}, | ||
_cb; | ||
if (typeof timeout === 'function') { | ||
_cb = timeout; | ||
_timeout = undefined; | ||
} else { | ||
_timeout = timeout; | ||
_cmdOpt.timeout = timeout; | ||
_cb = callback; | ||
} | ||
new Promise(this.#conn.ping.bind(this.#conn, _timeout)).then(_cb || this.#noop).catch(_cb || this.#noop); | ||
const cmdParam = new CommandParameter(null, null, _cmdOpt, _cb); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
new Promise(this.#conn.ping.bind(this.#conn, cmdParam)).then(_cb || this.#noop).catch(_cb || this.#noop); | ||
} | ||
@@ -290,3 +275,7 @@ | ||
reset(callback) { | ||
return new Promise(this.#conn.reset.bind(this.#conn)).then(callback || this.#noop).catch(callback || this.#noop); | ||
const cmdParam = new CommandParameter(); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.reset.bind(this.#conn, cmdParam)) | ||
.then(callback || this.#noop) | ||
.catch(callback || this.#noop); | ||
} | ||
@@ -308,3 +297,5 @@ | ||
end(callback) { | ||
new Promise(this.#conn.end.bind(this.#conn)) | ||
const cmdParam = new CommandParameter(); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
new Promise(this.#conn.end.bind(this.#conn, cmdParam)) | ||
.then(() => { | ||
@@ -311,0 +302,0 @@ if (callback) callback(); |
'use strict'; | ||
const Stream = require('./cmd/stream'); | ||
const CommandParameter = require('./command-parameter'); | ||
@@ -21,3 +22,2 @@ /** | ||
this.#conn = conn; | ||
this.query = ConnectionPromise._QUERY_CMD.bind(this, conn); | ||
this.on = this.#conn.on.bind(this.#conn); | ||
@@ -35,6 +35,2 @@ this.once = this.#conn.once.bind(this.#conn); | ||
get status() { | ||
return this.#conn.status; | ||
} | ||
/** | ||
@@ -49,3 +45,5 @@ * Permit to change user during connection. | ||
changeUser(options) { | ||
return new Promise(this.#conn.changeUser.bind(this.#conn, options)); | ||
const cmdParam = new CommandParameter(null, null, options); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.changeUser.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -68,3 +66,4 @@ | ||
commit() { | ||
return new Promise(this.#conn.changeTransaction.bind(this.#conn, 'COMMIT')); | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, 'COMMIT'); | ||
return new Promise(this.#conn.changeTransaction.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -78,3 +77,4 @@ | ||
rollback() { | ||
return new Promise(this.#conn.changeTransaction.bind(this.#conn, 'ROLLBACK')); | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, 'ROLLBACK'); | ||
return new Promise(this.#conn.changeTransaction.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -90,4 +90,12 @@ | ||
*/ | ||
query(sql, values) { | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, sql, values); | ||
return new Promise(this.#conn.query.bind(this.#conn, cmdParam)); | ||
} | ||
static _QUERY_CMD(conn, sql, values) { | ||
static _QUERY_CMD(conn, cmdParam) { | ||
return new Promise(conn.query.bind(conn, cmdParam)); | ||
} | ||
static _PARAM(options, sql, values) { | ||
let _cmdOpt, | ||
@@ -101,25 +109,16 @@ _sql = sql, | ||
} | ||
return new Promise(conn.query.bind(conn, _cmdOpt, _sql, _values)); | ||
const cmdParam = new CommandParameter(_sql, _values, _cmdOpt); | ||
if (options.trace) Error.captureStackTrace(cmdParam); | ||
return cmdParam; | ||
} | ||
execute(sql, values) { | ||
return ConnectionPromise._EXECUTE_CMD(this.#conn, sql, values); | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, sql, values); | ||
return ConnectionPromise._EXECUTE_CMD(this.#conn, cmdParam); | ||
} | ||
static _EXECUTE_CMD(conn, sql, values) { | ||
let _cmdOpt, | ||
_sql, | ||
_values = values; | ||
if (typeof sql === 'object') { | ||
_cmdOpt = sql; | ||
_sql = _cmdOpt.sql; | ||
if (_cmdOpt.values) _values = _cmdOpt.values; | ||
} else { | ||
_sql = sql; | ||
} | ||
return new Promise(conn.prepare.bind(conn, _cmdOpt, _sql, conn.executePromise.bind(conn))) | ||
static _EXECUTE_CMD(conn, cmdParam) { | ||
return new Promise(conn.prepare.bind(conn, cmdParam, conn.executePromise.bind(conn))) | ||
.then((prepare) => { | ||
return prepare.execute(_values, _cmdOpt).then((res) => { | ||
return prepare.execute(cmdParam.values, cmdParam.opts, null, cmdParam.stack).then((res) => { | ||
prepare.close(); | ||
@@ -143,3 +142,5 @@ return Promise.resolve(res); | ||
} | ||
return new Promise(this.#conn.prepare.bind(this.#conn, _cmdOpt, _sql, this.#conn.executePromise.bind(this.#conn))); | ||
const cmdParam = new CommandParameter(_sql, null, _cmdOpt); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.prepare.bind(this.#conn, cmdParam, this.#conn.executePromise.bind(this.#conn))); | ||
} | ||
@@ -156,18 +157,8 @@ | ||
batch(sql, values) { | ||
return ConnectionPromise._BATCH_CMD(this.#conn, sql, values); | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, sql, values); | ||
return this.#conn.batch(cmdParam); | ||
} | ||
static _BATCH_CMD(conn, sql, values) { | ||
let _options, | ||
_sql, | ||
_values = values; | ||
if (typeof sql === 'object') { | ||
_options = sql; | ||
_sql = _options.sql; | ||
if (_options.values) _values = _options.values; | ||
} else { | ||
_sql = sql; | ||
} | ||
return conn.batch(_sql, _options, _values); | ||
static _BATCH_CMD(conn, cmdParam) { | ||
return conn.batch(cmdParam); | ||
} | ||
@@ -185,16 +176,5 @@ | ||
queryStream(sql, values) { | ||
let _cmdOpt, | ||
_sql, | ||
_values = values; | ||
if (typeof sql === 'object') { | ||
_cmdOpt = sql; | ||
_sql = _cmdOpt.sql; | ||
if (sql.values) _values = sql.values; | ||
} else { | ||
_sql = sql; | ||
} | ||
const cmd = new Stream(_cmdOpt, this.#conn.opts, _sql, _values, this.#conn.socket); | ||
const cmdParam = ConnectionPromise._PARAM(this.#conn.opts, sql, values); | ||
const cmd = new Stream(cmdParam, this.#conn.opts, this.#conn.socket); | ||
if (this.#conn.opts.logger.error) cmd.on('error', this.#conn.opts.logger.error); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmd); | ||
this.#conn.addCommand(cmd); | ||
@@ -210,3 +190,5 @@ return cmd.inStream; | ||
ping(timeout) { | ||
return new Promise(this.#conn.ping.bind(this.#conn, timeout)); | ||
const cmdParam = new CommandParameter(null, null, { timeout: timeout }); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.ping.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -226,3 +208,5 @@ | ||
reset() { | ||
return new Promise(this.#conn.reset.bind(this.#conn)); | ||
const cmdParam = new CommandParameter(); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.reset.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -244,3 +228,5 @@ | ||
end() { | ||
return new Promise(this.#conn.end.bind(this.#conn)); | ||
const cmdParam = new CommandParameter(); | ||
if (this.#conn.opts.trace) Error.captureStackTrace(cmdParam); | ||
return new Promise(this.#conn.end.bind(this.#conn, cmdParam)); | ||
} | ||
@@ -247,0 +233,0 @@ |
@@ -32,2 +32,3 @@ 'use strict'; | ||
const { Status } = require('./const/connection_status'); | ||
const CommandParameter = require('./command-parameter'); | ||
@@ -51,3 +52,2 @@ /** | ||
status = Status.NOT_CONNECTED; | ||
socketConnected = false; | ||
socket = null; | ||
@@ -83,2 +83,3 @@ timeout = null; | ||
new ClosePrepare( | ||
new CommandParameter(null, null, null, null), | ||
() => {}, | ||
@@ -106,30 +107,34 @@ () => {}, | ||
const conn = this; | ||
switch (this.status) { | ||
case Status.NOT_CONNECTED: | ||
this.status = Status.CONNECTING; | ||
return new Promise(function (resolve, reject) { | ||
conn.registerHandshakeCmd(resolve, reject); | ||
}); | ||
this.status = Status.CONNECTING; | ||
const handshakeParam = new CommandParameter(null, null, this.opts, null); | ||
return new Promise(function (resolve, reject) { | ||
conn.connectRejectFct = reject; | ||
conn.connectResolveFct = resolve; | ||
// add a handshake to msg queue | ||
const handshake = new Handshake( | ||
handshakeParam, | ||
conn.authSucceedHandler.bind(conn), | ||
conn.authFailHandler.bind(conn), | ||
conn.createSecureContext.bind(conn), | ||
conn.addCommandEnable.bind(conn), | ||
conn.getSocket.bind(conn) | ||
); | ||
Error.captureStackTrace(handshake); | ||
case Status.CLOSING: | ||
case Status.CLOSED: | ||
const err = Errors.createFatalError('Connection closed', Errors.ER_CONNECTION_ALREADY_CLOSED, this.info); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
return Promise.reject(err); | ||
handshake.once('end', () => { | ||
// conn.info.collation might not be initialized | ||
// in case of handshake throwing error | ||
if (!conn.opts.collation && conn.info.collation) { | ||
conn.opts.emit('collation', conn.info.collation); | ||
} | ||
case Status.CONNECTING: | ||
case Status.AUTHENTICATING: | ||
const errAuth = Errors.createFatalError( | ||
'Connection is already connecting', | ||
Errors.ER_ALREADY_CONNECTING, | ||
this.info | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(errAuth); | ||
return Promise.reject(errAuth); | ||
} | ||
//status Connected | ||
return Promise.resolve(this); | ||
process.nextTick(conn.nextSendCmd.bind(conn)); | ||
}); | ||
conn.receiveQueue.push(handshake); | ||
conn.streamInitSocket.call(conn); | ||
}); | ||
} | ||
executePromise(values, cmdOpts, prepare, resolve, reject) { | ||
executePromise(cmdParam, prepare, resolve, reject) { | ||
const cmd = new Execute( | ||
@@ -141,18 +146,24 @@ resolve, | ||
}.bind(this), | ||
cmdOpts, | ||
this.opts, | ||
prepare, | ||
values | ||
cmdParam, | ||
prepare | ||
); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
} | ||
batch(_sql, _options, _values) { | ||
if (!_sql) { | ||
const err = Errors.createError('sql parameter is mandatory', Errors.ER_UNDEFINED_SQL, this.info, 'HY000'); | ||
batch(cmdParam) { | ||
if (!cmdParam.sql) { | ||
const err = Errors.createError( | ||
'sql parameter is mandatory', | ||
Errors.ER_UNDEFINED_SQL, | ||
this.info, | ||
'HY000', | ||
null, | ||
false, | ||
cmdParam.stack | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
return Promise.reject(err); | ||
} | ||
if (!_values) { | ||
if (!cmdParam.values) { | ||
const err = Errors.createError( | ||
@@ -163,3 +174,5 @@ 'Batch must have values set', | ||
'HY000', | ||
_sql | ||
cmdParam.sql, | ||
false, | ||
cmdParam.stack | ||
); | ||
@@ -170,29 +183,29 @@ if (this.opts.logger.error) this.opts.logger.error(err); | ||
return new Promise(this.prepare.bind(this, _options, _sql, this.executePromise.bind(this))).then((prepare) => { | ||
const usePlaceHolder = (_options && _options.namedPlaceholders) || this.opts.namedPlaceholders; | ||
return new Promise(this.prepare.bind(this, cmdParam, this.executePromise.bind(this))).then((prepare) => { | ||
const usePlaceHolder = (cmdParam.opts && cmdParam.opts.namedPlaceholders) || this.opts.namedPlaceholders; | ||
let vals; | ||
if (Array.isArray(_values)) { | ||
if (Array.isArray(cmdParam.values)) { | ||
if (usePlaceHolder) { | ||
vals = _values; | ||
} else if (Array.isArray(_values[0])) { | ||
vals = _values; | ||
vals = cmdParam.values; | ||
} else if (Array.isArray(cmdParam.values[0])) { | ||
vals = cmdParam.values; | ||
} else if (prepare.parameters.length === 1) { | ||
vals = []; | ||
for (let i = 0; i < _values.length; i++) { | ||
vals.push([_values[i]]); | ||
for (let i = 0; i < cmdParam.values.length; i++) { | ||
vals.push([cmdParam.values[i]]); | ||
} | ||
} else { | ||
vals = [_values]; | ||
vals = [cmdParam.values]; | ||
} | ||
} else { | ||
vals = [[_values]]; | ||
vals = [[cmdParam.values]]; | ||
} | ||
let useBulk = this._canUseBulk(vals, _options); | ||
cmdParam.values = vals; | ||
let useBulk = this._canUseBulk(vals, cmdParam.opts); | ||
if (useBulk) { | ||
return new Promise(this.executeBulkPromise.bind(this, vals, _options, prepare)); | ||
return new Promise(this.executeBulkPromise.bind(this, cmdParam, prepare)); | ||
} else { | ||
const executes = []; | ||
for (let i = 0; i < vals.length; i++) { | ||
executes.push(prepare.execute(vals[i], _options)); | ||
executes.push(prepare.execute(vals[i], cmdParam.opts, null, cmdParam.stack)); | ||
} | ||
@@ -202,3 +215,3 @@ return Promise.all(executes).then( | ||
prepare.close(); | ||
if (_options && _options.fullResult) { | ||
if (cmdParam.opts && cmdParam.opts.fullResult) { | ||
return Promise.resolve(res); | ||
@@ -227,3 +240,2 @@ } else { | ||
} | ||
return; | ||
} | ||
@@ -236,3 +248,3 @@ }.bind(this) | ||
executeBulkPromise(values, cmdOpts, prepare, resolve, reject) { | ||
executeBulkPromise(cmdParam, prepare, resolve, reject) { | ||
const cmd = new BatchBulk( | ||
@@ -247,8 +259,6 @@ (res) => { | ||
}.bind(this), | ||
cmdOpts, | ||
this.opts, | ||
prepare, | ||
values | ||
cmdParam | ||
); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
@@ -259,7 +269,7 @@ } | ||
* Send an empty MySQL packet to ensure connection is active, and reset @@wait_timeout | ||
* @param timeout (optional) timeout value in ms. If reached, throw error and close connection | ||
* @param cmdParam command context | ||
*/ | ||
ping(timeout, resolve, reject) { | ||
if (timeout) { | ||
if (timeout < 0) { | ||
ping(cmdParam, resolve, reject) { | ||
if (cmdParam.opts && cmdParam.opts.timeout) { | ||
if (cmdParam.opts.timeout < 0) { | ||
const err = Errors.createError( | ||
@@ -291,6 +301,7 @@ 'Ping cannot have negative timeout value', | ||
}.bind(this), | ||
timeout | ||
cmdParam.opts.timeout | ||
); | ||
this.addCommand( | ||
new Ping( | ||
cmdParam, | ||
() => { | ||
@@ -311,3 +322,3 @@ if (tOut) { | ||
} | ||
this.addCommand(new Ping(resolve, reject)); | ||
this.addCommand(new Ping(cmdParam, resolve, reject)); | ||
} | ||
@@ -323,6 +334,4 @@ | ||
* - remove all PREPARE statement | ||
* | ||
* @returns {Promise} promise | ||
*/ | ||
reset(resolve, reject) { | ||
reset(cmdParam, resolve, reject) { | ||
if ( | ||
@@ -332,3 +341,3 @@ (this.info.isMariaDB() && this.info.hasMinVersion(10, 2, 4)) || | ||
) { | ||
const resetCmd = new Reset(resolve, reject); | ||
const resetCmd = new Reset(cmdParam, resolve, reject); | ||
this.addCommand(resetCmd); | ||
@@ -341,2 +350,3 @@ return; | ||
); | ||
err.stack = cmdParam.stack; | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
@@ -357,3 +367,3 @@ reject(err); | ||
*/ | ||
end(resolve, reject) { | ||
end(cmdParam, resolve, reject) { | ||
this.addCommand = this.addCommandDisabled; | ||
@@ -372,3 +382,3 @@ clearTimeout(this.timeout); | ||
}; | ||
const quitCmd = new Quit(ended, ended); | ||
const quitCmd = new Quit(cmdParam, ended, ended); | ||
this.sendQueue.push(quitCmd); | ||
@@ -399,7 +409,7 @@ this.receiveQueue.push(quitCmd); | ||
.connect() | ||
.then(function () { | ||
.then(() => { | ||
//************************************************* | ||
//kill connection | ||
//************************************************* | ||
const killResHandler = function (err) { | ||
new Promise(killCon.query.bind(killCon, null, `KILL ${self.info.threadId}`, undefined)).finally((err) => { | ||
const destroyError = Errors.createFatalError( | ||
@@ -412,11 +422,14 @@ 'Connection destroyed, command was killed', | ||
self.socketErrorDispatchToQueries(destroyError); | ||
if (self.socket) process.nextTick(self.socket.destroy()); | ||
if (self.socket) { | ||
const sok = self.socket; | ||
process.nextTick(() => { | ||
sok.destroy(); | ||
}); | ||
} | ||
self.status = Status.CLOSED; | ||
self.clear(); | ||
new Promise(killCon.end.bind(killCon)).catch(() => {}); | ||
}; | ||
new Promise(killCon.query.bind(killCon, null, `KILL ${self.info.threadId}`, undefined)).finally( | ||
killResHandler | ||
); | ||
}); | ||
}) | ||
.catch((err) => { | ||
.catch(() => { | ||
//************************************************* | ||
@@ -429,3 +442,2 @@ //failing to create a kill connection, end normally | ||
self.status = Status.CLOSED; | ||
setImmediate(resolve); | ||
sock.destroy(); | ||
@@ -444,5 +456,5 @@ self.receiveQueue.clear(); | ||
this.socket.destroy(); | ||
this.clear(); | ||
} | ||
} | ||
this.clear(); | ||
} | ||
@@ -590,41 +602,2 @@ | ||
/** | ||
* Add handshake command to queue. | ||
* | ||
* @private | ||
*/ | ||
registerHandshakeCmd(resolve, rejected) { | ||
const _authFail = this.authFailHandler.bind( | ||
this, | ||
function (err) { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
rejected(err); | ||
}.bind(this) | ||
); | ||
const _authSucceed = this.authSucceedHandler.bind(this, resolve, _authFail); | ||
const handshake = new Handshake( | ||
_authSucceed, | ||
_authFail, | ||
this.createSecureContext.bind(this, _authFail), | ||
this.addCommandEnable.bind(this), | ||
this.getSocket.bind(this) | ||
); | ||
Error.captureStackTrace(handshake); | ||
handshake.once( | ||
'end', | ||
function () { | ||
if (!this.opts.collation) { | ||
this.opts.emit('collation', this.info.collation); | ||
} | ||
process.nextTick(this.nextSendCmd.bind(this)); | ||
}.bind(this) | ||
); | ||
this.receiveQueue.push(handshake); | ||
this.streamInitSocket(_authFail); | ||
} | ||
executeSessionVariableQuery() { | ||
@@ -636,26 +609,20 @@ if (this.opts.sessionVariables) { | ||
if (keys.length > 0) { | ||
return new Promise( | ||
function (resolve, reject) { | ||
for (let k = 0; k < keys.length; ++k) { | ||
sessionQuery += (k !== 0 ? ',' : '') + '@@' + keys[k].replace(/[^a-z0-9_]/gi, '') + '=?'; | ||
values.push(this.opts.sessionVariables[keys[k]]); | ||
} | ||
const errorHandling = (initialErr) => { | ||
const err = Errors.createFatalError( | ||
`Error setting session variable (value ${JSON.stringify(this.opts.sessionVariables)}). Error: ${ | ||
initialErr.message | ||
}`, | ||
Errors.ER_SETTING_SESSION_ERROR, | ||
this.info, | ||
'08S01', | ||
sessionQuery | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
reject(err); | ||
}; | ||
const cmd = new Query(resolve, errorHandling, null, this.opts, sessionQuery, values); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
}.bind(this) | ||
); | ||
for (let k = 0; k < keys.length; ++k) { | ||
sessionQuery += (k !== 0 ? ',' : '') + '@@' + keys[k].replace(/[^a-z0-9_]/gi, '') + '=?'; | ||
values.push(this.opts.sessionVariables[keys[k]]); | ||
} | ||
return new Promise(this.query.bind(this, new CommandParameter(sessionQuery, values))).catch((initialErr) => { | ||
const err = Errors.createFatalError( | ||
`Error setting session variable (value ${JSON.stringify(this.opts.sessionVariables)}). Error: ${ | ||
initialErr.message | ||
}`, | ||
Errors.ER_SETTING_SESSION_ERROR, | ||
this.info, | ||
'08S01', | ||
sessionQuery | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
return Promise.reject(err); | ||
}); | ||
} | ||
@@ -673,27 +640,27 @@ } | ||
if (this.opts.timezone === 'auto') { | ||
return new Promise(this.query.bind(this, null, 'SELECT @@system_time_zone stz, @@time_zone tz', undefined)).then( | ||
(res) => { | ||
const serverTimezone = res[0].tz === 'SYSTEM' ? res[0].stz : res[0].tz; | ||
const serverZone = moment.tz.zone(serverTimezone); | ||
if (serverZone) { | ||
const localTz = moment.tz.guess(); | ||
if (serverTimezone === localTz) { | ||
//db server and client use same timezone, avoid any conversion | ||
this.opts.tz = null; | ||
} else { | ||
this.opts._localTz = localTz; | ||
this.opts.tz = serverTimezone; | ||
} | ||
return new Promise( | ||
this.query.bind(this, new CommandParameter('SELECT @@system_time_zone stz, @@time_zone tz')) | ||
).then((res) => { | ||
const serverTimezone = res[0].tz === 'SYSTEM' ? res[0].stz : res[0].tz; | ||
const serverZone = moment.tz.zone(serverTimezone); | ||
if (serverZone) { | ||
const localTz = moment.tz.guess(); | ||
if (serverTimezone === localTz) { | ||
//db server and client use same timezone, avoid any conversion | ||
this.opts.tz = null; | ||
} else { | ||
const err = Errors.createFatalError( | ||
`Automatic timezone setting fails. Server timezone '${serverTimezone}' does't have a corresponding IANA timezone. Option timezone must be set according to server timezone`, | ||
Errors.ER_WRONG_AUTO_TIMEZONE, | ||
this.info | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
return Promise.reject(err); | ||
this.opts._localTz = localTz; | ||
this.opts.tz = serverTimezone; | ||
} | ||
return Promise.resolve(); | ||
} else { | ||
const err = Errors.createFatalError( | ||
`Automatic timezone setting fails. Server timezone '${serverTimezone}' doesn't have a corresponding IANA timezone. Option timezone must be set according to server timezone`, | ||
Errors.ER_WRONG_AUTO_TIMEZONE, | ||
this.info | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
return Promise.reject(err); | ||
} | ||
); | ||
return Promise.resolve(); | ||
}); | ||
} | ||
@@ -708,13 +675,9 @@ if (this.opts.tz && !this.opts.skipSetTimezone) { | ||
} | ||
return new Promise(this.query.bind(this, null, 'SET time_zone=?', [tz])) | ||
.then(() => { | ||
return Promise.resolve(); | ||
}) | ||
.catch((err) => { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
console.log( | ||
`warning: setting timezone '${this.opts.tz}' fails on server.\n look at https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ to load IANA timezone.\nSetting timezone can be disabled with option \`skipSetTimezone\`` | ||
); | ||
return Promise.resolve(); | ||
}); | ||
return new Promise(this.query.bind(this, new CommandParameter('SET time_zone=?', [tz]))).catch((err) => { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
console.log( | ||
`warning: setting timezone '${this.opts.tz}' fails on server.\n look at https://mariadb.com/kb/en/mysql_tzinfo_to_sql/ to load IANA timezone.\nSetting timezone can be disabled with option \`skipSetTimezone\`` | ||
); | ||
return Promise.resolve(); | ||
}); | ||
} | ||
@@ -728,3 +691,3 @@ return Promise.resolve(); | ||
} | ||
return new Promise(this.query.bind(this, null, 'SELECT @@VERSION AS v', undefined)).then( | ||
return new Promise(this.query.bind(this, new CommandParameter('SELECT @@VERSION AS v'))).then( | ||
function (res) { | ||
@@ -744,3 +707,3 @@ this.info.serverVersion.raw = res[0].v; | ||
initialArr.forEach((sql) => { | ||
initialPromises.push(new Promise(this.query.bind(this, null, sql, undefined))); | ||
initialPromises.push(new Promise(this.query.bind(this, new CommandParameter(sql)))); | ||
}); | ||
@@ -765,3 +728,3 @@ | ||
const query = `SET max_statement_time=${this.opts.queryTimeout / 1000}`; | ||
new Promise(this.query.bind(this, null, query, undefined)).catch( | ||
new Promise(this.query.bind(this, new CommandParameter(query))).catch( | ||
function (initialErr) { | ||
@@ -802,3 +765,3 @@ const err = Errors.createFatalError( | ||
*/ | ||
streamInitSocket(authFailHandler) { | ||
streamInitSocket() { | ||
if (this.opts.socketPath) { | ||
@@ -811,11 +774,7 @@ this.socket = Net.connect(this.opts.socketPath); | ||
if (err) { | ||
authFailHandler(err); | ||
this.authFailHandler(err); | ||
return; | ||
} else if (stream) { | ||
this.socket = stream; | ||
this.socketInit(authFailHandler); | ||
} else { | ||
this.socket = Net.connect(this.opts.port, this.opts.host); | ||
this.socketInit(authFailHandler); | ||
} | ||
this.socket = stream ? stream : Net.connect(this.opts.port, this.opts.host); | ||
this.socketInit(); | ||
}.bind(this) | ||
@@ -825,11 +784,12 @@ ); | ||
this.socket = tmpSocket; | ||
this.socketInit(authFailHandler); | ||
this.socketInit(); | ||
} | ||
} else { | ||
const err = Errors.createError( | ||
'stream option is not a function. stream must be a function with (error, callback) parameter', | ||
Errors.ER_BAD_PARAMETER_VALUE, | ||
this.info | ||
this.authFailHandler( | ||
Errors.createError( | ||
'stream option is not a function. stream must be a function with (error, callback) parameter', | ||
Errors.ER_BAD_PARAMETER_VALUE, | ||
this.info | ||
) | ||
); | ||
authFailHandler(err); | ||
} | ||
@@ -840,21 +800,13 @@ return; | ||
} | ||
this.socketInit(authFailHandler); | ||
this.socketInit(); | ||
} | ||
socketInit(authFailHandler) { | ||
socketInit() { | ||
if (this.opts.connectTimeout) { | ||
this.timeout = setTimeout( | ||
this.connectTimeoutReached.bind(this), | ||
this.opts.connectTimeout, | ||
authFailHandler, | ||
Date.now() | ||
); | ||
this.timeout = setTimeout(this.connectTimeoutReached.bind(this), this.opts.connectTimeout, Date.now()); | ||
} | ||
const socketError = this.socketErrorHandler.bind(this, authFailHandler); | ||
this.socket.on('data', this.streamIn.onData.bind(this.streamIn)); | ||
this.socket.on('error', socketError); | ||
this.socket.on('end', socketError); | ||
this.socket.on('error', this.socketErrorHandler.bind(this)); | ||
this.socket.on('end', this.socketErrorHandler.bind(this)); | ||
this.socket.on( | ||
@@ -866,4 +818,3 @@ 'connect', | ||
this.status = Status.AUTHENTICATING; | ||
this.socketConnected = true; | ||
this.socket.setTimeout(this.opts.socketTimeout, this.socketTimeoutReached.bind(this, authFailHandler)); | ||
this.socket.setTimeout(this.opts.socketTimeout, this.socketTimeoutReached.bind(this)); | ||
this.socket.setNoDelay(true); | ||
@@ -889,3 +840,3 @@ | ||
*/ | ||
authSucceedHandler(resolve, rejected) { | ||
authSucceedHandler() { | ||
//enable packet compression according to option | ||
@@ -898,9 +849,10 @@ if (this.opts.compress) { | ||
this.socket.on('data', this.streamIn.onData.bind(this.streamIn)); | ||
} else { | ||
const err = Errors.createError( | ||
"connection is configured to use packet compression, but the server doesn't have this capability", | ||
Errors.ER_COMPRESSION_NOT_SUPPORTED, | ||
this.info | ||
} else if (this.opts.logger.error) { | ||
this.opts.logger.error( | ||
Errors.createError( | ||
"connection is configured to use packet compression, but the server doesn't have this capability", | ||
Errors.ER_COMPRESSION_NOT_SUPPORTED, | ||
this.info | ||
) | ||
); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
} | ||
@@ -915,11 +867,3 @@ } | ||
}); | ||
const errorInitialQueries = (err) => { | ||
if (!err.fatal) | ||
this.end( | ||
() => {}, | ||
() => {} | ||
); | ||
process.nextTick(rejected, err); | ||
}; | ||
const conn = this; | ||
this.status = Status.INIT_CMD; | ||
@@ -931,9 +875,19 @@ this.executeSessionVariableQuery() | ||
.then(this.executeSessionTimeout.bind(this)) | ||
.then( | ||
function () { | ||
this.status = Status.CONNECTED; | ||
process.nextTick(resolve, this); | ||
}.bind(this) | ||
) | ||
.catch(errorInitialQueries); | ||
.then(() => { | ||
conn.status = Status.CONNECTED; | ||
process.nextTick(conn.connectResolveFct, conn); | ||
conn.connectRejectFct = null; | ||
conn.connectResolveFct = null; | ||
}) | ||
.catch((err) => { | ||
if (!err.fatal) { | ||
const res = () => { | ||
conn.authFailHandler.call(conn, err); | ||
}; | ||
conn.end(res, res); | ||
} else { | ||
conn.authFailHandler.call(conn, err); | ||
} | ||
}); | ||
} | ||
@@ -946,8 +900,12 @@ | ||
*/ | ||
authFailHandler(reject, err) { | ||
process.nextTick(reject, err); | ||
//remove handshake command | ||
this.receiveQueue.shift(); | ||
authFailHandler(err) { | ||
if (this.connectRejectFct) { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
//remove handshake command | ||
this.receiveQueue.shift(); | ||
this.fatalError(err, true); | ||
this.fatalError(err, true); | ||
process.nextTick(this.connectRejectFct, err); | ||
this.connectRejectFct = null; | ||
} | ||
} | ||
@@ -958,14 +916,6 @@ | ||
* | ||
* @param rejected rejected function when error | ||
* @param callback callback function when done | ||
* @private | ||
*/ | ||
createSecureContext(rejected, callback) { | ||
const socketError = this.socketErrorHandler.bind( | ||
this, | ||
function (err) { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
rejected(err); | ||
}.bind(this) | ||
); | ||
createSecureContext(callback) { | ||
const sslOption = Object.assign({}, this.opts.ssl, { | ||
@@ -980,4 +930,4 @@ servername: this.opts.host, | ||
secureSocket.on('data', this.streamIn.onData.bind(this.streamIn)); | ||
secureSocket.on('error', socketError); | ||
secureSocket.on('end', socketError); | ||
secureSocket.on('error', this.socketErrorHandler.bind(this)); | ||
secureSocket.on('end', this.socketErrorHandler.bind(this)); | ||
secureSocket.writeBuf = (buf) => secureSocket.write(buf); | ||
@@ -991,3 +941,3 @@ secureSocket.flush = () => {}; | ||
} catch (err) { | ||
socketError(err); | ||
this.socketErrorHandler(err); | ||
} | ||
@@ -1035,3 +985,3 @@ } | ||
*/ | ||
connectTimeoutReached(authFailHandler, initialConnectionTime) { | ||
connectTimeoutReached(initialConnectionTime) { | ||
this.timeout = null; | ||
@@ -1048,3 +998,3 @@ const handshake = this.receiveQueue.peekFront(); | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
authFailHandler(err); | ||
this.authFailHandler(err); | ||
} | ||
@@ -1153,7 +1103,6 @@ | ||
* | ||
* @param authFailHandler authentication handler | ||
* @param err socket error | ||
* @private | ||
*/ | ||
socketErrorHandler(authFailHandler, err) { | ||
socketErrorHandler(err) { | ||
if (this.status >= Status.CLOSING) return; | ||
@@ -1174,3 +1123,3 @@ if (this.socket) { | ||
err.fatal = true; | ||
this.sqlState = 'HY000'; | ||
err.sqlState = 'HY000'; | ||
} | ||
@@ -1185,3 +1134,3 @@ | ||
} | ||
authFailHandler(err); | ||
this.authFailHandler(err); | ||
break; | ||
@@ -1248,3 +1197,3 @@ | ||
errorThrownByCmd = true; | ||
setImmediate(receiveCmd.throwError.bind(receiveCmd), err, this.info); | ||
setImmediate(receiveCmd.throwError.bind(receiveCmd, err, this.info)); | ||
} | ||
@@ -1282,3 +1231,3 @@ } | ||
*/ | ||
changeTransaction(sql, resolve, reject) { | ||
changeTransaction(cmdParam, resolve, reject) { | ||
//if command in progress, driver cannot rely on status and must execute query | ||
@@ -1291,3 +1240,3 @@ if (this.status >= Status.CLOSING) { | ||
'08S01', | ||
sql | ||
cmdParam.sql | ||
); | ||
@@ -1307,8 +1256,5 @@ if (this.opts.logger.error) this.opts.logger.error(err); | ||
}, | ||
null, | ||
this.opts, | ||
sql, | ||
null | ||
cmdParam | ||
); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
@@ -1318,3 +1264,3 @@ } else resolve(); | ||
changeUser(options, resolve, reject) { | ||
changeUser(cmdParam, resolve, reject) { | ||
if (!this.info.isMariaDB()) { | ||
@@ -1334,12 +1280,9 @@ const err = Errors.createError( | ||
new ChangeUser( | ||
options, | ||
cmdParam, | ||
this.opts, | ||
(res) => { | ||
if (options && options.collation) this.opts.collation = options.collation; | ||
if (cmdParam.opts && cmdParam.opts.collation) this.opts.collation = cmdParam.opts.collation; | ||
resolve(res); | ||
}, | ||
this.authFailHandler.bind(this, (err) => { | ||
if (this.opts.logger.error) this.opts.logger.error(err); | ||
reject(err); | ||
}), | ||
this.authFailHandler.bind(this, reject), | ||
this.addCommand.bind(this) | ||
@@ -1350,5 +1293,15 @@ ) | ||
query(_cmdOpt, _sql, _values, resolve, reject) { | ||
if (!_sql) | ||
return reject(Errors.createError('sql parameter is mandatory', Errors.ER_UNDEFINED_SQL, this.info, 'HY000')); | ||
query(cmdParam, resolve, reject) { | ||
if (!cmdParam.sql) | ||
return reject( | ||
Errors.createError( | ||
'sql parameter is mandatory', | ||
Errors.ER_UNDEFINED_SQL, | ||
this.info, | ||
'HY000', | ||
null, | ||
false, | ||
cmdParam.stack | ||
) | ||
); | ||
const cmd = new Query( | ||
@@ -1360,14 +1313,10 @@ resolve, | ||
}, | ||
_cmdOpt, | ||
this.opts, | ||
_sql, | ||
_values | ||
cmdParam | ||
); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
} | ||
prepare(_cmdOpt, _sql, executeFct, resolve, reject) { | ||
if (!_sql) | ||
prepare(cmdParam, executeFct, resolve, reject) { | ||
if (!cmdParam.sql) | ||
return reject(Errors.createError('sql parameter is mandatory', Errors.ER_UNDEFINED_SQL, this.info, 'HY000')); | ||
@@ -1380,9 +1329,7 @@ const cmd = new Prepare( | ||
}, | ||
_cmdOpt, | ||
this.opts, | ||
_sql, | ||
cmdParam, | ||
executeFct, | ||
this | ||
); | ||
if (this.opts.trace) Error.captureStackTrace(cmd); | ||
this.addCommand(cmd); | ||
@@ -1389,0 +1336,0 @@ } |
@@ -17,3 +17,3 @@ // noinspection SpellCheckingInspection | ||
static fromCharset(charset) { | ||
return defaultCharsets[charset]; | ||
return defaultCharsets[charset == 'utf8mb3' ? 'utf8' : charset]; | ||
} | ||
@@ -20,0 +20,0 @@ |
@@ -45,7 +45,7 @@ /** | ||
.then((res) => { | ||
conn.end(); | ||
conn.release(); | ||
return res; | ||
}) | ||
.catch((err) => { | ||
conn.end(); | ||
conn.release(); | ||
return Promise.reject(err); | ||
@@ -74,7 +74,7 @@ }); | ||
.then((res) => { | ||
conn.end(); | ||
conn.release(); | ||
return res; | ||
}) | ||
.catch((err) => { | ||
conn.end(); | ||
conn.release(); | ||
return Promise.reject(err); | ||
@@ -103,7 +103,7 @@ }); | ||
.then((res) => { | ||
conn.end(); | ||
conn.release(); | ||
return res; | ||
}) | ||
.catch((err) => { | ||
conn.end(); | ||
conn.release(); | ||
return Promise.reject(err); | ||
@@ -110,0 +110,0 @@ }); |
@@ -126,6 +126,5 @@ 'use strict'; | ||
if (chunkLen - pos >= length) { | ||
const buf = chunk.slice(pos, pos + length); | ||
pos += length; | ||
if (this.parts) { | ||
this.parts.push(buf); | ||
this.parts.push(chunk.slice(pos - length, pos)); | ||
this.partsTotalLen += length; | ||
@@ -140,5 +139,5 @@ | ||
if (this.packetLen < 0xffffff) { | ||
this.receivePacket(this.packet.update(buf, 0, length)); | ||
this.receivePacket(this.packet.update(chunk, pos - length, pos)); | ||
} else { | ||
this.parts = [buf]; | ||
this.parts = [chunk.slice(pos - length, pos)]; | ||
this.partsTotalLen = length; | ||
@@ -145,0 +144,0 @@ } |
@@ -14,3 +14,3 @@ 'use strict'; | ||
//increase by level to avoid buffer copy. | ||
const SMALL_BUFFER_SIZE = 1024; | ||
const SMALL_BUFFER_SIZE = 256; | ||
const MEDIUM_BUFFER_SIZE = 16384; //16k | ||
@@ -132,9 +132,11 @@ const LARGE_BUFFER_SIZE = 131072; //128k | ||
this.markPos = -1; | ||
const data = Buffer.allocUnsafe(this.pos - 4); | ||
this.buf.copy(data, 0, 4, this.pos); | ||
this.cmd.sequenceNo = -1; | ||
this.cmd.compressSequenceNo = -1; | ||
this.bufContainDataAfterMark = false; | ||
return data; | ||
if (this.bufContainDataAfterMark) { | ||
const data = Buffer.allocUnsafe(this.pos - 4); | ||
this.buf.copy(data, 0, 4, this.pos); | ||
this.cmd.sequenceNo = -1; | ||
this.cmd.compressSequenceNo = -1; | ||
this.bufContainDataAfterMark = false; | ||
return data; | ||
} | ||
return null; | ||
} | ||
@@ -558,9 +560,2 @@ | ||
if (valLen * 2 > this.buf.length - this.pos) { | ||
if (this.markPos !== -1) { | ||
this.growBuffer(valLen * 2); | ||
if (this.markPos !== -1) { | ||
this.flushBufferStopAtMark(); | ||
} | ||
} | ||
//not enough space in buffer, will fill buffer | ||
@@ -567,0 +562,0 @@ for (let i = 0; i < valLen; i++) { |
@@ -192,2 +192,3 @@ 'use strict'; | ||
const type = this.buf[this.pos++] & 0xff; | ||
if (type < 0xfb) return type; | ||
switch (type) { | ||
@@ -203,4 +204,2 @@ case 0xfb: | ||
return Number(this.readBigInt64()); | ||
default: | ||
return type; | ||
} | ||
@@ -239,4 +238,5 @@ } | ||
switch (type) { | ||
case 0xfb: | ||
return null; | ||
// null test is not used for now, since only used for reading insertId | ||
// case 0xfb: | ||
// return null; | ||
case 0xfc: | ||
@@ -288,3 +288,3 @@ return BigInt(this.readUInt16()); | ||
this.pos += len; | ||
return negate ? -1 * result : result; | ||
return negate ? -1n * result : result; | ||
} | ||
@@ -360,3 +360,3 @@ | ||
readBinaryDateTime(opts, col) { | ||
readBinaryDateTime(opts) { | ||
const len = this.buf[this.pos++]; | ||
@@ -374,3 +374,3 @@ let year = 0; | ||
if (len > 2) { | ||
month = this.readUInt8() - 1; | ||
month = this.readUInt8(); | ||
if (len > 3) { | ||
@@ -391,28 +391,62 @@ day = this.readUInt8(); | ||
//handle zero-date as null | ||
if (year === 0 && month === 0 && day === 0 && hour === 0 && min === 0 && sec === 0 && microSec === 0) | ||
return opts.dateStrings | ||
? '0000-00-00 00:00:00' + (col.scale > 0 ? '.000000'.substr(0, col.scale + 1) : '') | ||
: null; | ||
if (year === 0 && month === 0 && day === 0 && hour === 0 && min === 0 && sec === 0 && microSec === 0) return null; | ||
if (opts.dateStrings) { | ||
return ( | ||
appendZero(year, 4) + | ||
'-' + | ||
appendZero(month + 1, 2) + | ||
'-' + | ||
appendZero(day, 2) + | ||
' ' + | ||
appendZero(hour, 2) + | ||
':' + | ||
appendZero(min, 2) + | ||
':' + | ||
appendZero(sec, 2) + | ||
(microSec > 0 && col.scale > 0 ? '.' + appendZero(microSec, 6).substr(0, col.scale) : '') | ||
); | ||
if (opts.tz && opts.tz === 'Etc/UTC') { | ||
return new Date(Date.UTC(year, month - 1, day, hour, min, sec, microSec / 1000)); | ||
} | ||
return new Date(year, month - 1, day, hour, min, sec, microSec / 1000); | ||
} | ||
if (opts.tz && opts.tz === 'Etc/UTC') { | ||
return new Date(Date.UTC(year, month, day, hour, min, sec, microSec / 1000)); | ||
readBinaryDateTimeAsString(scale) { | ||
const len = this.buf[this.pos++]; | ||
let year = 0; | ||
let month = 0; | ||
let day = 0; | ||
let hour = 0; | ||
let min = 0; | ||
let sec = 0; | ||
let microSec = 0; | ||
if (len > 0) { | ||
year = this.readInt16(); | ||
if (len > 2) { | ||
month = this.readUInt8(); | ||
if (len > 3) { | ||
day = this.readUInt8(); | ||
if (len > 4) { | ||
hour = this.readUInt8(); | ||
min = this.readUInt8(); | ||
sec = this.readUInt8(); | ||
if (len > 7) { | ||
microSec = this.readUInt32(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return new Date(year, month, day, hour, min, sec, microSec / 1000); | ||
//handle zero-date as null | ||
if (year === 0 && month === 0 && day === 0 && hour === 0 && min === 0 && sec === 0 && microSec === 0) | ||
return '0000-00-00 00:00:00' + (scale > 0 ? '.000000'.substr(0, scale + 1) : ''); | ||
return ( | ||
appendZero(year, 4) + | ||
'-' + | ||
appendZero(month, 2) + | ||
'-' + | ||
appendZero(day, 2) + | ||
' ' + | ||
appendZero(hour, 2) + | ||
':' + | ||
appendZero(min, 2) + | ||
':' + | ||
appendZero(sec, 2) + | ||
(microSec > 0 | ||
? scale > 0 | ||
? '.' + appendZero(microSec, 6).substr(0, scale) | ||
: '.' + appendZero(microSec, 6) | ||
: scale > 0 | ||
? '.' + appendZero(microSec, 6).substr(0, scale) | ||
: '') | ||
); | ||
} | ||
@@ -451,5 +485,15 @@ | ||
readIntLengthEncoded() { | ||
const len = this.readUnsignedLength(); | ||
if (len === null) return null; | ||
return this._atoi(len); | ||
const len = this.buf[this.pos++] & 0xff; | ||
if (len < 0xfb) return this._atoi(len); | ||
switch (len) { | ||
case 0xfb: | ||
return null; | ||
case 0xfc: | ||
return this._atoi(this.readUInt16()); | ||
case 0xfd: | ||
return this._atoi(this.readUInt24()); | ||
case 0xfe: | ||
// limitation to BigInt signed value | ||
return this._atoi(Number(this.readBigInt64())); | ||
} | ||
} | ||
@@ -536,10 +580,15 @@ | ||
let errno = this.readUInt16(); | ||
let sqlState = ''; | ||
let sqlState; | ||
let msg; | ||
// check '#' | ||
if (this.peek() === 0x23) { | ||
// skip '#' | ||
this.skip(6); | ||
sqlState = this.buf.toString(undefined, this.pos - 5, this.pos); | ||
msg = this.readStringNullEnded(); | ||
} else { | ||
// pre 4.1 format | ||
sqlState = 'HY000'; | ||
msg = this.buf.toString(undefined, this.pos, this.end); | ||
} | ||
let msg = this.buf.toString(undefined, this.pos, this.end); | ||
let fatal = sqlState.startsWith('08') || sqlState === '70100'; | ||
@@ -546,0 +595,0 @@ return Errors.createError(msg, errno, info, sqlState, sql, fatal, stack); |
@@ -15,2 +15,3 @@ 'use strict'; | ||
); | ||
this.name = 'SqlError'; | ||
this.text = msg; | ||
@@ -89,3 +90,2 @@ this.sql = sql; | ||
module.exports.ER_CONNECTION_ALREADY_CLOSED = 45001; | ||
module.exports.ER_ALREADY_CONNECTING = 45002; | ||
module.exports.ER_MYSQL_CHANGE_USER_BUG = 45003; | ||
@@ -128,3 +128,2 @@ module.exports.ER_CMD_NOT_EXECUTED_DESTROYED = 45004; | ||
module.exports.ER_DUPLICATE_FIELD = 45040; | ||
module.exports.ER_CLIENT_OPTION_INCOMPATIBILITY = 45041; | ||
module.exports.ER_PING_TIMEOUT = 45042; | ||
@@ -138,2 +137,3 @@ module.exports.ER_BAD_PARAMETER_VALUE = 45043; | ||
module.exports.ER_UNDEFINED_SQL = 45049; | ||
module.exports.ER_PARSING_PRECISION = 45050; | ||
@@ -140,0 +140,0 @@ const keys = Object.keys(module.exports); |
@@ -8,2 +8,3 @@ 'use strict'; | ||
const ConnectionCallback = require('./connection-callback'); | ||
const CommandParameter = require('./command-parameter'); | ||
@@ -19,2 +20,3 @@ class PoolCallback extends EventEmitter { | ||
this.#pool.on('release', this.emit.bind(this, 'release')); | ||
this.#pool.on('error', this.emit.bind(this, 'error')); | ||
} | ||
@@ -93,21 +95,11 @@ | ||
} | ||
const cmdParam = new CommandParameter(); | ||
if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam); | ||
this.#pool | ||
.getConnection() | ||
.getConnection(cmdParam) | ||
.then((baseConn) => { | ||
const conn = new ConnectionCallback(baseConn); | ||
conn.release = function () { | ||
return new Promise(baseConn.release); | ||
}; | ||
conn.release = (cb) => { | ||
baseConn.release( | ||
() => { | ||
if (cb) cb(); | ||
}, | ||
(err) => { | ||
if (cb) cb(err); | ||
} | ||
); | ||
}; | ||
conn.end = conn.release; | ||
cb(null, conn); | ||
const cc = new ConnectionCallback(baseConn); | ||
cc.end = (cb) => cc.release(cb); | ||
cc.close = (cb) => cc.release(cb); | ||
cb(null, cc); | ||
}) | ||
@@ -127,20 +119,15 @@ .catch(cb); | ||
query(sql, values, cb) { | ||
let _cb = cb, | ||
_values = values; | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
const cmdParam = ConnectionCallback._PARAM(this.#pool.opts.connOptions, sql, values, cb); | ||
this.#pool | ||
.getConnection() | ||
.getConnection(cmdParam) | ||
.then((baseConn) => { | ||
ConnectionCallback._QUERY_CMD(baseConn, sql, _values, (err, rows, meta) => { | ||
const _cb = cmdParam.callback; | ||
cmdParam.callback = (err, rows, meta) => { | ||
this.#pool.release(baseConn); | ||
if (_cb) _cb(err, rows, meta); | ||
}); | ||
}; | ||
ConnectionCallback._QUERY_CMD(baseConn, cmdParam); | ||
}) | ||
.catch((err) => { | ||
if (_cb) _cb(err); | ||
if (cmdParam.callback) cmdParam.callback(err); | ||
}); | ||
@@ -159,20 +146,16 @@ } | ||
execute(sql, values, cb) { | ||
let _cb = cb, | ||
_values = values; | ||
const cmdParam = ConnectionCallback._PARAM(this.#pool.opts.connOptions, sql, values, cb); | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
this.#pool | ||
.getConnection() | ||
.getConnection(cmdParam) | ||
.then((baseConn) => { | ||
ConnectionCallback._EXECUTE_CMD(baseConn, sql, _values, (err, rows, meta) => { | ||
const _cb = cmdParam.callback; | ||
cmdParam.callback = (err, rows, meta) => { | ||
this.#pool.release(baseConn); | ||
if (_cb) _cb(err, rows, meta); | ||
}); | ||
}; | ||
ConnectionCallback._EXECUTE_CMD(baseConn, cmdParam); | ||
}) | ||
.catch((err) => { | ||
if (_cb) _cb(err); | ||
if (cmdParam.callback) cmdParam.callback(err); | ||
}); | ||
@@ -190,20 +173,15 @@ } | ||
batch(sql, values, cb) { | ||
let _values = values, | ||
_cb = cb; | ||
if (typeof values === 'function') { | ||
_cb = values; | ||
_values = undefined; | ||
} | ||
const cmdParam = ConnectionCallback._PARAM(this.#pool.opts.connOptions, sql, values, cb); | ||
this.#pool | ||
.getConnection() | ||
.getConnection(cmdParam) | ||
.then((baseConn) => { | ||
ConnectionCallback._BATCH_CMD(baseConn, sql, _values, (err, rows, meta) => { | ||
const _cb = cmdParam.callback; | ||
cmdParam.callback = (err, rows, meta) => { | ||
this.#pool.release(baseConn); | ||
if (_cb) _cb(err, rows, meta); | ||
}); | ||
}; | ||
ConnectionCallback._BATCH_CMD(baseConn, cmdParam); | ||
}) | ||
.catch((err) => { | ||
if (_cb) _cb(err); | ||
if (cmdParam.callback) cmdParam.callback(err); | ||
}); | ||
@@ -210,0 +188,0 @@ } |
@@ -7,2 +7,3 @@ 'use strict'; | ||
const ConnectionPromise = require('./connection-promise'); | ||
const CommandParameter = require('./command-parameter'); | ||
@@ -18,2 +19,3 @@ class PoolPromise extends EventEmitter { | ||
this.#pool.on('release', this.emit.bind(this, 'release')); | ||
this.#pool.on('error', this.emit.bind(this, 'error')); | ||
} | ||
@@ -81,3 +83,5 @@ | ||
getConnection() { | ||
return this.#pool.getConnection().then((baseConn) => { | ||
const cmdParam = new CommandParameter(); | ||
if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam); | ||
return this.#pool.getConnection(cmdParam).then((baseConn) => { | ||
const conn = new ConnectionPromise(baseConn); | ||
@@ -87,2 +91,4 @@ conn.release = function () { | ||
}; | ||
conn.end = conn.release; | ||
conn.close = conn.release; | ||
return conn; | ||
@@ -101,4 +107,5 @@ }); | ||
query(sql, values) { | ||
return this.#pool.getConnection().then((baseConn) => { | ||
return ConnectionPromise._QUERY_CMD(baseConn, sql, values).finally(() => { | ||
const cmdParam = ConnectionPromise._PARAM(this.#pool.opts.connOptions, sql, values); | ||
return this.#pool.getConnection(cmdParam).then((baseConn) => { | ||
return new Promise(baseConn.query.bind(baseConn, cmdParam)).finally(() => { | ||
this.#pool.release(baseConn); | ||
@@ -118,4 +125,5 @@ }); | ||
execute(sql, values) { | ||
return this.#pool.getConnection().then((baseConn) => { | ||
return ConnectionPromise._EXECUTE_CMD(baseConn, sql, values).finally(() => { | ||
const cmdParam = ConnectionPromise._PARAM(this.#pool.opts.connOptions, sql, values); | ||
return this.#pool.getConnection(cmdParam).then((baseConn) => { | ||
return ConnectionPromise._EXECUTE_CMD(baseConn, cmdParam).finally(() => { | ||
this.#pool.release(baseConn); | ||
@@ -134,4 +142,5 @@ }); | ||
batch(sql, values) { | ||
return this.#pool.getConnection().then((baseConn) => { | ||
return ConnectionPromise._BATCH_CMD(baseConn, sql, values).finally(() => { | ||
const cmdParam = ConnectionPromise._PARAM(this.#pool.opts.connOptions, sql, values); | ||
return this.#pool.getConnection(cmdParam).then((baseConn) => { | ||
return ConnectionPromise._BATCH_CMD(baseConn, cmdParam).finally(() => { | ||
this.#pool.release(baseConn); | ||
@@ -138,0 +147,0 @@ }); |
295
lib/pool.js
@@ -9,7 +9,9 @@ 'use strict'; | ||
const Connection = require('./connection'); | ||
const CommandParameter = require('./command-parameter'); | ||
class Pool extends EventEmitter { | ||
#opts; | ||
opts; | ||
#closed = false; | ||
#connectionInCreation = false; | ||
#errorCreatingConnection = null; | ||
#idleConnections = new Queue(); | ||
@@ -21,2 +23,3 @@ #activeConnections = {}; | ||
#connErrorNumber = 0; | ||
#initialized = false; | ||
_sizeHandlerTimeout; | ||
@@ -26,3 +29,3 @@ | ||
super(); | ||
this.#opts = options; | ||
this.opts = options; | ||
@@ -44,9 +47,13 @@ this.on('_idle', this._requestsHandler); | ||
conn.forceEnd( | ||
null, | ||
() => {}, | ||
() => {} | ||
); | ||
throw new Errors.createFatalError( | ||
'Cannot create new connection to pool, pool closed', | ||
Errors.ER_ADD_CONNECTION_CLOSED_POOL | ||
reject( | ||
new Errors.createFatalError( | ||
'Cannot create new connection to pool, pool closed', | ||
Errors.ER_ADD_CONNECTION_CLOSED_POOL | ||
) | ||
); | ||
return; | ||
} | ||
@@ -73,7 +80,7 @@ | ||
pool.#idleConnections.removeOne(idx); | ||
break; | ||
continue; | ||
} | ||
//since connection did have an error, other waiting connection might too | ||
//forcing validation when borrowed next time, even if "minDelayValidation" is not reached. | ||
currConn.lastUse = Math.min(currConn.lastUse, Date.now() - pool.#opts.minDelayValidation); | ||
currConn.lastUse = Math.min(currConn.lastUse, Date.now() - pool.opts.minDelayValidation); | ||
idx++; | ||
@@ -101,6 +108,7 @@ } | ||
) { | ||
err.message = err.message + this._errorMsgAddon(); | ||
reject(err); | ||
return; | ||
} | ||
setTimeout(this._doCreateConnection.bind(this), 500, resolve, reject, timeoutEnd); | ||
setTimeout(this._doCreateConnection.bind(this, resolve, reject, timeoutEnd), 500); | ||
}); | ||
@@ -112,5 +120,5 @@ } | ||
delete this.#activeConnections[conn.threadId]; | ||
conn.lastUse = Date.now(); | ||
conn.forceEnd( | ||
null, | ||
() => {}, | ||
@@ -128,23 +136,21 @@ () => {} | ||
release(conn) { | ||
this._endLeak(conn); | ||
delete this.#activeConnections[conn.threadId]; | ||
// ensure releasing only once | ||
if (this.#activeConnections[conn.threadId]) { | ||
this._endLeak(conn); | ||
this.#activeConnections[conn.threadId] = null; | ||
conn.lastUse = Date.now(); | ||
conn.lastUse = Date.now(); | ||
if (this.#closed) { | ||
conn.forceEnd( | ||
() => {}, | ||
() => {} | ||
); | ||
this.emit('validateSize'); | ||
} else if (conn.isValid()) { | ||
this.emit('release', conn); | ||
this.#idleConnections.push(conn); | ||
process.nextTick( | ||
function () { | ||
this.emit('_idle'); | ||
}.bind(this) | ||
); | ||
} else { | ||
this.emit('validateSize'); | ||
if (this.#closed) { | ||
conn.forceEnd( | ||
null, | ||
() => {}, | ||
() => {} | ||
); | ||
} else if (conn.isValid()) { | ||
this.emit('release', conn); | ||
this.#idleConnections.push(conn); | ||
process.nextTick(this.emit.bind(this, '_idle')); | ||
} else { | ||
this.emit('validateSize'); | ||
} | ||
} | ||
@@ -158,10 +164,12 @@ } | ||
(conn) => { | ||
conn.leaked = true; | ||
console.log( | ||
`Possible connection leak on thread ${conn.info.threadId} (connection not returned to pool since ${ | ||
`A possible connection leak on the thread ${ | ||
conn.info.threadId | ||
} (the connection not returned to the pool since ${ | ||
Date.now() - conn.lastUse | ||
}ms. Did connection.released() been implemented` | ||
} ms). Has the connection.release() been called ?` + this._errorMsgAddon() | ||
); | ||
conn.leaked = true; | ||
}, | ||
this.#opts.leakDetectionTimeout, | ||
this.opts.leakDetectionTimeout, | ||
conn | ||
@@ -185,3 +193,3 @@ ); | ||
_startReaping() { | ||
if (!this.#unusedConnectionRemoverId && this.#opts.idleTimeout > 0) { | ||
if (!this.#unusedConnectionRemoverId && this.opts.idleTimeout > 0) { | ||
this.#unusedConnectionRemoverId = setInterval(this._reaper.bind(this), 500); | ||
@@ -198,4 +206,4 @@ } | ||
_reaper() { | ||
const idleTimeRemoval = Date.now() - this.#opts.idleTimeout * 1000; | ||
let maxRemoval = Math.max(0, this.#idleConnections.length - this.#opts.minimumIdle); | ||
const idleTimeRemoval = Date.now() - this.opts.idleTimeout * 1000; | ||
let maxRemoval = Math.max(0, this.#idleConnections.length - this.opts.minimumIdle); | ||
while (maxRemoval > 0) { | ||
@@ -207,2 +215,3 @@ const conn = this.#idleConnections.peek(); | ||
conn.forceEnd( | ||
null, | ||
() => {}, | ||
@@ -225,4 +234,4 @@ () => {} | ||
!this.#connectionInCreation && | ||
this.#idleConnections.length < this.#opts.minimumIdle && | ||
this.totalConnections() < this.#opts.connectionLimit && | ||
this.#idleConnections.length < this.opts.minimumIdle && | ||
this.totalConnections() < this.opts.connectionLimit && | ||
!this.#closed | ||
@@ -240,3 +249,3 @@ ); | ||
function () { | ||
const timeoutEnd = Date.now() + this.#opts.initializationTimeout; | ||
const timeoutEnd = Date.now() + this.opts.initializationTimeout; | ||
new Promise((resolve, reject) => { | ||
@@ -246,2 +255,4 @@ this._doCreateConnection(resolve, reject, timeoutEnd); | ||
.then(() => { | ||
this.#initialized = true; | ||
this.#errorCreatingConnection = null; | ||
this.#connErrorNumber = 0; | ||
@@ -255,21 +266,22 @@ if (this._shouldCreateMoreConnections()) { | ||
this.#connectionInCreation = false; | ||
if (this.totalConnections() === 0) { | ||
const task = this.#requests.shift(); | ||
if (task) { | ||
this._rejectTask(task, err); | ||
if (!this.#closed) { | ||
if (!this.#initialized) { | ||
err.message = 'Error during pool initialization: ' + err.message; | ||
} else { | ||
err.message = 'Pool fails to create connection: ' + err.message; | ||
} | ||
} else if (!this.#closed) { | ||
console.error(`pool fail to create connection (${err.message})`); | ||
this.#errorCreatingConnection = err; | ||
this.emit('error', err); | ||
//delay next try | ||
this._sizeHandlerTimeout = setTimeout( | ||
function () { | ||
this._sizeHandlerTimeout = null; | ||
if (!this.#requests.isEmpty()) { | ||
this._sizeHandler(); | ||
} | ||
}.bind(this), | ||
Math.min(++this.#connErrorNumber * 500, 10000) | ||
); | ||
} | ||
//delay next try | ||
this._sizeHandlerTimeout = setTimeout( | ||
function () { | ||
this._sizeHandlerTimeout = null; | ||
if (!this.#requests.isEmpty()) { | ||
this._sizeHandler(); | ||
} | ||
}.bind(this), | ||
Math.min(++this.#connErrorNumber * 500, 10000) | ||
); | ||
}); | ||
@@ -291,3 +303,3 @@ }.bind(this) | ||
if (conn) { | ||
if (this.#opts.leakDetectionTimeout > 0) this._checkLeak(conn); | ||
if (this.opts.leakDetectionTimeout > 0) this._checkLeak(conn); | ||
this.emit('acquire', conn); | ||
@@ -313,27 +325,28 @@ this.#activeConnections[conn.threadId] = conn; | ||
*/ | ||
_doAcquire() { | ||
async _doAcquire() { | ||
if (!this._hasIdleConnection() || this.#closed) return Promise.reject(); | ||
const conn = this.#idleConnections.shift(); | ||
this.#activeConnections[conn.threadId] = conn; | ||
if (this.#opts.minDelayValidation <= 0 || Date.now() - conn.lastUse > this.#opts.minDelayValidation) { | ||
return new Promise(conn.ping.bind(conn, this.#opts.pingTimeout)).then( | ||
() => { | ||
if (this.#opts.leakDetectionTimeout > 0) this._checkLeak(conn); | ||
return Promise.resolve(conn); | ||
}, | ||
() => { | ||
delete this.#activeConnections[conn.threadId]; | ||
this.emit('validateSize'); | ||
return this._doAcquire(); | ||
let conn; | ||
let mustRecheckSize = false; | ||
while ((conn = this.#idleConnections.shift()) != null) { | ||
//just check connection state first | ||
if (conn.isValid()) { | ||
this.#activeConnections[conn.threadId] = conn; | ||
//if not used for some time, validate connection with a COM_PING | ||
if (this.opts.minDelayValidation <= 0 || Date.now() - conn.lastUse > this.opts.minDelayValidation) { | ||
try { | ||
const cmdParam = new CommandParameter(null, null, { timeout: this.opts.pingTimeout }); | ||
await new Promise(conn.ping.bind(conn, cmdParam)); | ||
} catch (e) { | ||
delete this.#activeConnections[conn.threadId]; | ||
continue; | ||
} | ||
} | ||
); | ||
} else { | ||
//just check connection state | ||
if (conn.isValid()) { | ||
if (this.#opts.leakDetectionTimeout > 0) this._checkLeak(conn); | ||
if (this.opts.leakDetectionTimeout > 0) this._checkLeak(conn); | ||
if (mustRecheckSize) setImmediate(this.emit.bind(this, 'validateSize')); | ||
return Promise.resolve(conn); | ||
} | ||
mustRecheckSize = true; | ||
} | ||
setImmediate(this.emit.bind(this, 'validateSize')); | ||
return Promise.reject(); | ||
} | ||
@@ -349,10 +362,21 @@ | ||
this.#requests.shift(); | ||
request.reject( | ||
Errors.createError( | ||
`retrieve connection from pool timeout after ${Math.abs( | ||
Date.now() - (request.timeout - this.#opts.acquireTimeout) | ||
)}ms`, | ||
Errors.ER_GET_CONNECTION_TIMEOUT | ||
) | ||
let err = Errors.createError( | ||
`retrieve connection from pool timeout after ${Math.abs( | ||
Date.now() - (request.timeout - this.opts.acquireTimeout) | ||
)}ms${this._errorMsgAddon()}`, | ||
Errors.ER_GET_CONNECTION_TIMEOUT, | ||
null, | ||
'HY000', | ||
null, | ||
false, | ||
request.stack | ||
); | ||
// in order to provide more information when configuration is wrong / server is down | ||
if (this.activeConnections() == 0 && this.#errorCreatingConnection) { | ||
const errConnMsg = this.#errorCreatingConnection.message.split('\n')[0]; | ||
err.message = err.message + `\n connection error: ${errConnMsg}`; | ||
} | ||
request.reject(err); | ||
} else { | ||
@@ -373,7 +397,12 @@ this.#requestTimeoutId = setTimeout(this._requestTimeoutHandler.bind(this), request.timeout - currTime); | ||
if (conn == null) { | ||
conn = Object.keys(this.#activeConnections)[0]; | ||
if (!conn) { | ||
for (const threadId in Object.keys(this.#activeConnections)) { | ||
conn = this.#activeConnections[threadId]; | ||
if (!conn) { | ||
break; | ||
} | ||
} | ||
} | ||
if (conn != null) { | ||
if (conn) { | ||
info = conn.info; | ||
@@ -392,7 +421,7 @@ } | ||
async _createConnection() { | ||
const conn = new Connection(this.#opts.connOptions); | ||
const conn = new Connection(this.opts.connOptions); | ||
await conn.connect(); | ||
const pool = this; | ||
conn.forceEnd = conn.end; | ||
conn.release = function (resolve, release) { | ||
conn.release = function (resolve, reject) { | ||
if (pool.#closed || !conn.isValid()) { | ||
@@ -403,3 +432,3 @@ pool._destroy(conn); | ||
} | ||
if (pool.#opts.noControlAfterUse) { | ||
if (pool.opts.noControlAfterUse) { | ||
pool.release(conn); | ||
@@ -415,3 +444,3 @@ resolve(); | ||
if ( | ||
pool.#opts.resetAfterUse && | ||
pool.opts.resetAfterUse && | ||
conn.info.isMariaDB() && | ||
@@ -421,4 +450,4 @@ ((conn.info.serverVersion.minor === 2 && conn.info.hasMinVersion(10, 2, 22)) || | ||
) { | ||
revertFunction = conn.reset.bind(conn); | ||
} else revertFunction = conn.changeTransaction.bind(conn, 'ROLLBACK'); | ||
revertFunction = conn.reset.bind(conn, new CommandParameter()); | ||
} else revertFunction = conn.changeTransaction.bind(conn, new CommandParameter('ROLLBACK')); | ||
@@ -431,2 +460,21 @@ new Promise(revertFunction).then(pool.release.bind(pool, conn), pool._destroy.bind(pool, conn)).finally(resolve); | ||
_leakedConnections() { | ||
let counter = 0; | ||
for (const [key, value] of Object.entries(this.#activeConnections)) { | ||
if (value.leaked) counter++; | ||
} | ||
return counter; | ||
} | ||
_errorMsgAddon() { | ||
if (this.opts.leakDetectionTimeout > 0) { | ||
return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} leak=${this._leakedConnections()} limit=${ | ||
this.opts.connectionLimit | ||
})`; | ||
} | ||
return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} limit=${ | ||
this.opts.connectionLimit | ||
})`; | ||
} | ||
//***************************************************************** | ||
@@ -453,3 +501,7 @@ // public methods | ||
activeConnections() { | ||
return Object.keys(this.#activeConnections).length; | ||
let counter = 0; | ||
for (const [key, value] of Object.entries(this.#activeConnections)) { | ||
if (value) counter++; | ||
} | ||
return counter; | ||
} | ||
@@ -474,7 +526,7 @@ | ||
escape(value) { | ||
return Utils.escape(this.#opts.connOptions, this._searchInfo(), value); | ||
return Utils.escape(this.opts.connOptions, this._searchInfo(), value); | ||
} | ||
escapeId(value) { | ||
return Utils.escapeId(this.#opts.connOptions, this._searchInfo(), value); | ||
return Utils.escapeId(this.opts.connOptions, this._searchInfo(), value); | ||
} | ||
@@ -490,10 +542,11 @@ | ||
* wait until acquireTimeout. | ||
* | ||
* @param cmdParam for stackTrace error | ||
* @return {Promise} | ||
*/ | ||
getConnection() { | ||
getConnection(cmdParam) { | ||
if (this.#closed) { | ||
return Promise.reject(Errors.createError('pool is closed', Errors.ER_POOL_ALREADY_CLOSED)); | ||
return Promise.reject( | ||
Errors.createError('pool is closed', Errors.ER_POOL_ALREADY_CLOSED, null, 'HY000', null, false, cmdParam.stack) | ||
); | ||
} | ||
return this._doAcquire().then( | ||
@@ -507,16 +560,24 @@ (conn) => { | ||
if (this.#closed) { | ||
throw Errors.createError('Cannot add request to pool, pool is closed', Errors.ER_POOL_ALREADY_CLOSED); | ||
throw Errors.createError( | ||
'Cannot add request to pool, pool is closed', | ||
Errors.ER_POOL_ALREADY_CLOSED, | ||
null, | ||
'HY000', | ||
null, | ||
false, | ||
cmdParam.stack | ||
); | ||
} | ||
// no idle connection available | ||
// create a new connection if limit is not reached | ||
this.emit('validateSize'); | ||
setImmediate(this.emit.bind(this, 'validateSize')); | ||
return new Promise( | ||
function (resolver, rejecter) { | ||
// stack request | ||
setImmediate(this.emit.bind(this, 'enqueue')); | ||
const request = new Request(Date.now() + this.opts.acquireTimeout, cmdParam.stack, resolver, rejecter); | ||
this.#requests.push(request); | ||
if (!this.#requestTimeoutId) { | ||
this.#requestTimeoutId = setTimeout(this._requestTimeoutHandler.bind(this), this.#opts.acquireTimeout); | ||
this.#requestTimeoutId = setTimeout(this._requestTimeoutHandler.bind(this), this.opts.acquireTimeout); | ||
} | ||
// stack request | ||
setImmediate(this.emit.bind(this, 'enqueue')); | ||
this.#requests.push(new Request(Date.now() + this.#opts.acquireTimeout, resolver, rejecter)); | ||
}.bind(this) | ||
@@ -540,3 +601,4 @@ ); | ||
clearInterval(this._sizeHandlerTimeout); | ||
const cmdParam = new CommandParameter(); | ||
if (this.opts.trace) Error.captureStackTrace(cmdParam); | ||
//close unused connections | ||
@@ -546,3 +608,3 @@ const idleConnectionsEndings = []; | ||
while ((conn = this.#idleConnections.shift())) { | ||
idleConnectionsEndings.push(new Promise(conn.forceEnd.bind(conn))); | ||
idleConnectionsEndings.push(new Promise(conn.forceEnd.bind(conn, cmdParam))); | ||
} | ||
@@ -552,7 +614,13 @@ | ||
this.#requestTimeoutId = null; | ||
//reject all waiting task | ||
if (!this.#requests.isEmpty()) { | ||
const err = Errors.createError('pool is ending, connection request aborted', Errors.ER_CLOSING_POOL); | ||
const err = Errors.createError( | ||
'pool is ending, connection request aborted', | ||
Errors.ER_CLOSING_POOL, | ||
null, | ||
'HY000', | ||
null, | ||
false, | ||
cmdParam.stack | ||
); | ||
let task; | ||
@@ -569,4 +637,5 @@ while ((task = this.#requests.shift())) { | ||
class Request { | ||
constructor(timeout, resolver, rejecter) { | ||
constructor(timeout, stack, resolver, rejecter) { | ||
this.timeout = timeout; | ||
this.stack = stack; | ||
this.resolver = resolver; | ||
@@ -573,0 +642,0 @@ this.rejecter = rejecter; |
{ | ||
"name": "mariadb", | ||
"version": "3.0.0", | ||
"description": "fast mariadb/mysql connector.", | ||
"version": "3.0.1", | ||
"description": "fast mariadb or mysql connector.", | ||
"main": "promise.js", | ||
@@ -22,3 +22,3 @@ "types": "types/index.d.ts", | ||
"coverage:report": "nyc report --reporter=text-lcov > coverage.lcov && codecov", | ||
"benchmark": "node ./benchmarks/benchmarks.js", | ||
"benchmark": "node benchmarks/benchmarks-all.js", | ||
"generate": "node ./tools/generate-mariadb.js" | ||
@@ -62,4 +62,4 @@ }, | ||
"chai": "^4.3.4", | ||
"chalk": "^4.1.2", | ||
"codecov": "^3.8.2", | ||
"chalk": "^4.1.2", | ||
"dom-parser": "^0.1.6", | ||
@@ -66,0 +66,0 @@ "error-stack-parser": "^2.0.6", |
@@ -18,6 +18,3 @@ <p align="center"> | ||
version before 2.4 is compatible with Node.js 6+ | ||
version after 2.4 is compatible with Node.js 10+ | ||
## Documentation | ||
@@ -36,3 +33,13 @@ | ||
Connector is production grade quality, with multiple features: | ||
* superfast batching | ||
* fast pool | ||
* easy debugging, trace pointing to code line on error | ||
* allows data streaming without high memory consumption | ||
* pipelining | ||
* metadata skipping (for MariaDB server only) | ||
* ... | ||
see some of those features: | ||
### Insert Streaming | ||
@@ -71,7 +78,8 @@ | ||
MariaDB provides benchmarks comparing the Connector with popular Node.js MySQL clients, including: | ||
MariaDB provides benchmarks comparing the Connector with other Node.js MariaDB/MySQL clients, including: | ||
* [`promise-mysql`](https://www.npmjs.com/package/promise-mysql) version 5.0.4 + [`mysql`](https://www.npmjs.com/package/mysql) version 2.18.1 | ||
* [`mysql2`](https://www.npmjs.com/package/mysql2) version 2.2.5 | ||
* [`promise-mysql`](https://www.npmjs.com/package/promise-mysql) version 5.2.0 + [`mysql`](https://www.npmjs.com/package/mysql) version 2.18.1 | ||
* [`mysql2`](https://www.npmjs.com/package/mysql2) version 2.3.3 | ||
See the [Benchmarks](./documentation/benchmarks.md) page for multiple results. | ||
@@ -81,18 +89,18 @@ #### query | ||
``` | ||
select * from mysql.user - mysql x 1,442 ops/sec ±0.38% | ||
select * from mysql.user - mysql2 x 1,484 ops/sec ±0.60% | ||
select * from mysql.user - mariadb x 1,595 ops/sec ±0.38% | ||
select 20 * int, 20 * varchar(32) | ||
mysql : 3,086 ops/s ± 0.6% | ||
mysql2 : 2,799.6 ops/s ± 1.6% ( -9.3% ) | ||
mariadb : 4,710.8 ops/s ± 1% ( +52.7% ) | ||
``` | ||
![select 20 * int, 20 * varchar(32) benchmark results](https://quickchart.io/chart/render/zm-ef74089a-be91-49f1-b5a0-5b9ac5752435?data1=3086&data2=2800&data3=4711) | ||
<img src="https://quickchart.io/chart/render/zm-e2bd7f00-c7ca-4412-84e5-5284055056b5?data1=1442&data2=1484&data3=1595&title=select%20one%20mysql.user%0A%20%5B%20sql%3A%20select%20*%20from%20mysql.user%20LIMIT%201%20%5D" width="500" height="160"/> | ||
#### execute | ||
``` | ||
select * from mysql.user using execute - mysql2 x 2,257 ops/sec ±0.84% | ||
select * from mysql.user using execute - mariadb x 2,651 ops/sec ±0.59% | ||
select 20 * int, 20 * varchar(32) using execute | ||
mysql2 : 2,998 ops/s ± 1.3% | ||
mariadb : 4,419.6 ops/s ± 1% ( +47.4% ) | ||
``` | ||
![select 20 * int, 20 * varchar(32) using execute benchmark results](https://quickchart.io/chart/render/zm-36b213f4-8efe-4943-8f94-82edf94fce83?data1=2998&data2=4420) | ||
<img src="https://quickchart.io/chart?devicePixelRatio=1.0&h=140&w=520&c=%7B%22type%22%3A%22horizontalBar%22%2C%22data%22%3A%7B%22datasets%22%3A%5B%7B%22label%22%3A%22mysql2%202.2.5%22%2C%22backgroundColor%22%3A%22%234285f4%22%2C%22data%22%3A%5B2257%5D%7D%2C%7B%22label%22%3A%22mariadb%203.0.1%22%2C%22backgroundColor%22%3A%22%23ff9900%22%2C%22data%22%3A%5B2651%5D%7D%5D%7D%2C%22options%22%3A%7B%22plugins%22%3A%7B%22datalabels%22%3A%7B%22anchor%22%3A%22end%22%2C%22align%22%3A%22start%22%2C%22color%22%3A%22%23fff%22%2C%22font%22%3A%7B%22weight%22%3A%22bold%22%7D%7D%7D%2C%22elements%22%3A%7B%22rectangle%22%3A%7B%22borderWidth%22%3A0%7D%7D%2C%22responsive%22%3Atrue%2C%22legend%22%3A%7B%22position%22%3A%22right%22%7D%2C%22title%22%3A%7B%22display%22%3Atrue%2C%22text%22%3A%22select%20one%20mysql.user%20using%20execute%5Cn%20%5B%20sql%3A%20select%20*%20from%20mysql.user%20LIMIT%201%20%5D%22%7D%2C%22scales%22%3A%7B%22xAxes%22%3A%5B%7B%22display%22%3Atrue%2C%22scaleLabel%22%3A%7B%22display%22%3Atrue%2C%22labelString%22%3A%22operations%20per%20second%22%7D%2C%22ticks%22%3A%7B%22beginAtZero%22%3Atrue%7D%7D%5D%7D%7D%7D" width="500" height="140"/> | ||
For more information, see the [Benchmarks](./documentation/benchmarks.md) page. | ||
@@ -142,2 +150,2 @@ ## Quick Start | ||
[codecov-image]:https://codecov.io/gh/mariadb-corporation/mariadb-connector-nodejs/branch/master/graph/badge.svg | ||
[codecov-url]:https://codecov.io/gh/mariadb-corporation/mariadb-connector-nodejs | ||
[codecov-url]:https://codecov.io/gh/mariadb-corporation/mariadb-connector-nodejs |
@@ -214,3 +214,3 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/** | ||
* The milliseconds before a timeout occurs during the initial connection to the MySQL server. (Default: 10 seconds) | ||
* The milliseconds before a timeout occurs during the initial connection to the MySQL server. (Default: 1000) | ||
*/ | ||
@@ -499,2 +499,7 @@ connectTimeout?: number; | ||
/** | ||
* connection collation | ||
*/ | ||
collation: null; | ||
/** | ||
* Server capabilities | ||
@@ -576,3 +581,3 @@ * see https://mariadb.com/kb/en/library/connection/#capabilities | ||
*/ | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult[]>; | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult> | Promise<UpsertResult[]>; | ||
@@ -679,3 +684,3 @@ /** | ||
*/ | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult[]>; | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult> | Promise<UpsertResult[]>; | ||
@@ -739,3 +744,3 @@ /** | ||
query(sql: string | QueryOptions, values?: any): Promise<any>; | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult[]>; | ||
batch(sql: string | QueryOptions, values?: any): Promise<UpsertResult> | Promise<UpsertResult[]>; | ||
execute(sql: string | QueryOptions, values?: any): Promise<any>; | ||
@@ -742,0 +747,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
515111
70
13709
147