Two Phase Commit support in Thin mode
This commit is contained in:
parent
4977cdeef4
commit
0d72c1b271
|
@ -315,11 +315,6 @@ The properties of a *Connection* object are listed below.
|
|||
This read/write attribute is a string that specifies the internal name
|
||||
that is used by the connection when logging two-phase commit transactions.
|
||||
|
||||
.. note::
|
||||
|
||||
This property can only be used in the node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
.. attribute:: connection.tpcExternalName
|
||||
|
||||
.. versionadded:: 5.3
|
||||
|
@ -327,11 +322,6 @@ The properties of a *Connection* object are listed below.
|
|||
This read/write attribute is a string that specifies the external name
|
||||
that is used by the connection when logging two-phase commit transactions.
|
||||
|
||||
.. note::
|
||||
|
||||
This property can only be used in the node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
.. attribute:: connection.transactionInProgress
|
||||
|
||||
.. versionadded:: 6.3
|
||||
|
@ -2343,11 +2333,6 @@ Connection Methods
|
|||
|
||||
See :ref:`Two-Phase Commits (TPC) <twopc>`.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
The parameters of the ``connection.tpcBegin()`` method are:
|
||||
|
||||
.. _tpcbegin:
|
||||
|
@ -2427,11 +2412,6 @@ Connection Methods
|
|||
ignored and ``tpcCommit()`` has the same behavior as a regular
|
||||
``connection.commit()`` call.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
Note: When using an external transaction manager with two-phase commits,
|
||||
:attr:`autocommitting <oracledb.autoCommit>` should be disabled.
|
||||
|
||||
|
@ -2497,11 +2477,6 @@ Connection Methods
|
|||
If ``xid`` is not passed, the transaction identifier used by the
|
||||
previous ``connection.tpcBegin()`` call is used.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
The parameters of the ``connection.tpcEnd()`` method are:
|
||||
|
||||
.. _tpcend:
|
||||
|
@ -2623,11 +2598,6 @@ Connection Methods
|
|||
|
||||
Returns a boolean indicating the transaction requires a commit.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
After calling this function, no further activity should take place on
|
||||
this connection until either
|
||||
:meth:`connection.tpcCommit()` or
|
||||
|
@ -2700,11 +2670,6 @@ Connection Methods
|
|||
(XIDs) suitable for use with :meth:`connection.tpcCommit()`
|
||||
or :meth:`connection.tpcRollback()`.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
This function is a convenience wrapper that queries the view
|
||||
``DBA_PENDING_TRANSACTIONS``. It requires SELECT privilege on that view.
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ Thin Mode Changes
|
|||
provided for `Issue #1565 <https://github.com/oracle/node-oracledb/issues/
|
||||
1565>`__.
|
||||
|
||||
#) Added :ref:`Two-Phase Commits <tpc>` support.
|
||||
|
||||
Thick Mode Changes
|
||||
+++++++++++++++++++
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ node-oracledb Thin and Thick modes. For more details see :ref:`modediff`.
|
|||
- No
|
||||
- Yes
|
||||
* - Two-phase Commit (TPC) (see :ref:`twopc`)
|
||||
- No
|
||||
- Yes
|
||||
- Yes
|
||||
* - REF CURSORs (see :ref:`refcursors`)
|
||||
- Yes
|
||||
|
|
|
@ -9,11 +9,6 @@ support distributed transactions. See `Two-Phase Commit Mechanism
|
|||
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-8152084F-4760
|
||||
-4B89-A91C-9A84F81C23D1>`__ in the Oracle Database documentation.
|
||||
|
||||
.. note::
|
||||
|
||||
In this release, TPC is only supported in node-oracledb Thick mode. See
|
||||
:ref:`enablingthick`.
|
||||
|
||||
Distributed transaction protocols attempt to keep multiple data sources
|
||||
consistent with one another by ensuring updates to the data sources
|
||||
participating in a distributed transaction are all performed, or none of
|
||||
|
|
|
@ -1412,7 +1412,7 @@ class Connection extends EventEmitter {
|
|||
async tpcBegin(xid, flag, timeout) {
|
||||
errors.assertArgCount(arguments, 1, 3);
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
|
||||
const normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
if (arguments.length < 3) {
|
||||
timeout = 60; // seconds
|
||||
} else {
|
||||
|
@ -1423,9 +1423,14 @@ class Connection extends EventEmitter {
|
|||
flag = constants.TPC_BEGIN_NEW;
|
||||
} else {
|
||||
errors.assertParamValue(typeof flag === 'number', 2);
|
||||
const options = [constants.TPC_BEGIN_NEW, constants.TPC_BEGIN_JOIN,
|
||||
constants.TPC_BEGIN_RESUME, constants.TPC_BEGIN_PROMOTE];
|
||||
if (options.indexOf(flag) < 0) {
|
||||
errors.throwErr(errors.ERR_INVALID_TPC_BEGIN_FLAGS);
|
||||
}
|
||||
}
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
await this._impl.tpcBegin(xid, flag, timeout);
|
||||
await this._impl.tpcBegin(normalizedXid, flag, timeout);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1441,11 +1446,13 @@ class Connection extends EventEmitter {
|
|||
} else {
|
||||
errors.assertParamValue(typeof onePhase === 'boolean', 2);
|
||||
}
|
||||
let normalizedXid;
|
||||
if (arguments.length >= 1) {
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
}
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
await this._impl.tpcCommit(xid, onePhase);
|
||||
await this._impl.tpcCommit(normalizedXid, onePhase);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1460,14 +1467,19 @@ class Connection extends EventEmitter {
|
|||
flag = constants.TPC_END_NORMAL;
|
||||
} else {
|
||||
errors.assertParamValue(typeof flag === 'number', 2);
|
||||
const options = [constants.TPC_END_NORMAL, constants.TPC_END_SUSPEND];
|
||||
if (!options.includes(flag)) {
|
||||
errors.throwErr(errors.ERR_INVALID_TPC_END_FLAGS);
|
||||
}
|
||||
}
|
||||
|
||||
let normalizedXid;
|
||||
if (arguments.length >= 1) {
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
}
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
|
||||
await this._impl.tpcEnd(xid, flag);
|
||||
await this._impl.tpcEnd(normalizedXid, flag);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1479,9 +1491,10 @@ class Connection extends EventEmitter {
|
|||
async tpcForget(xid) {
|
||||
errors.assertArgCount(arguments, 1, 1);
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
const normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
|
||||
await this._impl.tpcForget(xid);
|
||||
await this._impl.tpcForget(normalizedXid);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1491,12 +1504,14 @@ class Connection extends EventEmitter {
|
|||
//---------------------------------------------------------------------------
|
||||
async tpcPrepare(xid) {
|
||||
errors.assertArgCount(arguments, 0, 1);
|
||||
let normalizedXid;
|
||||
if (arguments.length >= 1) {
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
}
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
|
||||
return await this._impl.tpcPrepare(xid);
|
||||
return await this._impl.tpcPrepare(normalizedXid);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1541,12 +1556,14 @@ class Connection extends EventEmitter {
|
|||
//---------------------------------------------------------------------------
|
||||
async tpcRollback(xid) {
|
||||
errors.assertArgCount(arguments, 0, 1);
|
||||
let normalizedXid;
|
||||
if (arguments.length == 1) {
|
||||
errors.assertParamValue(nodbUtil.isXid(xid), 1);
|
||||
normalizedXid = nodbUtil.normalizeXid(xid);
|
||||
}
|
||||
errors.assert(this._impl, errors.ERR_INVALID_CONNECTION);
|
||||
|
||||
await this._impl.tpcRollback(xid);
|
||||
await this._impl.tpcRollback(normalizedXid);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -156,6 +156,11 @@ const ERR_VECTOR_VERSION_NOT_SUPPORTED = 145;
|
|||
const ERR_OBJECT_IS_NOT_A_COLLECTION = 146;
|
||||
const ERR_CURSOR_HAS_BEEN_CLOSED = 147;
|
||||
const ERR_DML_RETURNING_DUP_BINDS = 149;
|
||||
const ERR_INVALID_TPC_BEGIN_FLAGS = 150;
|
||||
const ERR_INVALID_TPC_END_FLAGS = 151;
|
||||
const ERR_UNKNOWN_TRANSACTION_STATE = 152;
|
||||
const ERR_INVALID_TRANSACTION_SIZE = 153;
|
||||
const ERR_INVALID_BRANCH_SIZE = 154;
|
||||
|
||||
// Oracle Net layer errors start from 500
|
||||
const ERR_CONNECTION_CLOSED = 500;
|
||||
|
@ -441,6 +446,16 @@ messages.set(ERR_CURSOR_HAS_BEEN_CLOSED, // NJS-147
|
|||
'cursor has been closed by the database');
|
||||
messages.set(ERR_DML_RETURNING_DUP_BINDS, // NJS-149
|
||||
'the bind variable placeholder "%s" cannot be used both before and after the RETURNING clause in a DML RETURNING statement');
|
||||
messages.set(ERR_INVALID_TPC_BEGIN_FLAGS, // NJS-150
|
||||
'invalid flags for tpcBegin() in Two Phase Commit');
|
||||
messages.set(ERR_INVALID_TPC_END_FLAGS, // NJS-151
|
||||
'invalid flags for tpcEnd() in Two Phase Commit');
|
||||
messages.set(ERR_UNKNOWN_TRANSACTION_STATE, // NJS-152
|
||||
'internal error: unknown transaction state {state} in Two Phase Commit');
|
||||
messages.set(ERR_INVALID_TRANSACTION_SIZE, // NJS-153
|
||||
'size of the transaction ID is %d and cannot exceed 64');
|
||||
messages.set(ERR_INVALID_BRANCH_SIZE, // NJS-154
|
||||
'size of the branch ID is %d and cannot exceed 64');
|
||||
|
||||
// Oracle Net layer errors
|
||||
|
||||
|
@ -868,6 +883,11 @@ module.exports = {
|
|||
ERR_OBJECT_IS_NOT_A_COLLECTION,
|
||||
ERR_CURSOR_HAS_BEEN_CLOSED,
|
||||
ERR_DML_RETURNING_DUP_BINDS,
|
||||
ERR_INVALID_TPC_BEGIN_FLAGS,
|
||||
ERR_INVALID_TPC_END_FLAGS,
|
||||
ERR_UNKNOWN_TRANSACTION_STATE,
|
||||
ERR_INVALID_TRANSACTION_SIZE,
|
||||
ERR_INVALID_BRANCH_SIZE,
|
||||
ERR_CONNECTION_CLOSED_CODE: `${ERR_PREFIX}-${ERR_CONNECTION_CLOSED}`,
|
||||
WRN_COMPILATION_CREATE,
|
||||
assert,
|
||||
|
|
|
@ -60,7 +60,13 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
async close() {
|
||||
try {
|
||||
if (this._protocol.txnInProgress) {
|
||||
await this.rollback();
|
||||
if (this.tpcContext) {
|
||||
const message = this.createTpcRollbackMessage();
|
||||
await this._protocol._processMessage(message);
|
||||
} else {
|
||||
await this.rollback();
|
||||
}
|
||||
this.tpcContext = null;
|
||||
}
|
||||
if (this._drcpEnabled) {
|
||||
await this._sessRelease();
|
||||
|
@ -730,6 +736,7 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
this.serviceName = '';
|
||||
this.remoteAddress = '';
|
||||
this.comboKey = null; // used in changePassword API
|
||||
this.tpcContext = null;
|
||||
|
||||
this.nscon = new nsi();
|
||||
finalizationRegistry.register(this, this.nscon);
|
||||
|
@ -1045,6 +1052,14 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
this._dbOp = dbOp;
|
||||
}
|
||||
|
||||
setExternalName(value) {
|
||||
this.externalName = value;
|
||||
}
|
||||
|
||||
setInternalName(value) {
|
||||
this.internalName = value;
|
||||
}
|
||||
|
||||
setClientInfo(clientInfo) {
|
||||
this._clientInfoModified = true;
|
||||
this._clientInfo = clientInfo;
|
||||
|
@ -1083,6 +1098,102 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
return lobImpl;
|
||||
}
|
||||
|
||||
// Check the state returned by the tpcCommit() call.
|
||||
checkTpcCommitState(state, onePhase) {
|
||||
if ((onePhase && state !== constants.TNS_TPC_TXN_STATE_READ_ONLY
|
||||
&& state !== constants.TNS_TPC_TXN_STATE_COMMITTED) ||
|
||||
(!onePhase && state !== constants.TNS_TPC_TXN_STATE_FORGOTTEN)) {
|
||||
errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a two-phase commit message suitable for committing a transaction.
|
||||
createTpcCommitMessage(xid, onePhase) {
|
||||
const message = new messages.TransactionChangeStateMessage(this);
|
||||
message.operation = constants.TNS_TPC_TXN_COMMIT;
|
||||
message.state = (onePhase == 0) ? constants.TNS_TPC_TXN_STATE_COMMITTED :
|
||||
constants.TNS_TPC_TXN_STATE_READ_ONLY;
|
||||
message.xid = xid;
|
||||
message.context = this.tpcContext;
|
||||
return message;
|
||||
}
|
||||
|
||||
// Creates a two-phase commit rollback message suitable for use in both
|
||||
// the close() method and explicitly by the user.
|
||||
createTpcRollbackMessage(xid = null) {
|
||||
const message = new messages.TransactionChangeStateMessage(this);
|
||||
message.operation = constants.TNS_TPC_TXN_ABORT;
|
||||
message.state = constants.TNS_TPC_TXN_STATE_ABORTED;
|
||||
message.xid = xid;
|
||||
message.context = this.tpcContext;
|
||||
return message;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// tpcBegin()
|
||||
//---------------------------------------------------------------------------
|
||||
async tpcBegin(xid, flags, timeout) {
|
||||
const message = new messages.TransactionSwitchMessage(this);
|
||||
message.operation = constants.TNS_TPC_TXN_START;
|
||||
message.xid = xid;
|
||||
message.flags = flags;
|
||||
message.timeout = timeout;
|
||||
await this._protocol._processMessage(message);
|
||||
this.tpcContext = message.context;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// tpcCommit()
|
||||
//---------------------------------------------------------------------------
|
||||
async tpcCommit(xid, onePhase) {
|
||||
const message = this.createTpcCommitMessage(xid, onePhase);
|
||||
await this._protocol._processMessage(message);
|
||||
this.checkTpcCommitState(message.state, onePhase);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// tpcEnd()
|
||||
//---------------------------------------------------------------------------
|
||||
async tpcEnd(xid, flags) {
|
||||
const message = new messages.TransactionSwitchMessage(this);
|
||||
message.operation = constants.TNS_TPC_TXN_DETACH;
|
||||
message.xid = xid;
|
||||
message.context = this.tpcContext;
|
||||
message.flags = flags;
|
||||
await this._protocol._processMessage(message);
|
||||
this.tpcContext = null;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// tpcPrepare()
|
||||
//---------------------------------------------------------------------------
|
||||
async tpcPrepare(xid) {
|
||||
const message = new messages.TransactionChangeStateMessage(this);
|
||||
message.operation = constants.TNS_TPC_TXN_PREPARE;
|
||||
message.xid = xid;
|
||||
message.context = this.tpcContext;
|
||||
await this._protocol._processMessage(message);
|
||||
if (message.state === constants.TNS_TPC_TXN_STATE_REQUIRES_COMMIT) {
|
||||
return true;
|
||||
} else if (message.state === constants.TNS_TPC_TXN_STATE_READ_ONLY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, message.state);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// tpcRollback()
|
||||
//---------------------------------------------------------------------------
|
||||
async tpcRollback(xid) {
|
||||
const message = this.createTpcRollbackMessage(xid);
|
||||
await this._protocol._processMessage(message);
|
||||
if (message.state !== constants.TNS_TPC_TXN_STATE_ABORTED) {
|
||||
errors.throwErr(errors.ERR_UNKNOWN_TRANSACTION_STATE, message.state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Returns the statement cache size for the statement cache maintained by
|
||||
// the connection object
|
||||
|
@ -1106,6 +1217,10 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
return '';
|
||||
}
|
||||
|
||||
getExternalName() {
|
||||
return this.externalName;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Returns the Oracle Database instance name associated with the connection.
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -1113,6 +1228,10 @@ class ThinConnectionImpl extends ConnectionImpl {
|
|||
return this.instanceName;
|
||||
}
|
||||
|
||||
getInternalName() {
|
||||
return this.internalName;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Returns the Oracle Database domain name associated with the connection.
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -548,6 +548,8 @@ module.exports = {
|
|||
TNS_FUNC_SESSION_RELEASE: 163,
|
||||
TNS_FUNC_SESSION_STATE: 176, // piggyback fn
|
||||
TNS_FUNC_CANCEL_ALL: 120, // piggyback fn
|
||||
TNS_FUNC_TPC_TXN_SWITCH: 103,
|
||||
TNS_FUNC_TPC_TXN_CHANGE_STATE: 104,
|
||||
|
||||
// character sets and encodings
|
||||
TNS_CHARSET_UTF8: 873,
|
||||
|
@ -660,6 +662,24 @@ module.exports = {
|
|||
TNS_EOCS_FLAGS_TXN_IN_PROGRESS: 0x00000002,
|
||||
TNS_EOCS_FLAGS_SESS_RELEASE: 0x00008000,
|
||||
|
||||
// transaction switching op codes
|
||||
TNS_TPC_TXN_START: 0x01,
|
||||
TNS_TPC_TXN_DETACH: 0x02,
|
||||
|
||||
// transaction change state op codes
|
||||
TNS_TPC_TXN_COMMIT: 0x01,
|
||||
TNS_TPC_TXN_ABORT: 0x02,
|
||||
TNS_TPC_TXN_PREPARE: 0x03,
|
||||
TNS_TPC_TXN_FORGET: 0x04,
|
||||
|
||||
// transaction states
|
||||
TNS_TPC_TXN_STATE_PREPARE: 0,
|
||||
TNS_TPC_TXN_STATE_REQUIRES_COMMIT: 1,
|
||||
TNS_TPC_TXN_STATE_COMMITTED: 2,
|
||||
TNS_TPC_TXN_STATE_ABORTED: 3,
|
||||
TNS_TPC_TXN_STATE_READ_ONLY: 4,
|
||||
TNS_TPC_TXN_STATE_FORGOTTEN: 5,
|
||||
|
||||
// other constants
|
||||
TNS_ESCAPE_CHAR: 253,
|
||||
TNS_LONG_LENGTH_INDICATOR: dataHandlerConstants.TNS_LONG_LENGTH_INDICATOR,
|
||||
|
|
|
@ -38,6 +38,8 @@ const ProtocolMessage = require('./protocol.js');
|
|||
const RollbackMessage = require('./rollback.js');
|
||||
const SessionReleaseMessage = require('./sessionRelease.js');
|
||||
const FastAuthMessage = require('./fastAuth.js');
|
||||
const TransactionChangeStateMessage = require('./transactionChangeState.js');
|
||||
const TransactionSwitchMessage = require('./transactionSwitch.js');
|
||||
|
||||
module.exports = {
|
||||
AuthMessage,
|
||||
|
@ -51,5 +53,7 @@ module.exports = {
|
|||
PingMessage,
|
||||
ProtocolMessage,
|
||||
RollbackMessage,
|
||||
SessionReleaseMessage
|
||||
SessionReleaseMessage,
|
||||
TransactionChangeStateMessage,
|
||||
TransactionSwitchMessage
|
||||
};
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This software is dual-licensed to you under the Universal Permissive License
|
||||
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
// either license.
|
||||
//
|
||||
// If you elect to accept the software under the Apache License, Version 2.0,
|
||||
// the following applies:
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'use strict';
|
||||
|
||||
const Message = require("./base.js");
|
||||
const constants = require("../constants.js");
|
||||
|
||||
/**
|
||||
* Used for two-phase commit (TPC) transaction change state: commit, rollback,
|
||||
* forget, etc.
|
||||
*
|
||||
* @class TransactionChangeStateMessage
|
||||
* @extends {Message}
|
||||
*/
|
||||
class TransactionChangeStateMessage extends Message {
|
||||
|
||||
constructor(connImpl) {
|
||||
super(connImpl);
|
||||
this.functionCode = constants.TNS_FUNC_TPC_TXN_CHANGE_STATE;
|
||||
}
|
||||
|
||||
processReturnParameter(buf) {
|
||||
// process the parameters returned by the datatabase
|
||||
this.state = buf.readUB4();
|
||||
}
|
||||
|
||||
encode(buf) {
|
||||
// writes the message to the database
|
||||
// acquire data to send to the server
|
||||
let xidBytes;
|
||||
if (this.xid) {
|
||||
xidBytes = Buffer.alloc(128);
|
||||
this.xid.globalTransactionId.copy(xidBytes);
|
||||
this.xid.branchQualifier.copy(xidBytes,
|
||||
this.xid.globalTransactionId.length);
|
||||
}
|
||||
|
||||
this.writeFunctionHeader(buf);
|
||||
buf.writeUB4(this.operation);
|
||||
if (this.context) {
|
||||
buf.writeUInt8(1);
|
||||
buf.writeUB4(this.context.length);
|
||||
} else {
|
||||
buf.writeUInt8(0);
|
||||
buf.writeUB4(0);
|
||||
}
|
||||
|
||||
if (this.xid) {
|
||||
buf.writeUB4(this.xid.formatId);
|
||||
buf.writeUB4(this.xid.globalTransactionId.length);
|
||||
buf.writeUB4(this.xid.branchQualifier.length);
|
||||
buf.writeUInt8(1);
|
||||
buf.writeUB4(xidBytes.length);
|
||||
} else {
|
||||
buf.writeUB4(0);
|
||||
buf.writeUB4(0);
|
||||
buf.writeUB4(0);
|
||||
buf.writeUInt8(0);
|
||||
buf.writeUB4(0);
|
||||
}
|
||||
buf.writeUB4(0);
|
||||
buf.writeUB4(this.state);
|
||||
buf.writeUInt8(1);
|
||||
buf.writeUB4(this.flags);
|
||||
|
||||
if (this.context) {
|
||||
buf.writeBytes(this.context);
|
||||
}
|
||||
if (this.xid) {
|
||||
buf.writeBytes(xidBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TransactionChangeStateMessage;
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) 2024, Oracle and/or its affiliates.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This software is dual-licensed to you under the Universal Permissive License
|
||||
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||
// either license.
|
||||
//
|
||||
// If you elect to accept the software under the Apache License, Version 2.0,
|
||||
// the following applies:
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'use strict';
|
||||
|
||||
const Message = require("./base.js");
|
||||
const constants = require("../constants.js");
|
||||
|
||||
/**
|
||||
* Used for two-phase commit (TPC) transaction start, attach and detach.
|
||||
*
|
||||
* @class TransactionSwitchMessage
|
||||
* @extends {Message}
|
||||
*/
|
||||
class TransactionSwitchMessage extends Message {
|
||||
|
||||
constructor(connImpl) {
|
||||
super(connImpl);
|
||||
this.functionCode = constants.TNS_FUNC_TPC_TXN_SWITCH;
|
||||
this.applicationValue = 0;
|
||||
this.internalName = connImpl.internalName;
|
||||
if (this.internalName) {
|
||||
this.internalName = Buffer.from(this.internalName);
|
||||
}
|
||||
this.externalName = connImpl.externalName;
|
||||
if (this.externalName) {
|
||||
this.externalName = Buffer.from(this.externalName);
|
||||
}
|
||||
}
|
||||
|
||||
processReturnParameter(buf) {
|
||||
// process the parameters returned by the datatabase
|
||||
this.applicationValue = buf.readUB4();
|
||||
this.contextLen = buf.readUB2();
|
||||
if (this.contextLen > 0) {
|
||||
this.context = Buffer.from(buf.readBytes(this.contextLen));
|
||||
}
|
||||
}
|
||||
|
||||
encode(buf) {
|
||||
// writes the message to the database
|
||||
// acquire data to send to the server
|
||||
let xidBytes;
|
||||
if (this.xid) {
|
||||
xidBytes = Buffer.alloc(128);
|
||||
this.xid.globalTransactionId.copy(xidBytes);
|
||||
this.xid.branchQualifier.copy(xidBytes,
|
||||
this.xid.globalTransactionId.length);
|
||||
}
|
||||
|
||||
// write message
|
||||
this.writeFunctionHeader(buf);
|
||||
buf.writeUB4(this.operation);
|
||||
if (this.context) {
|
||||
buf.writeUInt8(1); // pointer (transaction context)
|
||||
buf.writeUB4(this.context.length);
|
||||
} else {
|
||||
buf.writeUInt8(0); // pointer (transaction context)
|
||||
buf.writeUB4(0); // transaction context length
|
||||
}
|
||||
|
||||
if (this.xid) {
|
||||
buf.writeUB4(this.xid.formatId);
|
||||
buf.writeUB4(this.xid.globalTransactionId.length);
|
||||
buf.writeUB4(this.xid.branchQualifier.length);
|
||||
buf.writeUInt8(1); // pointer (XID)
|
||||
buf.writeUB4(xidBytes.length);
|
||||
} else {
|
||||
buf.writeUB4(0); // format id
|
||||
buf.writeUB4(0); // global transaction id length
|
||||
buf.writeUB4(0); // branch qualifier length
|
||||
buf.writeUInt8(0); // pointer (XID)
|
||||
buf.writeUB4(0); // XID length
|
||||
}
|
||||
|
||||
buf.writeUB4(this.flags);
|
||||
buf.writeUB4(this.timeout);
|
||||
buf.writeUInt8(1); // pointer (application value)
|
||||
buf.writeUInt8(1); // pointer (return context)
|
||||
buf.writeUInt8(1); // pointer (return context length)
|
||||
|
||||
if (this.internalName) {
|
||||
buf.writeUInt8(1); // pointer (internal name)
|
||||
buf.writeUB4(this.internalName.length);
|
||||
} else {
|
||||
buf.writeUInt8(0); // pointer (internal name)
|
||||
buf.writeUB4(0); // length of internal name
|
||||
}
|
||||
|
||||
if (this.externalName) {
|
||||
buf.writeUInt8(1); // pointer (external name)
|
||||
buf.writeUB4(this.externalName.length);
|
||||
} else {
|
||||
buf.writeUInt8(0); // pointer (external name)
|
||||
buf.writeUB4(0); // length of external name
|
||||
}
|
||||
|
||||
if (this.context) {
|
||||
buf.writeBytes(this.context);
|
||||
}
|
||||
if (this.xid) {
|
||||
buf.writeBytes(xidBytes);
|
||||
}
|
||||
buf.writeUB4(this.applicationValue);
|
||||
|
||||
if (this.internalName) {
|
||||
buf.writeBytes(this.internalName);
|
||||
}
|
||||
|
||||
if (this.externalName) {
|
||||
buf.writeBytes(this.externalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TransactionSwitchMessage;
|
31
lib/util.js
31
lib/util.js
|
@ -277,6 +277,36 @@ function isXid(value) {
|
|||
typeof value.branchQualifier === 'string'));
|
||||
}
|
||||
|
||||
function normalizeXid(value) {
|
||||
let normalizedXid;
|
||||
if (Buffer.isBuffer(value.globalTransactionId) &&
|
||||
Buffer.isBuffer(value.branchQualifier)) {
|
||||
normalizedXid = value;
|
||||
} else {
|
||||
normalizedXid = {
|
||||
formatId: value.formatId,
|
||||
globalTransactionId: value.globalTransactionId,
|
||||
branchQualifier: value.branchQualifier
|
||||
};
|
||||
if (typeof value.globalTransactionId === 'string') {
|
||||
normalizedXid.globalTransactionId = Buffer.from(value.globalTransactionId);
|
||||
}
|
||||
if (typeof value.branchQualifier === 'string') {
|
||||
normalizedXid.branchQualifier = Buffer.from(value.branchQualifier);
|
||||
}
|
||||
}
|
||||
if (normalizedXid.globalTransactionId.length > 64) {
|
||||
errors.throwErr(errors.ERR_INVALID_TRANSACTION_SIZE,
|
||||
normalizedXid.globalTransactionId.length);
|
||||
}
|
||||
if (normalizedXid.branchQualifier.length > 64) {
|
||||
errors.throwErr(errors.ERR_INVALID_BRANCH_SIZE,
|
||||
normalizedXid.branchQualifier.length);
|
||||
}
|
||||
|
||||
return normalizedXid;
|
||||
}
|
||||
|
||||
function verifySodaDoc(content) {
|
||||
if (isSodaDocument(content))
|
||||
return content._impl;
|
||||
|
@ -403,6 +433,7 @@ module.exports = {
|
|||
isTokenValid,
|
||||
isVectorValue,
|
||||
isXid,
|
||||
normalizeXid,
|
||||
makeDate,
|
||||
preventConcurrent,
|
||||
serialize,
|
||||
|
|
57
test/tpc.js
57
test/tpc.js
|
@ -81,8 +81,6 @@ describe('259. tpc.js', function() {
|
|||
END;`;
|
||||
|
||||
before(async function() {
|
||||
if (oracledb.thin)
|
||||
return this.skip();
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
await conn.execute(sql);
|
||||
|
||||
|
@ -234,6 +232,9 @@ describe('259. tpc.js', function() {
|
|||
});
|
||||
|
||||
it('259.2.8 negative tpcForget after tpcPrepare', async function() {
|
||||
if (oracledb.thin) {
|
||||
this.skip();
|
||||
}
|
||||
const xid = {
|
||||
formatId: 3998,
|
||||
globalTransactionId: "txn3998",
|
||||
|
@ -258,6 +259,9 @@ describe('259. tpc.js', function() {
|
|||
});
|
||||
|
||||
it('259.2.9 negative tpcForget without tpcPrepare', async function() {
|
||||
if (oracledb.thin) {
|
||||
this.skip();
|
||||
}
|
||||
const xid = {
|
||||
formatId: 3999,
|
||||
globalTransactionId: "txn3999",
|
||||
|
@ -289,12 +293,14 @@ describe('259. tpc.js', function() {
|
|||
};
|
||||
|
||||
await conn.tpcBegin(xid, oracledb.TPC_BEGIN_NEW, 60);
|
||||
await conn.execute(`INSERT INTO TBL_259_2 VALUES (101, 'test#1')`);
|
||||
await conn.tpcPrepare(xid);
|
||||
const promise = dbaConn.tpcRecover();
|
||||
const res = await Promise.resolve(promise);
|
||||
assert.strictEqual(res[0].formatId, 5000);
|
||||
assert.strictEqual(res[0].globalTransactionId, "txn5000");
|
||||
assert.strictEqual(res[0].branchQualifier, "branchId");
|
||||
await conn.tpcCommit(res[0]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -313,8 +319,6 @@ describe('259. tpc.js', function() {
|
|||
END;`;
|
||||
|
||||
before(async function() {
|
||||
if (oracledb.thin)
|
||||
return this.skip();
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
await conn.execute(sql);
|
||||
});
|
||||
|
@ -461,8 +465,6 @@ describe('259. tpc.js', function() {
|
|||
let conn = null;
|
||||
|
||||
before(async function() {
|
||||
if (oracledb.thin)
|
||||
return this.skip();
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
});
|
||||
|
||||
|
@ -487,6 +489,9 @@ describe('259. tpc.js', function() {
|
|||
});
|
||||
|
||||
it('259.4.3 set and query ecid', async function() {
|
||||
if (oracledb.thin) {
|
||||
this.skip();
|
||||
}
|
||||
const sql = `SELECT USERNAME, SID, OSUSER, ECID FROM V$SESSION
|
||||
WHERE SID = :1`;
|
||||
|
||||
|
@ -518,8 +523,6 @@ describe('259. tpc.js', function() {
|
|||
END;`;
|
||||
|
||||
before(async function() {
|
||||
if (oracledb.thin)
|
||||
return this.skip();
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
await conn.execute(sql);
|
||||
});
|
||||
|
@ -586,8 +589,6 @@ describe('259. tpc.js', function() {
|
|||
};
|
||||
|
||||
before(async function() {
|
||||
if (oracledb.thin)
|
||||
return this.skip();
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
});
|
||||
|
||||
|
@ -645,4 +646,40 @@ describe('259. tpc.js', function() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('259.7 XID parameters validation', function() {
|
||||
let conn = null;
|
||||
before(async function() {
|
||||
conn = await oracledb.getConnection(dbConfig);
|
||||
});
|
||||
|
||||
after (async function() {
|
||||
if (conn)
|
||||
await conn.close();
|
||||
});
|
||||
|
||||
it('259.6.1 globalTransactionId size greater than 64', async function() {
|
||||
const xid = {
|
||||
formatId: 979797,
|
||||
globalTransactionId: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
||||
branchQualifier: "brancId1"
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await conn.tpcBegin(xid, oracledb.TPC_BEGIN_NEW, 60),
|
||||
/NJS-153/
|
||||
);
|
||||
});
|
||||
|
||||
it('259.6.2 branchQualifier invalid number of arguments', async function() {
|
||||
const xid = {
|
||||
formatId: 979797,
|
||||
globalTransactionId: "txn9999",
|
||||
branchQualifier: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await conn.tpcBegin(xid, oracledb.TPC_BEGIN_NEW, 60),
|
||||
/NJS-154/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue