// SPDX-License-Identifier: LGPL-2.1-or-later // Copyright (c) 2015-2024 MariaDB Corporation Ab 'use strict'; const Parser = require('./parser'); const Parse = require('../misc/parse'); const BinaryEncoder = require('./encoder/binary-encoder'); const PrepareCacheWrapper = require('./class/prepare-cache-wrapper'); const PrepareResult = require('./class/prepare-result-packet'); const ServerStatus = require('../const/server-status'); const Errors = require('../misc/errors'); const ColumnDefinition = require('./column-definition'); /** * send a COM_STMT_PREPARE: permits sending a prepare packet * see https://mariadb.com/kb/en/com_stmt_prepare/ */ class Prepare extends Parser { constructor(resolve, reject, connOpts, cmdParam, conn) { super(resolve, reject, connOpts, cmdParam); this.encoder = new BinaryEncoder(this.opts); this.binary = true; this.conn = conn; this.executeCommand = cmdParam.executeCommand; } /** * Send COM_STMT_PREPARE * * @param out output writer * @param opts connection options * @param info connection information */ start(out, opts, info) { // check in cache if enabled if (this.conn.prepareCache) { let cachedPrepare = this.conn.prepareCache.get(this.sql); if (cachedPrepare) { this.emit('send_end'); return this.successEnd(cachedPrepare); } } if (opts.logger.query) opts.logger.query(`PREPARE: ${this.sql}`); this.onPacketReceive = this.readPrepareResultPacket; if (this.opts.namedPlaceholders) { const res = Parse.searchPlaceholder(this.sql); this.sql = res.sql; this.placeHolderIndex = res.placeHolderIndex; } out.startPacket(this); out.writeInt8(0x16); out.writeString(this.sql); out.flush(); this.emit('send_end'); } successPrepare(info, opts) { let prepare = new PrepareResult( this.statementId, this.parameterCount, this._columns, info.database, this.sql, this.placeHolderIndex, this.conn ); if (this.conn.prepareCache) { let cached = new PrepareCacheWrapper(prepare); this.conn.prepareCache.set(this.sql, cached); const cachedWrappedPrepared = cached.incrementUse(); if (this.executeCommand) this.executeCommand.prepare = cachedWrappedPrepared; return this.successEnd(cachedWrappedPrepared); } if (this.executeCommand) this.executeCommand.prepare = prepare; this.successEnd(prepare); } /** * Read COM_STMT_PREPARE response Packet. * see https://mariadb.com/kb/en/library/com_stmt_prepare/#com_stmt_prepare-response * * @param packet COM_STMT_PREPARE_OK packet * @param opts connection options * @param info connection information * @param out output writer * @returns {*} null or {Result.readResponsePacket} in case of multi-result-set */ readPrepareResultPacket(packet, out, opts, info) { switch (packet.peek()) { //********************************************************************************************************* //* PREPARE response //********************************************************************************************************* case 0x00: packet.skip(1); //skip header this.statementId = packet.readInt32(); this.columnNo = packet.readUInt16(); this.parameterCount = packet.readUInt16(); this._parameterNo = this.parameterCount; this._columns = []; if (this._parameterNo > 0) return (this.onPacketReceive = this.skipPrepareParameterPacket); if (this.columnNo > 0) return (this.onPacketReceive = this.readPrepareColumnsPacket); return this.successPrepare(info, opts); //********************************************************************************************************* //* ERROR response //********************************************************************************************************* case 0xff: const err = packet.readError(info, this.displaySql(), this.stack); //force in transaction status, since query will have created a transaction if autocommit is off //goal is to avoid unnecessary COMMIT/ROLLBACK. info.status |= ServerStatus.STATUS_IN_TRANS; this.onPacketReceive = this.readResponsePacket; return this.throwError(err, info); //********************************************************************************************************* //* Unexpected response //********************************************************************************************************* default: info.status |= ServerStatus.STATUS_IN_TRANS; this.onPacketReceive = this.readResponsePacket; return this.throwError(Errors.ER_UNEXPECTED_PACKET, info); } } readPrepareColumnsPacket(packet, out, opts, info) { this.columnNo--; this._columns.push(new ColumnDefinition(packet, info, opts.rowsAsArray)); if (this.columnNo === 0) { if (info.eofDeprecated) { return this.successPrepare(info, opts); } this.onPacketReceive = this.skipEofPacket; } } skipEofPacket(packet, out, opts, info) { if (this.columnNo > 0) return (this.onPacketReceive = this.readPrepareColumnsPacket); this.successPrepare(info, opts); } skipPrepareParameterPacket(packet, out, opts, info) { this._parameterNo--; if (this._parameterNo === 0) { if (info.eofDeprecated) { if (this.columnNo > 0) return (this.onPacketReceive = this.readPrepareColumnsPacket); return this.successPrepare(info, opts); } this.onPacketReceive = this.skipEofPacket; } } /** * Display current SQL with parameters (truncated if too big) * * @returns {string} */ displaySql() { if (this.opts) { if (this.sql.length > this.opts.debugLen) { return this.sql.substring(0, this.opts.debugLen) + '...'; } } return this.sql; } } module.exports = Prepare;