Two Phase Commit support in Thin mode

This commit is contained in:
Sharad Chandran R 2024-07-15 13:03:52 +05:30
parent 4977cdeef4
commit 0d72c1b271
13 changed files with 509 additions and 61 deletions

View File

@ -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.

View File

@ -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
+++++++++++++++++++

View File

@ -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

View File

@ -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

View File

@ -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);
}
//---------------------------------------------------------------------------

View File

@ -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,

View File

@ -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.
//---------------------------------------------------------------------------

View File

@ -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,

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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/
);
});
});
});