diff --git a/LiteDB.Demo/Program.cs b/LiteDB.Demo/Program.cs index 0c27398c7..e0b33a801 100644 --- a/LiteDB.Demo/Program.cs +++ b/LiteDB.Demo/Program.cs @@ -1,90 +1,130 @@ -using LiteDB; -using System; -using System.Collections.Generic; +using System; using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using LiteDB; -namespace LiteDB.Demo +namespace litedb_test { - class Program + /// + /// Test record from desktop app + /// + + public class UnreadNotificationRecord { - static void Main(string[] args) + public enum NotificationTypeEnum { - var timer = new Stopwatch(); - ITest test = new LiteDB_Paging(); - //ITest test = new SQLite_Paging(); + Info, + Error + } - Console.WriteLine("Testing: {0}", test.GetType().Name); + [BsonId] + public int Id { get; set; } - test.Init(); + public Guid UserId { get; set; } - Console.WriteLine("Populating 100.000 documents..."); + public string Title { get; set; } + public string Message { get; set; } + public NotificationTypeEnum NotificationType { get; set; } - timer.Start(); - test.Populate(ReadDocuments()); - timer.Stop(); + public DateTime When { get; set; } + } - Console.WriteLine("Done in {0}ms", timer.ElapsedMilliseconds); - timer.Restart(); - var counter = test.Count(); - timer.Stop(); - - Console.WriteLine("Result query counter: {0} ({1}ms)", counter, timer.ElapsedMilliseconds); - - var input = "0"; - - while (input != "") - { - var skip = Convert.ToInt32(input); - var limit = 10; - - timer.Restart(); - var result = test.Fetch(skip, limit); - timer.Stop(); - - foreach(var doc in result) - { - Console.WriteLine( - doc["_id"].AsString.PadRight(6) + " - " + - doc["name"].AsString.PadRight(30) + " -> " + - doc["age"].AsInt32); - } - - Console.Write("\n({0}ms) => Enter skip index: ", timer.ElapsedMilliseconds); - input = Console.ReadLine(); - } - - Console.WriteLine("End"); - Console.ReadKey(); - } - static IEnumerable ReadDocuments() + /// + /// + /// + + internal class Program1 + { + + /// + /// Defines the entry point of the application. + /// + /// The args. + [STAThread] + static void Main0(string[] args) { - using (var s = File.OpenRead(@"datagen.txt")) + /* + * Important!: + * `connectionString` + * has to be path to **NON-SSD** drive + */ + + const string connectionString = "d://generated.litedb"; + + //Some GUID keys to share across all processes + Guid[] sharedGuids = { + Guid.Parse("B9321547-D4BE-461F-B7F9-2E2600839428"), + Guid.Parse("1F0689E8-121A-414D-80D1-2A54B516A6AC") + }; + + // main start point + if (args.Length == 0) { - var r = new StreamReader(s); + File.Delete(connectionString); - while(!r.EndOfStream) + const int processCount = 15; + + Console.WriteLine($"Spawning {processCount} child processes"); + + for (int i = 0; i < processCount; i++) + Process.Start(Process.GetCurrentProcess().MainModule.FileName, $"child_{i}"); + + return; + } + + var procId = args[0]; + + Console.WriteLine($"Running as `{procId}`"); + + try + { + using (var database = new LiteDatabase(connectionString)) { - var line = r.ReadLine(); - if (!string.IsNullOrEmpty(line)) + database.Shrink(); + + var collection = database.GetCollection(); + collection.EnsureIndex(x => x.UserId); + + for (int i = 0; i < 50; i++) { - var row = line.Split(','); + var random = new Random(); - yield return new BsonDocument + var record = new UnreadNotificationRecord { - ["_id"] = Convert.ToInt32(row[0]), - ["name"] = row[1], - ["age"] = Convert.ToInt32(row[2]) + UserId = sharedGuids[random.Next() % sharedGuids.Length], }; + + Console.WriteLine($"Item[{i}]: {procId}"); + + //Every 2nd iteration run some query that actually has to yield some results + + if (i % 2 == 0) + collection. + Find(Query.EQ(nameof(UnreadNotificationRecord.UserId), sharedGuids[random.Next() % sharedGuids.Length])). + ToArray(); + + //Every iteration insert new record + + collection.Insert(record); } + + Console.WriteLine($"{procId} process finished"); + } } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.WriteLine(ex.StackTrace); + Console.ReadKey(); + } } } + + } \ No newline at end of file diff --git a/LiteDB.Demo/Program1.cs b/LiteDB.Demo/Program1.cs new file mode 100644 index 000000000..2116e12de --- /dev/null +++ b/LiteDB.Demo/Program1.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using LiteDB; + +namespace litedb_test +{ + internal class Program + { + public class EntityInt { public int Id { get; set; } public string Name { get; set; } } + + + /// + /// Defines the entry point of the application. + /// + /// The args. + [STAThread] + static void Main(string[] args) + { + var r = new { name = "John", Age = 40 }; + + var d = BsonMapper.Global.ToDocument(r); + + var j = JsonSerializer.Serialize(d); + + File.Delete("d:\\Test.db"); + var db = new LiteDatabase("d:\\Test.db"); + var col = db.GetCollection("col1"); + for (int i = 0; i < 50; i++) + col.Upsert(new EntityInt { Name = i.ToString() }); + + for (int i = 0; i < 10; i++) + col.Delete(i); + + db.Shrink(); + + for (int i = 0; i < 5; i++) + col.Upsert(new EntityInt { Name = i.ToString() }); //Cannot insert duplicate key in unique index '_id'. The duplicate value is '42'. + + } + } + + +} \ No newline at end of file diff --git a/LiteDB.Tests/Engine/Create_Database_Tests.cs b/LiteDB.Tests/Engine/Create_Database_Tests.cs index c7f9a95df..bcbe61e87 100644 --- a/LiteDB.Tests/Engine/Create_Database_Tests.cs +++ b/LiteDB.Tests/Engine/Create_Database_Tests.cs @@ -34,5 +34,32 @@ public void Create_Database_With_Initial_Size() Assert.AreEqual(minimal, file.Size); } } + + [TestMethod] + public void Create_Database_With_Initial_Size_Encrypted() + { + var initial = 40 * 1024; // initial size: 40kb + var minimal = 4096 * 5; // 1 header + 1 lock + 1 collection + 1 data + 1 index = 5 pages minimal + + using (var file = new TempFile(checkIntegrity: false)) + using (var db = new LiteDatabase(file.Conn("initial size=40kb; password=123"))) + { + // just ensure open datafile + var uv = db.Engine.UserVersion; + + // test if file has 40kb + Assert.AreEqual(initial, file.Size); + + // simple insert to test if datafile still with 40kb + db.Engine.Run("db.col1.insert {a:1}"); // use 3 pages to this + + Assert.AreEqual(initial, file.Size); + + // ok, now shrink and test if file are minimal size + db.Shrink(); + + Assert.AreEqual(minimal, file.Size); + } + } } } \ No newline at end of file diff --git a/LiteDB.Tests/Utils/TempFile.cs b/LiteDB.Tests/Utils/TempFile.cs index f91c7479b..6fc68398c 100644 --- a/LiteDB.Tests/Utils/TempFile.cs +++ b/LiteDB.Tests/Utils/TempFile.cs @@ -68,7 +68,10 @@ protected virtual void Dispose(bool disposing) } // check file integrity - this.CheckIntegrity(); + if (_checkIntegrity) + { + this.CheckIntegrity(); + } File.Delete(this.Filename); diff --git a/LiteDB/Database/LiteDatabase.cs b/LiteDB/Database/LiteDatabase.cs index 5104fe057..2aa548386 100644 --- a/LiteDB/Database/LiteDatabase.cs +++ b/LiteDB/Database/LiteDatabase.cs @@ -209,7 +209,7 @@ public bool RenameCollection(string oldName, string newName) /// public long Shrink() { - return this.Shrink(_connectionString == null ? null : _connectionString.Password); + return this.Shrink(_connectionString?.Password); } /// diff --git a/LiteDB/Engine/Engine/Shrink.cs b/LiteDB/Engine/Engine/Shrink.cs index a53304e6a..80af1bc49 100644 --- a/LiteDB/Engine/Engine/Shrink.cs +++ b/LiteDB/Engine/Engine/Shrink.cs @@ -65,6 +65,8 @@ public long Shrink(string password = null, IDiskService tempDisk = null) } // create/destroy crypto class + if (_crypto != null) _crypto.Dispose(); + _crypto = password == null ? null : new AesEncryption(password, header.Salt); // initialize all services again (crypto can be changed) diff --git a/LiteDB/Engine/LiteEngine.cs b/LiteDB/Engine/LiteEngine.cs index 7a815e6d5..4e801f218 100644 --- a/LiteDB/Engine/LiteEngine.cs +++ b/LiteDB/Engine/LiteEngine.cs @@ -261,6 +261,9 @@ public static void CreateDatabase(Stream stream, string password = null, long in // write second page as an empty AREA (it's not a page) just to use as lock control stream.Write(new byte[BasePage.PAGE_SIZE], 0, BasePage.PAGE_SIZE); + // create crypto class if has password + var crypto = password != null ? new AesEncryption(password, header.Salt) : null; + // if initial size is defined, lets create empty pages in a linked list if (emptyPages > 0) { @@ -276,9 +279,18 @@ public static void CreateDatabase(Stream stream, string password = null, long in NextPageID = pageID == emptyPages + 1 ? uint.MaxValue : pageID + 1 }; - stream.Write(empty.WritePage(), 0, BasePage.PAGE_SIZE); + var bytes = empty.WritePage(); + + if (password != null) + { + bytes = crypto.Encrypt(bytes); + } + + stream.Write(bytes, 0, BasePage.PAGE_SIZE); } } + + if (crypto != null) crypto.Dispose(); } } } \ No newline at end of file diff --git a/LiteDB/Utils/AesEncryption.cs b/LiteDB/Utils/AesEncryption.cs index c6f4b1ddc..dce98d8d7 100644 --- a/LiteDB/Utils/AesEncryption.cs +++ b/LiteDB/Utils/AesEncryption.cs @@ -17,13 +17,12 @@ public AesEncryption(string password, byte[] salt) _aes = Aes.Create(); _aes.Padding = PaddingMode.Zeros; + var pdb = new Rfc2898DeriveBytes(password, salt); + + using (pdb as IDisposable) { - var pdb = new Rfc2898DeriveBytes(password, salt); - using (pdb as IDisposable) - { - _aes.Key = pdb.GetBytes(32); - _aes.IV = pdb.GetBytes(16); - } + _aes.Key = pdb.GetBytes(32); + _aes.IV = pdb.GetBytes(16); } }