171 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| //  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;
 |