Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLIENT-3181: Add Multi-Record Transactions #645

Merged
merged 21 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/
npm run test

test-lowest-supported-server:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -278,7 +278,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/ -- --t 20000
npm run test -- --t 20000

test-ee:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -335,7 +335,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/admin.js -- --h localhost --U admin --P admin --t 40000
npm run test -- --h localhost --U superuser --P superuser --t 40000

test-valgrind:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## [5.13.2]
* **Bug Fixes**
* [CLIENT-3155] - Fixed typescript compilation by removing the protected modifier from the ExpOperation class.

## [5.13.1]

* **New Features**
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ To run all the test cases:

npm test

To run a specific tests, use:

npm test --testfile=filename.js

Note: make sure your server has TTL enabled for the `test` namespace ([Namespace Retention Configuration](https://docs.aerospike.com/server/operations/configure/namespace/retention)) to allow all tests to run correctly.

To run the tests and also report on test coverage:
Expand Down
2 changes: 1 addition & 1 deletion aerospike-client-c
Submodule aerospike-client-c updated 94 files
+3 −0 Makefile
+2 −2 README.md
+1 −0 examples/async_examples/Makefile
+3 −3 examples/async_examples/async_batch_get/src/main/example.c
+2 −2 examples/async_examples/async_delay_queue/src/main/example.c
+4 −0 examples/async_examples/async_transaction/Makefile
+329 −0 examples/async_examples/async_transaction/src/main/example.c
+1 −0 examples/basic_examples/Makefile
+2 −2 examples/basic_examples/append/src/main/example.c
+2 −2 examples/basic_examples/incr/src/main/example.c
+4 −0 examples/basic_examples/transaction/Makefile
+144 −0 examples/basic_examples/transaction/src/main/example.c
+3 −3 examples/batch_examples/get/src/main/example.c
+ project/aerospike_logo.png
+12 −12 project/doxyfile
+116 −0 project/doxygen-awesome-sidebar-only.css
+2,681 −0 project/doxygen-awesome.css
+84 −0 project/header.html
+0 −0 project/layout.xml
+2 −0 project/test.mk
+0 −14 src/apidocs/footer.html
+0 −47 src/apidocs/header.html
+0 −716 src/apidocs/html/aerospike.css
+0 −563 src/apidocs/html/style.css
+0 −1,204 src/apidocs/old.css
+6 −6 src/include/aerospike/aerospike.h
+70 −30 src/include/aerospike/aerospike_batch.h
+93 −6 src/include/aerospike/aerospike_key.h
+6 −6 src/include/aerospike/aerospike_stats.h
+227 −0 src/include/aerospike/aerospike_txn.h
+3 −3 src/include/aerospike/as_admin.h
+21 −8 src/include/aerospike/as_async.h
+1 −1 src/include/aerospike/as_batch.h
+23 −12 src/include/aerospike/as_cluster.h
+124 −21 src/include/aerospike/as_command.h
+5 −5 src/include/aerospike/as_config.h
+2 −2 src/include/aerospike/as_error.h
+21 −16 src/include/aerospike/as_event.h
+27 −2 src/include/aerospike/as_event_internal.h
+3 −3 src/include/aerospike/as_exp.h
+1 −1 src/include/aerospike/as_latency.h
+11 −11 src/include/aerospike/as_node.h
+1 −1 src/include/aerospike/as_operations.h
+2 −1 src/include/aerospike/as_peers.h
+184 −45 src/include/aerospike/as_policy.h
+3 −3 src/include/aerospike/as_proto.h
+7 −1 src/include/aerospike/as_query.h
+2 −2 src/include/aerospike/as_record.h
+2 −2 src/include/aerospike/as_socket.h
+62 −21 src/include/aerospike/as_status.h
+308 −0 src/include/aerospike/as_txn.h
+95 −0 src/include/aerospike/as_txn_monitor.h
+1 −1 src/include/aerospike/version.h
+1,455 −287 src/main/aerospike/aerospike_batch.c
+1,093 −249 src/main/aerospike/aerospike_key.c
+13 −5 src/main/aerospike/aerospike_query.c
+7 −4 src/main/aerospike/aerospike_scan.c
+740 −0 src/main/aerospike/aerospike_txn.c
+9 −9 src/main/aerospike/as_cluster.c
+200 −31 src/main/aerospike/as_command.c
+12 −4 src/main/aerospike/as_error.c
+176 −16 src/main/aerospike/as_event.c
+1 −1 src/main/aerospike/as_event_uv.c
+27 −19 src/main/aerospike/as_info.c
+2 −2 src/main/aerospike/as_metrics_writer.c
+3 −3 src/main/aerospike/as_node.c
+94 −40 src/main/aerospike/as_peers.c
+8 −4 src/main/aerospike/as_policy.c
+412 −0 src/main/aerospike/as_txn.c
+349 −0 src/main/aerospike/as_txn_monitor.c
+1 −1 src/main/aerospike/version.c
+2 −2 src/test/aerospike_query/query_background.c
+31 −14 src/test/aerospike_test.c
+924 −0 src/test/transaction.c
+1,157 −0 src/test/transaction_async.c
+2 −2 src/test/util/map_rec.c
+2 −2 src/test/util/test_aerospike.c
+1 −1 vs/aerospike-client-c-libevent.nuspec
+1 −1 vs/aerospike-client-c-libuv.nuspec
+1 −1 vs/aerospike-client-c.nuspec
+2 −0 vs/aerospike-test/aerospike-test.vcxproj
+6 −0 vs/aerospike-test/aerospike-test.vcxproj.filters
+54 −0 vs/aerospike.sln
+6 −0 vs/aerospike/aerospike.vcxproj
+18 −0 vs/aerospike/aerospike.vcxproj.filters
+248 −0 vs/examples/async-transaction/async-transaction.vcxproj
+33 −0 vs/examples/async-transaction/async-transaction.vcxproj.filters
+4 −0 vs/examples/async-transaction/packages.config
+4 −0 vs/examples/transaction/packages.config
+248 −0 vs/examples/transaction/transaction.vcxproj
+33 −0 vs/examples/transaction/transaction.vcxproj.filters
+8 −0 xcode/aerospike-test.xcodeproj/project.pbxproj
+24 −0 xcode/aerospike.xcodeproj/project.pbxproj
+24 −0 xcode/examples.xcodeproj/project.pbxproj
7 changes: 7 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
'sources': [
'src/main/aerospike.cc',
'src/main/client.cc',
'src/main/transaction.cc',
'src/main/config.cc',
'src/main/events.cc',
'src/main/cdt_ctx.cc',
Expand Down Expand Up @@ -125,11 +126,17 @@
'src/main/commands/scan_background.cc',
'src/main/commands/scan_pages.cc',
'src/main/commands/select_async.cc',
'src/main/commands/transaction_abort.cc',
'src/main/commands/transaction_commit.cc',
'src/main/commands/truncate.cc',
'src/main/commands/user_create.cc',
'src/main/commands/user_drop.cc',
'src/main/commands/udf_register.cc',
'src/main/commands/udf_remove.cc',
'src/main/enums/abort_status.cc',
'src/main/enums/commit_status.cc',
'src/main/enums/txn_state.cc',
'src/main/enums/txn_capacity.cc',
'src/main/enums/predicates.cc',
'src/main/enums/bitwise_enum.cc',
'src/main/enums/hll_enum.cc',
Expand Down
58 changes: 58 additions & 0 deletions examples/mrtAbort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node
// *****************************************************************************
// Copyright 2013-2024 Aerospike, Inc.
//
// 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
//
// http://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.
// *****************************************************************************
//
const Aerospike = require('aerospike')
const shared = require('./shared')

shared.runner()

async function mrtAbort (client, argv) {
// sconst record1 = { abc: 123 }
const record2 = { def: 456 }

const mrt = new Aerospike.Transaction()

const policy = {
txn: mrt
}
const keyList = []
for (let i = 0; i < argv.keys.length; i++) {
keyList.push(new Aerospike.Key(argv.namespace, argv.set, argv.keys[i]))
}

for (let i = 0; i < keyList.length; i++) {
await client.put(keyList[i], record2, policy)
await client.get(keyList[i], policy)
}

console.log(keyList)

console.log('aborting multi-record transaction with %d operations.', keyList.length * 2)
await client.abort(mrt)
console.info('multi-record transaction has been aborted.')
}

exports.command = 'mrtAbort <keys..>'
exports.describe = 'Abort a multi-record transaction'
exports.handler = shared.run(mrtAbort)
exports.builder = {
keys: {
desc: 'Provide keys for the records in the multi-record transaction',
type: 'array',
group: 'Command:'
}
}
57 changes: 57 additions & 0 deletions examples/mrtCommit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env node
// *****************************************************************************
// Copyright 2013-2024 Aerospike, Inc.
//
// 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
//
// http://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.
// *****************************************************************************
//
const Aerospike = require('aerospike')
const shared = require('./shared')

shared.runner()

async function mrtCommit (client, argv) {
// const record1 = { abc: 123 }
const record2 = { def: 456 }

const mrt = new Aerospike.Transaction()

const policy = {
txn: mrt
}
const keyList = []
for (let i = 0; i < argv.keys.length; i++) {
keyList.push(new Aerospike.Key(argv.namespace, argv.set, argv.keys[i]))
}
for (let i = 0; i < argv.keys.length; i++) {
await client.put(keyList[i], record2, policy)
await client.get(keyList[i], policy)
}

console.log(keyList)

console.log('committing multi-record transaction with %d operations.', keyList.length * 2)
await client.commit(mrt)
console.info('multi-record transaction has been committed.')
}

exports.command = 'mrtCommit <keys..>'
exports.describe = 'Commit a multi-record transaction'
exports.handler = shared.run(mrtCommit)
exports.builder = {
keys: {
desc: 'Provide keys for the records in the multi-record transaction',
type: 'array',
group: 'Command:'
}
}
2 changes: 2 additions & 0 deletions examples/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const commands = [
'geospatialMonteCarlo',
'get',
'info',
'mrtAbort',
'mrtCommit',
'operate',
'put',
'query',
Expand Down
28 changes: 28 additions & 0 deletions lib/abort_status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// *****************************************************************************
// Copyright 2024 Aerospike, Inc.
//
// 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
//
// http://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 as = require('bindings')('aerospike.node')
const abortStatus = as.abortStatus

module.exports = {
OK: abortStatus.OK,
ALREADY_COMMITTED: abortStatus.ALREADY_COMMITTED,
ALREADY_ABORTED: abortStatus.ALREADY_ABORTED,
ROLL_BACK_ABANDONED: abortStatus.ROLL_BACK_ABANDONED,
CLOSE_ABANDONED: abortStatus.CLOSE_ABANDONED
}
53 changes: 53 additions & 0 deletions lib/aerospike.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
const as = require('bindings')('aerospike.node')
const AerospikeError = require('./error')
const EventLoop = require('./event_loop')
const TransactionPool = require('./transaction_pool')

const _transactionPool = new TransactionPool()

exports._transactionPool = _transactionPool
/**
* @module aerospike
*
Expand Down Expand Up @@ -238,6 +242,39 @@ exports.Config = require('./config')
*/
exports.Double = require('./double')

/**
* Multi-record transaction (MRT) class. Each command in the MRT must use the same namespace.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it say "All commands in the same MRT must use the same namespace"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this in recent commits.

*
* note: By default, open transactions are destroyed when the final client in a process is closed.
* If you need your transaction to persist after the last client has been closed, provide `false` for the
* destroy Transactions argument in {@link Client#close} (see example below).
*
* @example
*
* const Aerospike = require('aerospike')
*
* // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE!
* var config = {
* hosts: '192.168.33.10:3000',
* }
* Aerospike.connect(config)
* .then(client => {
* // client is ready to accept commands
* console.log("Connected. Now Closing Connection.")
* client.close()
* })
* .catch(error => {
* client.close(
* false, // do not release the event loop
* false // do not destroy open transactions
* )
* console.error('Failed to connect to cluster: %s', error.message)
* })
*
* @summary {@link Transaction} class
*/
exports.Transaction = require('./transaction')

/**
* Representation of a GeoJSON value. Since GeoJSON values are JSON objects
* they need to be wrapped in the {@link GeoJSON} class so that the client can
Expand Down Expand Up @@ -701,6 +738,22 @@ exports.setupGlobalCommandQueue = function (policy) {
*/
exports.batchType = require('./batch_type')

/**
* The {@link module:aerospike/commit_status|aerospike/commit_status}
* module contains a list of commit statuses.
*
* @summary {@link module:aerospike/commit_status|aerospike/commit_status} module
*/
exports.commitStatus = require('./commit_status')

/**
* The {@link module:aerospike/abortStatus|aerospike/abort_status}
* module contains a list of abort statuses.
*
* @summary {@link module:aerospike/abortStatus|aerospike/abort_status} module
*/
exports.abortStatus = require('./abort_status')

/**
* The {@link module:aerospike/privilegeCode|aerospike/privilege_code}
* module is comprised of permission codes which define the type of
Expand Down
47 changes: 45 additions & 2 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const EventEmitter = require('events')

const as = require('bindings')('aerospike.node')
const AerospikeError = require('./error')
const abortStatus = require('./abort_status')
const commitStatus = require('./commit_status')
const txnState = require('./txn_state')
const Transaction = require('./transaction')
const Context = require('./cdt_context')
const Commands = require('./commands')
const Config = require('./config')
Expand All @@ -35,6 +39,7 @@ const utils = require('./utils')

// number of client instances currently connected to any Aerospike cluster
let _connectedClients = 0
const { _transactionPool } = require('./aerospike')

// callback function for cluster events (node added/removed, etc.)
function eventsCallback (event) {
Expand Down Expand Up @@ -211,6 +216,40 @@ Client.prototype.getNodes = function () {
return this.as_client.getNodes()
}

Client.prototype.abort = function (transaction, callback) {
_transactionPool.tendTransactions()
if (transaction instanceof Transaction) {
if (transaction.getState() === txnState.COMMITTED) {
return abortStatus.ALREADY_COMMITTED
} else if (transaction.getState() === txnState.ABORTED) {
return abortStatus.ALREADY_ABORTED
} else if (transaction.getDestroyed() === true) {
throw new AerospikeError('The object has been destroyed, please create a new transaction.')
}
} else {
throw new AerospikeError('transaction must be an instance of class Transaction.')
}
const cmd = new Commands.TransactionAbort(this, [transaction.transaction], callback)
return cmd.execute()
}

Client.prototype.commit = function (transaction, callback) {
_transactionPool.tendTransactions()
if (transaction instanceof Transaction) {
if (transaction.getState() === txnState.COMMITTED) {
return commitStatus.ALREADY_COMMITTED
} else if (transaction.getState() === txnState.ABORTED) {
return commitStatus.ALREADY_ABORTED
} else if (transaction.getDestroyed() === true) {
throw new AerospikeError('The object has been destroyed, please create a new transaction.')
}
} else {
throw new AerospikeError('transaction must be an instance of class Transaction.')
}
const cmd = new Commands.TransactionCommit(this, [transaction.transaction], callback)
return cmd.execute()
}

/**
* @function Client#contextToBase64
*
Expand Down Expand Up @@ -1711,7 +1750,8 @@ Client.prototype.batchSelect = function (keys, bins, policy, callback) {
*
* @summary Closes the client connection to the cluster.
*
* @param {boolean} [releaseEventLoop=false] - Whether to release the event loop handle after the client is closed.
* @param {boolean} [releaseEventLoop=false] - Whether to release the event loop handle after the last client is closed.
* @param {boolean} [destroyTransactions=true] - Whether to destroy any open transactions after the last client is closed.
*
* @see module:aerospike.releaseEventLoop
*
Expand All @@ -1734,13 +1774,16 @@ Client.prototype.batchSelect = function (keys, bins, policy, callback) {
* console.error('Failed to connect to cluster: %s', error.message)
* })
*/
Client.prototype.close = function (releaseEventLoop = false) {
Client.prototype.close = function (releaseEventLoop = false, destroyTransactions = true) {
if (this.isConnected(false)) {
this.connected = false
this.as_client.close()
_connectedClients -= 1
}
if (_connectedClients === 0) {
if (destroyTransactions) {
_transactionPool.removeAllTransactions()
}
if (releaseEventLoop) {
EventLoop.releaseEventLoop()
} else {
Expand Down
Loading
Loading