Skip to content

Commit cecf0e4

Browse files
committed
SetVersionStampedKey will automatically detect the location of the versionstamp present in the key, and add the 16-bit offset suffix.
1 parent 6ae022a commit cecf0e4

File tree

3 files changed

+143
-52
lines changed

3 files changed

+143
-52
lines changed

FoundationDB.Client/FdbTransactionExtensions.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace FoundationDB.Client
3636
using System.Threading.Tasks;
3737
using Doxense.Diagnostics.Contracts;
3838
using Doxense.Linq;
39+
using Doxense.Memory;
3940
using Doxense.Serialization.Encoders;
4041
using JetBrains.Annotations;
4142

@@ -425,19 +426,64 @@ public static void AtomicMin([NotNull] this IFdbTransaction trans, Slice key, Sl
425426
trans.Atomic(key, value, FdbMutationType.Min);
426427
}
427428

429+
private static int GetVersionStampOffset(Slice buffer, Slice token, string argName)
430+
{
431+
// the buffer MUST contain one incomplete stamp, either the random token of the current transsaction or the default token (all-FF)
432+
433+
int p = token.HasValue ? buffer.IndexOf(token) : -1;
434+
if (p >= 0)
435+
{ // found a candidate spot, we have to make sure that it is only present once in the key!
436+
437+
if (buffer.IndexOf(token, p + token.Count) >= 0)
438+
{
439+
if (argName == "key")
440+
throw new ArgumentException("The key should only contain one occurrence of a VersionStamp.", argName);
441+
else
442+
throw new ArgumentException("The value should only contain one occurrence of a VersionStamp.", argName);
443+
}
444+
}
445+
else
446+
{ // not found, maybe it is using the default incomplete stamp (all FF) ?
447+
p = buffer.IndexOf(VersionStamp.IncompleteToken);
448+
if (p < 0)
449+
{
450+
if (argName == "key")
451+
throw new ArgumentException("The key should contain at least one VersionStamp.", argName);
452+
else
453+
throw new ArgumentException("The value should contain at least one VersionStamp.", argName);
454+
}
455+
}
456+
Contract.Assert(p >= 0 && p + token.Count <= buffer.Count);
457+
458+
return p;
459+
}
460+
428461
//TODO: XML Comments!
429462
public static void SetVersionStampedKey([NotNull] this IFdbTransaction trans, Slice key, Slice value)
430463
{
431464
Contract.NotNull(trans, nameof(trans));
432465

433-
trans.Atomic(key, value, FdbMutationType.VersionStampedKey);
466+
//TODO: PERF: optimize this to not have to allocate!
467+
var token = trans.CreateVersionStamp().ToSlice();
468+
var offset = GetVersionStampOffset(key, token, nameof(key));
469+
470+
var writer = new SliceWriter(key.Count + 2);
471+
writer.WriteBytes(key);
472+
writer.WriteFixed16(checked((ushort) offset)); //note: currently stored as 16-bits in Little Endian
473+
474+
trans.Atomic(writer.ToSlice(), value, FdbMutationType.VersionStampedKey);
434475
}
435476

436-
//TODO: XML Comments!
477+
/// <summary>Set the <paramref name="value"/> of the <paramref name="key"/> in the database, with the first 10 bytes overwritten with the transaction's <see cref="VersionStamp"/>.</summary>
478+
/// <param name="trans">Transaction to use for the operation</param>
479+
/// <param name="key">Name of the key whose value is to be mutated.</param>
480+
/// <param name="value">Value whose first 10 bytes will be overwritten by the database with the resolved VersionStamp at commit time. The rest of the value will be untouched.</param>
437481
public static void SetVersionStampedValue([NotNull] this IFdbTransaction trans, Slice key, Slice value)
438482
{
439483
Contract.NotNull(trans, nameof(trans));
440484

485+
if (value.Count < 10) throw new ArgumentException("The value must be at least 10 bytes long.", nameof(value));
486+
441487
trans.Atomic(key, value, FdbMutationType.VersionStampedValue);
442488
}
443489

