Skip to content

Commit

Permalink
JSCBC-1305: Allow binary transactions to use RawBinaryTranscoder
Browse files Browse the repository at this point in the history
Changes
=======
* Add optional option object to get, insert and replace transaction operations (follows
  binary txn spec)
* Add tests to confirm functionality

Change-Id: I3dcfcb64b3b69ed6062a951d13d1627723dabc99
Reviewed-on: https://review.couchbase.org/c/couchnode/+/219470
Reviewed-by: Sergey Avseyev <sergey.avseyev@gmail.com>
Tested-by: Jared Casey <jared.casey@couchbase.com>
  • Loading branch information
thejcfactor committed Nov 20, 2024
1 parent 7566df0 commit a704a23
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 73 deletions.
66 changes: 51 additions & 15 deletions lib/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
QueryScanConsistency,
} from './querytypes'
import { Scope } from './scope'
import { DefaultTranscoder } from './transcoders'
import { DefaultTranscoder, Transcoder } from './transcoders'
import { Cas, PromiseHelper } from './utilities'

/**
Expand Down Expand Up @@ -360,23 +360,50 @@ export interface TransactionQueryOptions {
scope?: Scope
}

/**
* @category Transactions
*/
export interface TransactionGetOptions {
/**
* Specifies an explicit transcoder to use for this specific operation.
*/
transcoder?: Transcoder
}

/**
* @category Transactions
*/
export interface TransactionInsertOptions {
/**
* Specifies an explicit transcoder to use for this specific operation.
*/
transcoder?: Transcoder
}

/**
* @category Transactions
*/
export interface TransactionReplaceOptions {
/**
* Specifies an explicit transcoder to use for this specific operation.
*/
transcoder?: Transcoder
}

/**
* @internal
*/
function translateGetResult(
cppRes: CppTransactionGetResult | null
cppRes: CppTransactionGetResult | null,
transcoder: Transcoder
): TransactionGetResult | null {
if (!cppRes) {
return null
}

let content
if (cppRes.content && cppRes.content.data && cppRes.content.data.length > 0) {
try {
content = JSON.parse(cppRes.content.data.toString('utf8'))
} catch (e) {
content = cppRes.content.data
}
content = transcoder.decode(cppRes.content.data, cppRes.content.flags)
}

return new TransactionGetResult({
Expand Down Expand Up @@ -436,11 +463,14 @@ export class TransactionAttemptContext {
*
* @param collection The collection the document lives in.
* @param key The document key to retrieve.
* @param options Optional parameters for this operation.
*/
async get(
collection: Collection,
key: string
key: string,
options?: TransactionGetOptions
): Promise<TransactionGetResult> {
const transcoder = options?.transcoder || this._transcoder
return PromiseHelper.wrap((wrapCallback) => {
const id = collection._cppDocId(key)
this._impl.get(
Expand All @@ -453,7 +483,7 @@ export class TransactionAttemptContext {
return wrapCallback(err, null)
}

wrapCallback(err, translateGetResult(cppRes))
wrapCallback(err, translateGetResult(cppRes, transcoder))
}
)
})
Expand All @@ -465,15 +495,18 @@ export class TransactionAttemptContext {
* @param collection The collection the document lives in.
* @param key The document key to insert.
* @param content The document content to insert.
* @param options Optional parameters for this operation.
*/
async insert(
collection: Collection,
key: string,
content: any
content: any,
options?: TransactionInsertOptions
): Promise<TransactionGetResult> {
return PromiseHelper.wrap((wrapCallback) => {
const id = collection._cppDocId(key)
const [data, flags] = this._transcoder.encode(content)
const transcoder = options?.transcoder || this._transcoder
const [data, flags] = transcoder.encode(content)
this._impl.insert(
{
id,
Expand All @@ -488,7 +521,7 @@ export class TransactionAttemptContext {
return wrapCallback(err, null)
}

wrapCallback(err, translateGetResult(cppRes))
wrapCallback(err, translateGetResult(cppRes, transcoder))
}
)
})
Expand All @@ -499,13 +532,16 @@ export class TransactionAttemptContext {
*
* @param doc The document to replace.
* @param content The document content to insert.
* @param options Optional parameters for this operation.
*/
async replace(
doc: TransactionGetResult,
content: any
content: any,
options?: TransactionReplaceOptions
): Promise<TransactionGetResult> {
return PromiseHelper.wrap((wrapCallback) => {
const [data, flags] = this._transcoder.encode(content)
const transcoder = options?.transcoder || this._transcoder
const [data, flags] = transcoder.encode(content)
this._impl.replace(
{
doc: {
Expand All @@ -529,7 +565,7 @@ export class TransactionAttemptContext {
return wrapCallback(err, null)
}

wrapCallback(err, translateGetResult(cppRes))
wrapCallback(err, translateGetResult(cppRes, transcoder))
}
)
})
Expand Down
175 changes: 117 additions & 58 deletions test/transactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const assert = require('chai').assert

const H = require('./harness')

const { RawBinaryTranscoder } = require('../lib/transcoders')

describe('#transactions', function () {
before(async function () {
H.skipIfMissingFeature(this, H.Features.Transactions)
Expand Down Expand Up @@ -492,70 +494,127 @@ describe('#transactions', function () {
}
})

it('should not fail to parse binary doc w/in lambda', async function () {
const testkey = H.genTestKey()
var testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)

await H.co.insert(testkey, testBinVal)

await H.c.transactions().run(
async (attempt) => {
const getDoc = await attempt.get(H.co, testkey)
assert.deepEqual(getDoc.content, testBinVal)
},
{ timeout: 5000 }
)
}).timeout(15000)
describe('#binary', function () {
it('should not fail to parse binary doc w/in lambda', async function () {
const testkey = H.genTestKey()
var testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)

it('should allow binary txns', async function () {
H.skipIfMissingFeature(this, H.Features.BinaryTransactions)
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
const newBinVal = Buffer.from('666f6f62617262617a', 'hex')
await H.c.transactions().run(
async (attempt) => {
await attempt.insert(H.co, testkey, testBinVal)
const getRes = await attempt.get(H.co, testkey)
assert.deepEqual(getRes.content, testBinVal)
const repRes = await attempt.replace(getRes, newBinVal)
assert.isTrue(getRes.cas != repRes.cas)
},
{ timeout: 5000 }
)
}).timeout(15000)
await H.co.insert(testkey, testBinVal)

it('should have FeatureNotAvailableError cause if binary txns not supported', async function () {
if (H.supportsFeature(H.Features.BinaryTransactions)) {
this.skip()
}
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
let numAttempts = 0
try {
await H.c.transactions().run(
async (attempt) => {
numAttempts++
const getDoc = await attempt.get(H.co, testkey)
assert.deepEqual(getDoc.content, testBinVal)
},
{ timeout: 5000 }
)
}).timeout(15000)

it('should allow binary txns', async function () {
H.skipIfMissingFeature(this, H.Features.BinaryTransactions)
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
const newBinVal = Buffer.from('666f6f62617262617a', 'hex')
await H.c.transactions().run(
async (attempt) => {
await attempt.insert(H.co, testkey, testBinVal)
const getRes = await attempt.get(H.co, testkey)
assert.deepEqual(getRes.content, testBinVal)
const repRes = await attempt.replace(getRes, newBinVal)
assert.isTrue(getRes.cas != repRes.cas)
},
{ timeout: 2000 }
{ timeout: 5000 }
)
} catch (err) {
assert.instanceOf(err, H.lib.TransactionFailedError)
assert.instanceOf(err.cause, H.lib.FeatureNotAvailableError)
assert.equal(
err.cause.cause.message,
'Possibly attempting a binary transaction operation with a server version < 7.6.2'
}).timeout(15000)

it('should have FeatureNotAvailableError cause if binary txns not supported', async function () {
if (H.supportsFeature(H.Features.BinaryTransactions)) {
this.skip()
}
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
}
assert.equal(numAttempts, 1)
}).timeout(15000)
let numAttempts = 0
try {
await H.c.transactions().run(
async (attempt) => {
numAttempts++
await attempt.insert(H.co, testkey, testBinVal)
},
{ timeout: 2000 }
)
} catch (err) {
assert.instanceOf(err, H.lib.TransactionFailedError)
assert.instanceOf(err.cause, H.lib.FeatureNotAvailableError)
assert.equal(
err.cause.cause.message,
'Possibly attempting a binary transaction operation with a server version < 7.6.2'
)
}
assert.equal(numAttempts, 1)
}).timeout(15000)

describe('#rawbinarytranscoder', function () {
it('should allow binary txns using RawBinaryTranscoder', async function () {
H.skipIfMissingFeature(this, H.Features.BinaryTransactions)
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
const newBinVal = Buffer.from('666f6f62617262617a', 'hex')
const tc = new RawBinaryTranscoder()
await H.c.transactions().run(
async (attempt) => {
await attempt.insert(H.co, testkey, testBinVal, { transcoder: tc })
const getRes = await attempt.get(H.co, testkey, { transcoder: tc })
assert.deepEqual(getRes.content, testBinVal)
const repRes = await attempt.replace(getRes, newBinVal)
assert.isTrue(getRes.cas != repRes.cas)
},
{ timeout: 5000 }
)
}).timeout(15000)

it('should have FeatureNotAvailableError cause if binary txns not supported using RawBinaryTranscoder', async function () {
if (H.supportsFeature(H.Features.BinaryTransactions)) {
this.skip()
}
const testkey = H.genTestKey()
const testBinVal = Buffer.from(
'00092bc691fb824300a6871ceddf7090d7092bc691fb824300a6871ceddf7090d7',
'hex'
)
const tc = new RawBinaryTranscoder()
let numAttempts = 0
try {
await H.c.transactions().run(
async (attempt) => {
numAttempts++
await attempt.insert(H.co, testkey, testBinVal, {
transcoder: tc,
})
},
{ timeout: 2000 }
)
} catch (err) {
assert.instanceOf(err, H.lib.TransactionFailedError)
assert.instanceOf(err.cause, H.lib.FeatureNotAvailableError)
assert.equal(
err.cause.cause.message,
'Possibly attempting a binary transaction operation with a server version < 7.6.2'
)
}
assert.equal(numAttempts, 1)
}).timeout(15000)
})
})
})

0 comments on commit a704a23

Please sign in to comment.