@mysql/xdevapi
Version:
MySQL Connector/Node.js - A Node.js driver for MySQL using the X Protocol and X DevAPI.
266 lines (232 loc) • 8.64 kB
JavaScript
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0, as
* published by the Free Software Foundation.
*
* This program is also distributed with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an
* additional permission to link the program and your derivative works
* with the separately licensed software that they have included with
* MySQL.
*
* Without limiting anything contained in the foregoing, this file,
* which is part of MySQL Connector/Node.js, is also subject to the
* Universal FOSS Exception, version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
'use strict';
const errors = require('../constants/errors');
/**
* Enum the current statement lifecycle stage.
* @readonly
* @private
* @name STATEMENT_STATUS
* @enum {number}
*/
const STATEMENT_STATUS = {
TO_START: 1,
TO_PREPARE: 2,
TO_EXECUTE: 3,
TO_REPREPARE: 4,
TO_RESTART: 5,
TO_SKIP: 6
};
/**
* Preparing mixin.
* @mixin
* @private
* @alias Preparing
* @param {Object} state
* @returns {Preparing}
*/
function Preparing (state) {
state = Object.assign({ statementId: 0, stage: STATEMENT_STATUS.TO_START }, state);
return {
/**
* Acquire a statement context.
* @function
* @private
* @name Preparing#allocate
* @return {Preparing} The statement instance.
*/
allocate () {
const statements = state.connection.getPreparedStatements();
let id = 0;
while (statements[id]) {
++id;
}
statements[id] = true;
state.statementId = id + 1;
return this;
},
/**
* Release a previously prepared statement.
* @function
* @private
* @name Preparing#deallocate
* @return {Preparing} The statement instance.
*/
deallocate () {
return state.connection.getClient().deallocate(this)
.then(() => {
if (state.stage === STATEMENT_STATUS.TO_RESTART) {
state.stage = STATEMENT_STATUS.TO_START;
} else if (state.stage === STATEMENT_STATUS.TO_REPREPARE) {
state.stage = STATEMENT_STATUS.TO_PREPARE;
}
state.connection.removePreparedStatement(state.statementId);
return this;
});
},
/**
* Manage statement execution (prepared or plain) given the existing context.
* @function
* @private
* @name Preparing#execute
* @param {Function} fn - fallback function to execute when the server does not support prepared statements.
* @param {Function} [dataCursor] - callback function used to process result set data
* @param {Function} [metadataCursor] - callback function used to process result set metadata
* @returns {Promise.<Object>}
*/
execute (fn, dataCursor, metadataCursor) {
if (state.stage === STATEMENT_STATUS.TO_RESTART || state.stage === STATEMENT_STATUS.TO_REPREPARE) {
return this.deallocate().then(() => this.execute(fn, dataCursor, metadataCursor));
}
if (state.stage === STATEMENT_STATUS.TO_PREPARE) {
return this.prepare().then(() => this.execute(fn, dataCursor, metadataCursor)).catch(err => this.handlePrepareError(err, fn));
}
if (state.stage === STATEMENT_STATUS.TO_EXECUTE) {
return this.executePrepared(dataCursor, metadataCursor);
}
return this.executePlain(fn);
},
/**
* Execute a plain statement wrapped inside an operation factory function.
* @function
* @private
* @name Preparing#executePlain
* @param {Function} fn - fallback function
* @returns {Promise.<Object>}
*/
executePlain (fn) {
return fn()
.then(res => {
if (state.stage !== STATEMENT_STATUS.TO_SKIP) {
state.stage = STATEMENT_STATUS.TO_PREPARE;
}
return res;
});
},
/**
* Execute a previously prepared statement.
* @function
* @private
* @name Preparing#executePrepared
* @param {module:CollectionFind~documentCursor|module:TableSelect~rowCursor} [rowcb]
* @param {module:TableSelect~metadataCursor} [metacb]
* @return {Promise.<Object>}
*/
executePrepared (rowcb, metacb) {
return state.connection.getClient().prepareExecute(this, rowcb, metacb);
},
/**
* Force statement to be re-prepared in the next execution.
* @function
* @private
* @name Preparing#forceReprepare
* @return {Preparing}
*/
forceReprepare () {
if (state.stage === STATEMENT_STATUS.TO_EXECUTE) {
state.stage = STATEMENT_STATUS.TO_REPREPARE;
}
return this;
},
/**
* Force statement lifecycle to be restarted in the next execution.
* @function
* @private
* @name Preparing#forceRestart
* @return {Preparing}
*/
forceRestart () {
if (state.stage === STATEMENT_STATUS.TO_EXECUTE) {
state.stage = STATEMENT_STATUS.TO_RESTART;
} else {
state.stage = STATEMENT_STATUS.TO_START;
}
return this;
},
/**
* Get the current statement lifecycle stage.
* @function
* @private
* @name Preparing#getStage
* @returns {}
*/
getStage () {
return state.stage;
},
/**
* Retrieve the statement id.
* @function
* @private
* @name Preparing#getStatementId
* @returns {Number} The statement id.
*/
getStatementId () {
return state.statementId;
},
/**
* Execute plain statement if the server does not support prepared statements.
* @function
* @private
* @name Preparing#handlePrepareError
* @param {Error} err - error to evaluate
* @param {Function} fn - fallback function to execute when the server does not support prepared statements.
*/
handlePrepareError (err, fn) {
// Non-fatal errors include errors when max_prepared_stmt_count
// has been exceeded, or when the server does not support
// prepared statements and reports an unexpected message.
const nonFatalErrors = [errors.ER_UNKNOWN_COM_ERROR, errors.ER_MAX_PREPARED_STMT_COUNT_REACHED];
if (!err.info || nonFatalErrors.indexOf(err.info.code) === -1) {
// Needs to return a failing Promise
return Promise.reject(err);
}
state.connection.disablePreparedStatements();
state.stage = STATEMENT_STATUS.TO_SKIP;
return fn();
},
/**
* Prepare a statement in the server.
* @function
* @private
* @name Preparing#prepare
* @return {Preparing} The statement instance.
*/
prepare () {
this.allocate();
return state.connection.getClient().prepare(this)
.then(() => {
state.stage = STATEMENT_STATUS.TO_EXECUTE;
return this;
});
}
};
}
Preparing.Stages = STATEMENT_STATUS;
module.exports = Preparing;