#region Copyright 2012-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* 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.
*/
#endregion
using System;
using System.Collections.Generic;
using CSharpTest.Net.Collections;
using CSharpTest.Net.IO;
using CSharpTest.Net.Serialization;
using NUnit.Framework;
namespace CSharpTest.Net.BPlusTree.Test
{
///
/// Demonstrates a few possible ways to use a BPlusTree across process boundaries by having a single-writer and multiple
/// readers. Calling Commit() on the writer can be dangerous to the readers, thus a cross-process thread synchronization
/// primitive must be used to ensure that readers are aware of Commit() calls, and, in the case of read-only, not actively
/// reading the tree in it's previous state. The writer guarantees that the file is consistent at any given moment in
/// time; however, since the reader traverses the data over time it is not possible to guarantee a consistent read. I do
/// not have any intentions of further support for this capability, it's generally just a bad idea ;) Instead, IIWY I
/// would look to use RPC to talk to the process controlling the writes.
///
[TestFixture]
public partial class TestMultiInstance
{
IEnumerable> MakeValues(int start, int count)
{
for (int ix = start; count > 0; count--, ix++)
yield return new KeyValuePair(ix, ix.ToString());
}
// Opens and reviews a 'read-only' instance of an already open B+Tree. Will only have access to data that the writer
// has comitted to disk.
[Test]
public void TestReadOnlyCopy()
{
using (var tempFile = new TempFile())
{
var options = new BPlusTree.OptionsV2(new PrimitiveSerializer(), new PrimitiveSerializer())
{
CreateFile = CreatePolicy.Always,
FileName = tempFile.TempPath,
}.CalcBTreeOrder(4, 10);
var readcopy = options.Clone();
readcopy.CreateFile = CreatePolicy.Never;
readcopy.ReadOnly = true;
using (var tree = new BPlusTree(options))
{
using (var copy = new BPlusTree(readcopy))
{
copy.EnableCount();
Assert.AreEqual(0, copy.Count);
}
//insert some data...
tree.AddRange(MakeValues(0, 100));
using (var copy = new BPlusTree(readcopy))
{
copy.EnableCount();
Assert.AreEqual(0, copy.Count);
}
tree.Commit();
//insert some data...
for (int i = 0; i < 100; i++)
tree.Remove(i);
tree.AddRange(MakeValues(1000, 1000));
using (var copy = new BPlusTree(readcopy))
{
copy.EnableCount();
Assert.AreEqual(100, copy.Count);
Assert.AreEqual(0, copy.First().Key);
Assert.AreEqual(99, copy.Last().Key);
}
tree.Commit();
}
}
}
// Demonstrates creating a second copy of an existing tree while its still open, and then keeping that copy in sync
// with the orignal by replaying the log periodically. Calling Commit() on the writer instance will reset the log,
// so periodic calls to Commit should be avoided to allow reading the log file.
[Test]
public void TestSyncFromLogging()
{
using (var tempFile = new TempFile())
using (var logfile = new TempFile())
using (var tempCopy = new TempFile())
{
var options = new BPlusTree.OptionsV2(new PrimitiveSerializer(), new PrimitiveSerializer())
{
CreateFile = CreatePolicy.Always,
FileName = tempFile.TempPath,
TransactionLogFileName = logfile.TempPath,
}.CalcBTreeOrder(4, 10);
var readcopy = options.Clone();
readcopy.FileName = tempCopy.TempPath;
readcopy.StoragePerformance = StoragePerformance.Fastest;
using (var tree = new BPlusTree(options))
using (var copy = new BPlusTree(readcopy))
using (var tlog = new TransactionLog(
new TransactionLogOptions(logfile.TempPath, PrimitiveSerializer.Int32, PrimitiveSerializer.String) { ReadOnly = true }))
{
tree.Add(0, "0");
tree.Commit();
long logpos = 0;
copy.EnableCount();
//start by copying the data from tree's file into the copy instance:
copy.BulkInsert(
BPlusTree.EnumerateFile(options),
new BulkInsertOptions { InputIsSorted = true, CommitOnCompletion = false, ReplaceContents = true }
);
Assert.AreEqual(1, copy.Count);
Assert.AreEqual("0", copy[0]);
tlog.ReplayLog(copy, ref logpos);
Assert.AreEqual(1, copy.Count);
//insert some data...
tree.AddRange(MakeValues(1, 99));
tlog.ReplayLog(copy, ref logpos);
Assert.AreEqual(100, copy.Count);
//insert some data...
for (int i = 0; i < 100; i++)
tree.Remove(i);
tlog.ReplayLog(copy, ref logpos);
Assert.AreEqual(0, copy.Count);
tree.AddRange(MakeValues(1000, 1000));
tlog.ReplayLog(copy, ref logpos);
Assert.AreEqual(1000, copy.Count);
}
}
}
}
}