public bool ReadKey(BTreeValue *pEntry, ushort keyType, out TangleKey key) { if (keyType == 0) { key = default(TangleKey); return(false); } var buffer = ImmutableArrayPool <byte> .Allocate(pEntry->KeyLength); if (pEntry->KeyLength <= BTreeValue.KeyPrefixSize) { fixed(byte *pBuffer = buffer.Array) Native.memmove(pBuffer + buffer.Offset, pEntry->KeyPrefix, new UIntPtr(pEntry->KeyLength)); } else { using (var keyRange = KeyStream.AccessRange( pEntry->KeyOffset, pEntry->KeyLength, MemoryMappedFileAccess.Read )) Unsafe.ReadBytes(keyRange.Pointer, 0, buffer.Array, buffer.Offset, pEntry->KeyLength); } key = new TangleKey(buffer, keyType); return(true); }
public void NumericKeysWork() { var key = new TangleKey(1234); Scheduler.WaitFor(Tangle.Set(key, 1)); Assert.AreEqual(1, Scheduler.WaitFor(Tangle.Get(key))); }
public void UsesStaticSerializerAndDeserializerMethodsAutomatically() { var key = new TangleKey("hello"); Scheduler.WaitFor(Tangle.Set(key, new SpecialType(key, 4))); var result = Scheduler.WaitFor(Tangle.Get("hello")); Assert.AreEqual(4, result.Value); }
public void TestRepeatedGrowAndShrink() { var wasted1 = Tangle.WastedDataBytes; for (int i = 1; i < 50; i += 10) { for (int j = 0; j < 20; j++) { var key = new TangleKey(String.Format("test{0}", j)); var text = new String((char)(j + 63), i); Scheduler.WaitFor(Tangle.Set(key, text)); Assert.AreEqual( text, Scheduler.WaitFor(Tangle.Get(key)) ); } } var wasted2 = Tangle.WastedDataBytes; Assert.Greater(wasted2, wasted1); for (int j = 0; j < 20; j++) { var key = new TangleKey(String.Format("test{0}", j)); var text = new String((char)(j + 63), 5); Scheduler.WaitFor(Tangle.Set(key, text)); Assert.AreEqual( text, Scheduler.WaitFor(Tangle.Get(key)) ); } var wasted3 = Tangle.WastedDataBytes; Assert.AreEqual(wasted3, wasted2); for (int j = 20; j < 40; j++) { var key = new TangleKey(String.Format("test{0}", j)); var text = new String((char)(j + 63), 10); Scheduler.WaitFor(Tangle.Set(key, text)); Assert.AreEqual( text, Scheduler.WaitFor(Tangle.Get(key)) ); } // This will fail if the freelist is not being used Assert.Less(Tangle.WastedDataBytes, wasted3, "Freelist not functioning"); }
public void IndexUpdatedWhenAddingNewValues() { var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); var key = new TangleKey("hello"); var value = "world"; Scheduler.WaitFor(Tangle.Set(key, value)); Assert.AreEqual(key, Scheduler.WaitFor(ByValue.FindOne(value))); Assert.AreEqual(value, Scheduler.WaitFor(ByValue.GetOne(value))); }
public void WriteKey(ref BTreeValue btreeValue, TangleKey key) { var prefixSize = Math.Min(key.Data.Count, BTreeValue.KeyPrefixSize); fixed(byte *pPrefix = btreeValue.KeyPrefix) Unsafe.WriteBytes(pPrefix, 0, key.Data.Array, key.Data.Offset, prefixSize); if (prefixSize < key.Data.Count) { using (var keyRange = KeyStream.AccessRange(btreeValue.KeyOffset, btreeValue.KeyLength, MemoryMappedFileAccess.Write)) Unsafe.WriteBytes(keyRange.Pointer, 0, key.Data); } }
public void TestKeyEquals() { var keyA = new TangleKey("abcd"); var keyB = new TangleKey("abcd"); var keyC = new TangleKey(2); var keyD = new TangleKey(2); Assert.IsTrue(keyA.Equals(keyA)); Assert.IsTrue(keyA.Equals(keyB)); Assert.IsTrue(keyC.Equals(keyC)); Assert.IsTrue(keyC.Equals(keyD)); Assert.IsFalse(keyA.Equals(keyC)); }
public void CanAddIndexToTangleWithExistingValues() { var key1 = new TangleKey("hello"); var value1 = "world"; var key2 = new TangleKey("greetings"); var value2 = "place"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); Assert.AreEqual(key1, Scheduler.WaitFor(ByValue.FindOne(value1))); Assert.AreEqual(key2, Scheduler.WaitFor(ByValue.FindOne(value2))); }
public void IndexHandlesMultipleKeysForTheSameValue() { var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); var key1 = new TangleKey("hello"); var key2 = new TangleKey("greetings"); var value = "world"; Scheduler.WaitFor(Tangle.Set(key1, value)); Scheduler.WaitFor(Tangle.Set(key2, value)); Assert.AreEqual( new TangleKey[] { key1, key2 }, Scheduler.WaitFor(ByValue.Find(value)) ); Assert.AreEqual( new string[] { value, value }, Scheduler.WaitFor(ByValue.Get(value)) ); }
public void FetchKeysForMultipleValuesWithEnumeratorIndex() { var ByWords = Scheduler.WaitFor(Tangle.CreateIndex <string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual( new[] { key1, key2 }, Scheduler.WaitFor(ByWords.Find(new [] { "Hello", "Greetings" })) ); }
public void CanUseEnumeratorAsIndexFunction() { // Bleh, unless we explicitly specify the type argument to CreateIndex, // it assumes a type of <string[]> instead of picking the IEnumerable overload. var ByWords = Scheduler.WaitFor(Tangle.CreateIndex <string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual(new [] { key1, key2 }, Scheduler.WaitFor(ByWords.Find("World"))); Assert.AreEqual(new [] { key2 }, Scheduler.WaitFor(ByWords.Find("Greetings"))); }
public unsafe void TestGrowAndAlterDataByLockingIt() { var key = new TangleKey("test"); var value = "abcdefgh"; var newValue = "acdefghijkl"; Scheduler.WaitFor(Tangle.Set(key, value)); var findResult = Scheduler.WaitFor(Tangle.Find(key)); var newBytes = Encoding.UTF8.GetBytes(newValue); fixed(byte *pNewBytes = newBytes) using (var data = Scheduler.WaitFor(findResult.LockData(newBytes.Length))) { Assert.GreaterOrEqual(data.Size, newBytes.Length); Native.memmove(data.Pointer, pNewBytes, new UIntPtr((uint)newBytes.Length)); } Assert.AreEqual(newValue, Scheduler.WaitFor(Tangle.Get(key))); }
public unsafe void TestLockExistingData() { var key = new TangleKey("test"); var value = "abcdefgh"; Scheduler.WaitFor(Tangle.Set(key, value)); var findResult = Scheduler.WaitFor(Tangle.Find(key)); var expectedBytes = Encoding.UTF8.GetBytes(value); fixed(byte *pExpected = expectedBytes) using (var data = Scheduler.WaitFor(findResult.LockData())) { Assert.AreEqual(expectedBytes.Length, data.Size); Assert.AreEqual(0, Native.memcmp( pExpected, data.Pointer, new UIntPtr((uint)Math.Min(data.Size, expectedBytes.Length)) )); } }
public void CanUseEnumeratorAsIndexFunction() { // Bleh, unless we explicitly specify the type argument to CreateIndex, // it assumes a type of <string[]> instead of picking the IEnumerable overload. var ByWords = Scheduler.WaitFor(Tangle.CreateIndex<string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual(new [] { key1, key2 }, Scheduler.WaitFor(ByWords.Find("World"))); Assert.AreEqual(new [] { key2 }, Scheduler.WaitFor(ByWords.Find("Greetings"))); }
public void TestCopyDataToStream() { var key = new TangleKey("test"); var value = new String('a', 1024 * 1024 * 2); Scheduler.WaitFor(Tangle.Set(key, value)); var findResult = Scheduler.WaitFor(Tangle.Find(key)); var ms = new MemoryStream(); Scheduler.WaitFor(findResult.CopyTo(ms, bufferSize: 64 * 1024)); var expectedBytes = Encoding.UTF8.GetBytes(value); var actualBytes = new byte[expectedBytes.Length]; ms.Seek(0, SeekOrigin.Begin); ms.Read(actualBytes, 0, expectedBytes.Length); Assert.AreEqual(expectedBytes, actualBytes); }
public void WriteNewKey(BTreeValue *pEntry, TangleKey key) { if (key.Data.Count > BTreeValue.KeyPrefixSize) { pEntry->KeyOffset = (uint)KeyStream.AllocateSpace((uint)key.Data.Count).Value; } else { pEntry->KeyOffset = 0; } pEntry->KeyLength = (ushort)key.Data.Count; // It's important that we zero out these fields so that when we write the data, // it's done in append mode instead of replace mode pEntry->DataOffset = 0; pEntry->DataLength = 0; pEntry->ExtraDataBytes = 0; WriteKey(ref *pEntry, key); }
public void SerializerAndDeserializerHaveAccessToKey() { var key = new TangleKey("hello"); // This will fail because the specified keys don't match, and that lets us know // that the serializer had access to the key. Kind of a hack. try { Scheduler.WaitFor(Tangle.Set("world", new SpecialType(key, 4))); } catch (FutureException fe) { Assert.IsInstanceOf <SerializerThrewException>(fe.InnerException); Assert.IsInstanceOf <InvalidDataException>(fe.InnerException.InnerException); } // As a side effect, this also tests the Tangle's ability to recover from // a failed serialization. If an exception from the Serializer were to bubble // up, the BTree would be left in an invalid state and this set would fail. Scheduler.WaitFor(Tangle.Set(key, new SpecialType(key, 4))); var result = Scheduler.WaitFor(Tangle.Get("hello")); Assert.IsTrue(key.Equals(result.Key)); }
public void IndexUpdatedWhenValueChanged() { var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); var key = new TangleKey("hello"); var value1 = "world"; var value2 = "place"; Scheduler.WaitFor(Tangle.Set(key, value1)); Assert.AreEqual(key, Scheduler.WaitFor(ByValue.FindOne(value1))); Scheduler.WaitFor(Tangle.Set(key, value2)); try { Scheduler.WaitFor(ByValue.FindOne(value1)); Assert.Fail("Expected to throw"); } catch (FutureException fe) { Assert.IsInstanceOf <KeyNotFoundException>(fe.InnerException); } Assert.AreEqual(key, Scheduler.WaitFor(ByValue.FindOne(value2))); }
public void TestGetAllKeys() { var ByWords = Scheduler.WaitFor(Tangle.CreateIndex <string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual( new[] { "Greetings", "Hello", "World" }, Scheduler.WaitFor(ByWords.GetAllKeys()) .Select((k) => k.Value as string) .OrderBy((k) => k) .ToArray() ); }
public void FetchKeysForMultipleValues() { var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); var key1 = new TangleKey("hello"); var key2 = new TangleKey("greetings"); var key3 = new TangleKey("hi"); var value1 = "world"; var value2 = "cat"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value1)); Scheduler.WaitFor(Tangle.Set(key3, value2)); Assert.AreEqual( new TangleKey[] { key1, key2, key3 }, Scheduler.WaitFor(ByValue.Find(new[] { value1, value2 })) ); Assert.AreEqual( new string[] { value1, value1, value2 }, Scheduler.WaitFor(ByValue.Get(new [] { value1, value2 })) ); }
public void FetchKeysForMultipleValuesWithEnumeratorIndex() { var ByWords = Scheduler.WaitFor(Tangle.CreateIndex<string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual( new[] { key1, key2 }, Scheduler.WaitFor(ByWords.Find(new [] { "Hello", "Greetings" })) ); }
/// <summary> /// Searches the BTree for a provided key. /// </summary> /// <param name="key">The key to search for.</param> /// <param name="forInsertion">Indicates that you will be inserting the specified key if it does not already exist. If true, the find operation will prepare the tree for an insert operation.</param> /// <param name="nodeIndex">Contains the index of the BTree node where the search ended.</param> /// <param name="valueIndex">Contains the index of the value within the node that matched the key, if a match was found. If a match was not found, contains the index of the leaf where the key should be inserted.</param> /// <returns>True if the key was found within the tree. False if the key was not found.</returns> public bool FindKey(TangleKey key, bool forInsertion, out long nodeIndex, out uint valueIndex) { uint keyLength = (uint)key.Data.Count; long rootIndex = RootIndex; long parentNodeIndex = rootIndex; long currentNode = parentNodeIndex; uint parentValueIndex = 0; while (true) fixed(byte *pKey = &key.Data.Array[key.Data.Offset]) using (var range = AccessNode(currentNode, false)) { var pNode = (BTreeNode *)range.Pointer; // As we descend the tree, we split any full nodes we encounter so that // if we end up performing an insertion, we won't need to then walk all the // way back *up* the tree and split in reverse. if (forInsertion && (pNode->NumValues == BTreeNode.MaxValues)) { if (parentNodeIndex == currentNode) { // Splitting the root. var newRootIndex = CreateRoot(); using (var newRootRange = AccessNode(newRootIndex, true)) { var pNewRoot = (BTreeNode *)newRootRange.Pointer; var pNewLeaves = (BTreeLeaf *)(newRootRange.Pointer + BTreeNode.OffsetOfLeaves); // This looks wrong, but it's not: We want the new root to contain 0 values, // but have one leaf pointing to the old root, so that we can split the old // root in half. Splitting will move a single value up into the new root. pNewRoot->HasLeaves = 1; pNewLeaves[0].NodeIndex = (uint)currentNode; UnlockNode(newRootRange); } SplitLeafNode(newRootIndex, 0, currentNode); // Restart at the root currentNode = parentNodeIndex = rootIndex = newRootIndex; parentValueIndex = 0; continue; } else { // Splitting a regular node. SplitLeafNode(parentNodeIndex, parentValueIndex, currentNode); // Restart at the root currentNode = parentNodeIndex = rootIndex; parentValueIndex = 0; continue; } } var pValues = (BTreeValue *)(range.Pointer + BTreeNode.OffsetOfValues); if (SearchValues(pValues, pNode->NumValues, pKey, keyLength, out valueIndex)) { // Found an exact match within the BTree node. nodeIndex = currentNode; return(true); } if (pNode->HasLeaves == 1) { // No exact match was found, so valueIndex now contains the index of the leaf node. var pLeaves = (BTreeLeaf *)(range.Pointer + BTreeNode.OffsetOfLeaves); parentNodeIndex = currentNode; parentValueIndex = valueIndex; currentNode = pLeaves[valueIndex].NodeIndex; } else { // The value was not found inside the node, and we're in a node with no leaves, so there is no match. nodeIndex = currentNode; return(false); } } }
public unsafe void TestLockExistingData() { var key = new TangleKey("test"); var value = "abcdefgh"; Scheduler.WaitFor(Tangle.Set(key, value)); var findResult = Scheduler.WaitFor(Tangle.Find(key)); var expectedBytes = Encoding.UTF8.GetBytes(value); fixed (byte * pExpected = expectedBytes) using (var data = Scheduler.WaitFor(findResult.LockData())) { Assert.AreEqual(expectedBytes.Length, data.Size); Assert.AreEqual(0, Native.memcmp( pExpected, data.Pointer, new UIntPtr((uint)Math.Min(data.Size, expectedBytes.Length)) )); } }
public void IndexUpdatedWhenValueChanged() { var ByValue = Scheduler.WaitFor(Tangle.CreateIndex("ByValue", (ref string v) => v)); var key = new TangleKey("hello"); var value1 = "world"; var value2 = "place"; Scheduler.WaitFor(Tangle.Set(key, value1)); Assert.AreEqual(key, Scheduler.WaitFor(ByValue.FindOne(value1))); Scheduler.WaitFor(Tangle.Set(key, value2)); try { Scheduler.WaitFor(ByValue.FindOne(value1)); Assert.Fail("Expected to throw"); } catch (FutureException fe) { Assert.IsInstanceOf<KeyNotFoundException>(fe.InnerException); } Assert.AreEqual(key, Scheduler.WaitFor(ByValue.FindOne(value2))); }
public void WriteNewKey(StreamRange nodeRange, uint valueIndex, TangleKey key) { long position = BTreeNode.OffsetOfValues + (valueIndex * BTreeValue.Size); WriteNewKey((BTreeValue *)(nodeRange.Pointer + position), key); }
public unsafe void TestGrowAndAlterDataByLockingIt() { var key = new TangleKey("test"); var value = "abcdefgh"; var newValue = "acdefghijkl"; Scheduler.WaitFor(Tangle.Set(key, value)); var findResult = Scheduler.WaitFor(Tangle.Find(key)); var newBytes = Encoding.UTF8.GetBytes(newValue); fixed (byte* pNewBytes = newBytes) using (var data = Scheduler.WaitFor(findResult.LockData(newBytes.Length))) { Assert.GreaterOrEqual(data.Size, newBytes.Length); Native.memmove(data.Pointer, pNewBytes, new UIntPtr((uint)newBytes.Length)); } Assert.AreEqual(newValue, Scheduler.WaitFor(Tangle.Get(key))); }
public void TestGetAllKeys() { var ByWords = Scheduler.WaitFor(Tangle.CreateIndex<string>( "ByWords", (string v) => v.Split(' ') )); var key1 = new TangleKey("a"); var key2 = new TangleKey("b"); var value1 = "Hello World"; var value2 = "Greetings World"; Scheduler.WaitFor(Tangle.Set(key1, value1)); Scheduler.WaitFor(Tangle.Set(key2, value2)); Assert.AreEqual( new[] { "Greetings", "Hello", "World" }, Scheduler.WaitFor(ByWords.GetAllKeys()) .Select((k) => k.Value as string) .OrderBy((k) => k) .ToArray() ); }
public bool ReadKey(BTreeValue *pEntry, out TangleKey key) { ushort keyType = pEntry->KeyType; return(ReadKey(pEntry, keyType, out key)); }
public void SerializerAndDeserializerHaveAccessToKey() { var key = new TangleKey("hello"); // This will fail because the specified keys don't match, and that lets us know // that the serializer had access to the key. Kind of a hack. try { Scheduler.WaitFor(Tangle.Set("world", new SpecialType(key, 4))); } catch (FutureException fe) { Assert.IsInstanceOf<SerializerThrewException>(fe.InnerException); Assert.IsInstanceOf<InvalidDataException>(fe.InnerException.InnerException); } // As a side effect, this also tests the Tangle's ability to recover from // a failed serialization. If an exception from the Serializer were to bubble // up, the BTree would be left in an invalid state and this set would fail. Scheduler.WaitFor(Tangle.Set(key, new SpecialType(key, 4))); var result = Scheduler.WaitFor(Tangle.Get("hello")); Assert.IsTrue(key.Equals(result.Key)); }
public SpecialType(TangleKey key, UInt32 value) { Key = key; Value = value; }