FoundationDB.Client/VersionStamp.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ namespace FoundationDB.Client
5757
private const ushort FLAGS_HAS_VERSION = 0x1; // unset: 80-bits, set: 96-bits
5858
private const ushort FLAGS_IS_INCOMPLETE = 0x2; // unset: complete, set: incomplete
5959

60+
/// <summary>Serialized bytes of the default incomplete stamp (composed of only 0xFF)</summary>
61+
internal static readonly Slice IncompleteToken = Slice.Repeat(0xFF, 10);
62+
6063
/// <summary>Commit version of the transaction</summary>
6164
/// <remarks>This value is determined by the database at commit time.</remarks>
6265

FoundationDB.Tests/TransactionFacts.cs

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ namespace FoundationDB.Client.Tests
3636
using System.Text;
3737
using System.Threading;
3838
using System.Threading.Tasks;
39-
using Doxense.Collections.Tuples;
40-
using Doxense.Memory;
4139

4240
[TestFixture]
4341
public class TransactionFacts : FdbTest
@@ -1991,6 +1989,77 @@ public async Task Test_Simple_Read_Transaction()
19911989
}
19921990
}
19931991

1992+
[Test]
1993+
public async Task Test_VersionStamps_Share_The_Same_Token_Per_Transaction_Attempt()
1994+
{
1995+
// Veryify that we can set versionstamped keys inside a transaction
1996+
1997+
using (var db = await OpenTestDatabaseAsync())
1998+
{
1999+
using (var tr = db.BeginTransaction(this.Cancellation))
2000+
{
2001+
// should return a 80-bit incomplete stamp, using a random token
2002+
var x = tr.CreateVersionStamp();
2003+
Log($"> x : {x} with token '{x.ToSlice():X}'");
2004+
Assert.That(x.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2005+
Assert.That(x.HasUserVersion, Is.False);
2006+
Assert.That(x.UserVersion, Is.Zero);
2007+
Assert.That(x.TransactionVersion >> 56, Is.EqualTo(0xFF), "Highest 8 bit of Transaction Version should be set to 1");
2008+
Assert.That(x.TransactionOrder >> 12, Is.EqualTo(0xF), "Hight 4 bits of Transaction Order should be set to 1");
2009+
2010+
// should return a 96-bit incomplete stamp, using a the same random token and user version 0
2011+
var x0 = tr.CreateVersionStamp(0);
2012+
Log($"> x0 : {x0.ToSlice():X} => {x0}");
2013+
Assert.That(x0.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2014+
Assert.That(x0.TransactionVersion, Is.EqualTo(x.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2015+
Assert.That(x0.TransactionOrder, Is.EqualTo(x.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2016+
Assert.That(x0.HasUserVersion, Is.True);
2017+
Assert.That(x0.UserVersion, Is.EqualTo(0));
2018+
2019+
// should return a 96-bit incomplete stamp, using a the same random token and user version 1
2020+
var x1 = tr.CreateVersionStamp(1);
2021+
Log($"> x1 : {x1.ToSlice():X} => {x1}");
2022+
Assert.That(x1.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2023+
Assert.That(x1.TransactionVersion, Is.EqualTo(x.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2024+
Assert.That(x1.TransactionOrder, Is.EqualTo(x.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2025+
Assert.That(x1.HasUserVersion, Is.True);
2026+
Assert.That(x1.UserVersion, Is.EqualTo(1));
2027+
2028+
// should return a 96-bit incomplete stamp, using a the same random token and user version 42
2029+
var x42 = tr.CreateVersionStamp(42);
2030+
Log($"> x42: {x42.ToSlice():X} => {x42}");
2031+
Assert.That(x42.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2032+
Assert.That(x42.TransactionVersion, Is.EqualTo(x.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2033+
Assert.That(x42.TransactionOrder, Is.EqualTo(x.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2034+
Assert.That(x42.HasUserVersion, Is.True);
2035+
Assert.That(x42.UserVersion, Is.EqualTo(42));
2036+
2037+
// Reset the transaction
2038+
// => stamps should use a new value
2039+
Log("Reset!");
2040+
tr.Reset();
2041+
2042+
var y = tr.CreateVersionStamp();
2043+
Log($"> y : {y.ToSlice():X} => {y}'");
2044+
Assert.That(y, Is.Not.EqualTo(x), "VersionStamps should change when a transaction is reset");
2045+
2046+
Assert.That(y.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2047+
Assert.That(y.HasUserVersion, Is.False);
2048+
Assert.That(y.UserVersion, Is.Zero);
2049+
Assert.That(y.TransactionVersion >> 56, Is.EqualTo(0xFF), "Highest 8 bit of Transaction Version should be set to 1");
2050+
Assert.That(y.TransactionOrder >> 12, Is.EqualTo(0xF), "Hight 4 bits of Transaction Order should be set to 1");
2051+
2052+
var y42 = tr.CreateVersionStamp(42);
2053+
Log($"> y42: {y42.ToSlice():X} => {y42}");
2054+
Assert.That(y42.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2055+
Assert.That(y42.TransactionVersion, Is.EqualTo(y.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2056+
Assert.That(y42.TransactionOrder, Is.EqualTo(y.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2057+
Assert.That(y42.HasUserVersion, Is.True);
2058+
Assert.That(y42.UserVersion, Is.EqualTo(42));
2059+
}
2060+
}
2061+
}
2062+
19942063
[Test]
19952064
public async Task Test_VersionStamp_Operations()
19962065
{
@@ -2007,60 +2076,23 @@ public async Task Test_VersionStamp_Operations()
20072076
Log("Inserting keys with version stamps:");
20082077
using (var tr = db.BeginTransaction(this.Cancellation))
20092078
{
2010-
//TODO: HACKACK: until we add support to he transaction itself, we have to 'patch' the versionstamps by hand!
2011-
Slice HACKHACK_Stampify(Slice key)
2012-
{
2013-
// find the stamp byte sequence in the key
2014-
var x = tr.CreateVersionStamp().ToSlice();
2015-
int p = key.IndexOf(x);
2016-
Assert.That(p, Is.GreaterThan(0), "Stamp pattern was not found in the key!");
2017-
2018-
// append the offset at the end
2019-
var writer = new SliceWriter(key.Count + 2);
2020-
writer.WriteBytes(key);
2021-
writer.WriteFixed16((ushort) p); //note: the offset is Little Endian!
2022-
var y = writer.ToSlice();
2023-
2024-
//Log(y.ToHexaString(' ') + " | " + location.Keys.Dump(y));
2025-
return y;
2026-
}
20272079

2080+
// should return a 80-bit incomplete stamp, using a random token
20282081
var vs = tr.CreateVersionStamp();
20292082
Log($"> placeholder stamp: {vs} with token '{vs.ToSlice():X}'");
2030-
Assert.That(vs.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2031-
Assert.That(vs.HasUserVersion, Is.False);
2032-
Assert.That(vs.UserVersion, Is.Zero);
2033-
Assert.That(vs.TransactionVersion >> 56, Is.EqualTo(0xFF), "Highest 8 bit of Transaction Version should be set to 1");
2034-
Assert.That(vs.TransactionOrder >> 12, Is.EqualTo(0xF), "Hight 4 bits of Transaction Order should be set to 1");
2035-
2036-
var vs0 = tr.CreateVersionStamp(0);
2037-
Assert.That(vs0.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2038-
Assert.That(vs0.TransactionVersion, Is.EqualTo(vs.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2039-
Assert.That(vs0.TransactionOrder, Is.EqualTo(vs.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2040-
Assert.That(vs0.HasUserVersion, Is.True);
2041-
Assert.That(vs0.UserVersion, Is.EqualTo(0));
2042-
2043-
var vs1 = tr.CreateVersionStamp(1);
2044-
Assert.That(vs1.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2045-
Assert.That(vs1.TransactionVersion, Is.EqualTo(vs.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2046-
Assert.That(vs1.TransactionOrder, Is.EqualTo(vs.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2047-
Assert.That(vs1.HasUserVersion, Is.True);
2048-
Assert.That(vs1.UserVersion, Is.EqualTo(1));
2049-
2050-
var vs42 = tr.CreateVersionStamp(42);
2051-
Assert.That(vs42.IsIncomplete, Is.True, "Placeholder token should be incomplete");
2052-
Assert.That(vs42.TransactionVersion, Is.EqualTo(vs.TransactionVersion), "All generated stamps by one transaction should share the random token value ");
2053-
Assert.That(vs42.TransactionOrder, Is.EqualTo(vs.TransactionOrder), "All generated stamps by one transaction should share the random token value ");
2054-
Assert.That(vs42.HasUserVersion, Is.True);
2055-
Assert.That(vs42.UserVersion, Is.EqualTo(42));
20562083

20572084
// a single key using the 80-bit stamp
2058-
tr.SetVersionStampedKey(HACKHACK_Stampify(location.Keys.Encode("foo", vs, 123)), Slice.FromString("Hello, World!"));
2085+
tr.SetVersionStampedKey(location.Keys.Encode("foo", vs, 123), Slice.FromString("Hello, World!"));
20592086

20602087
// simulate a batch of 3 keys, using 96-bits stamps
2061-
tr.SetVersionStampedKey(HACKHACK_Stampify(location.Keys.Encode("bar", vs0)), Slice.FromString("Zero"));
2062-
tr.SetVersionStampedKey(HACKHACK_Stampify(location.Keys.Encode("bar", vs1)), Slice.FromString("One"));
2063-
tr.SetVersionStampedKey(HACKHACK_Stampify(location.Keys.Encode("bar", vs42)), Slice.FromString("FortyTwo"));
2088+
tr.SetVersionStampedKey(location.Keys.Encode("bar", tr.CreateVersionStamp(0)), Slice.FromString("Zero"));
2089+
tr.SetVersionStampedKey(location.Keys.Encode("bar", tr.CreateVersionStamp(1)), Slice.FromString("One"));
2090+
tr.SetVersionStampedKey(location.Keys.Encode("bar", tr.CreateVersionStamp(42)), Slice.FromString("FortyTwo"));
2091+
2092+
// value that contain the stamp
2093+
var val = Slice.FromString("$$$$$$$$$$Hello World!"); // '$' will be replaced by the stamp
2094+
Log($"> {val:X}");
2095+
tr.SetVersionStampedValue(location.Keys.Encode("baz"), val);
20642096

20652097
// need to be request BEFORE the commit
20662098
var vsTask = tr.GetVersionStampAsync();
@@ -2073,6 +2105,8 @@ Slice HACKHACK_Stampify(Slice key)
20732105
Log($"> actual stamp: {vsActual} with token '{vsActual.ToSlice():X}'");
20742106
}
20752107

2108+
await DumpSubspace(db, location);
2109+
20762110
Log("Checking database content:");
20772111
using (var tr = db.BeginReadOnlyTransaction(this.Cancellation))
20782112
{
@@ -2128,9 +2162,17 @@ Slice HACKHACK_Stampify(Slice key)
21282162
Assert.That(vs42.TransactionVersion, Is.EqualTo(vsActual.TransactionVersion));
21292163
Assert.That(vs42.TransactionOrder, Is.EqualTo(vsActual.TransactionOrder));
21302164
}
2165+
2166+
{
2167+
var baz = await tr.GetAsync(location.Keys.Encode("baz"));
2168+
Log($"> {baz:X}");
2169+
// ensure that the first 10 bytes have been overwritten with the stamp
2170+
Assert.That(baz.Count, Is.GreaterThan(0), "Key should be present in the database");
2171+
Assert.That(baz.StartsWith(vsActual.ToSlice()), Is.True, "The first 10 bytes should match the resolved stamp");
2172+
Assert.That(baz.Substring(10), Is.EqualTo(Slice.FromString("Hello World!")), "The rest of the slice should be untouched");
2173+
}
21312174
}
21322175

2133-
await DumpSubspace(db, location);
21342176
}
21352177
}
21362178

0 commit comments

Comments
 (0)